ソースを参照

Project structure improvement

Iñigo Valentin 4 年 前
コミット
9fc8a0bb27
63 ファイル変更2439 行追加865 行削除
  1. 26 0
      application/Application.php
  2. 68 0
      application/Context.php
  3. 282 12
      application/Controller.php
  4. 0 8
      application/config/auth.php
  5. 14 0
      application/config/config.ini
  6. 0 1
      application/config/config.php
  7. 21 32
      application/entity/Entity.php
  8. 107 59
      application/entity/License.php
  9. 404 142
      application/entity/Project.php
  10. 94 54
      application/entity/Project_Image.php
  11. 28 32
      application/entity/Project_Tag.php
  12. 75 46
      application/entity/Project_Type.php
  13. 90 52
      application/entity/Project_Url.php
  14. 51 53
      application/entity/Project_Url_Type.php
  15. 126 0
      application/helper/DB_Helper.php
  16. 97 0
      application/helper/HTML_Helper.php
  17. 20 0
      application/helper/Helper.php
  18. 59 0
      application/helper/NET_Helper.php
  19. 53 13
      application/helper/TEXT_Helper.php
  20. 21 4
      application/helper/db.php
  21. 0 117
      application/helper/net.php
  22. 50 38
      application/page/Home_Page.php
  23. 306 87
      application/page/Page.php
  24. 0 0
      application/resources/text/HELP_COOKIES_TEXT__EN
  25. 0 0
      application/resources/text/HELP_COOKIES_TEXT__ES
  26. 0 0
      application/resources/text/HELP_COOKIES_TEXT__EU
  27. 0 0
      application/resources/text/HELP_INFO_TEXT__EN
  28. 0 0
      application/resources/text/HELP_INFO_TEXT__ES
  29. 0 0
      application/resources/text/HELP_INFO_TEXT__EU
  30. 0 0
      application/resources/text/HELP_LICENSE_TEXT__EN
  31. 0 0
      application/resources/text/HELP_LICENSE_TEXT__ES
  32. 0 0
      application/resources/text/HELP_LICENSE_TEXT__EU
  33. 0 0
      application/resources/text/HELP_PRIVACY_TEXT__EN
  34. 0 0
      application/resources/text/HELP_PRIVACY_TEXT__ES
  35. 0 0
      application/resources/text/HELP_PRIVACY_TEXT__EU
  36. 0 0
      application/resources/text/LICENSE_CC-BY-SA_SUMMARY__EN
  37. 0 0
      application/resources/text/LICENSE_CC-BY-SA_SUMMARY__ES
  38. 0 0
      application/resources/text/LICENSE_CC-BY-SA_SUMMARY__EU
  39. 0 0
      application/resources/text/LICENSE_CC-BY-SA__EN
  40. 0 0
      application/resources/text/LICENSE_CC-BY-SA__ES
  41. 0 0
      application/resources/text/LICENSE_CC-BY-SA__EU
  42. 0 0
      application/resources/text/LICENSE_GPLV3_SUMMARY__EN
  43. 0 0
      application/resources/text/LICENSE_GPLV3_SUMMARY__ES
  44. 0 0
      application/resources/text/LICENSE_GPLV3_SUMMARY__EU
  45. 0 0
      application/resources/text/LICENSE_GPLV3__EN
  46. 0 0
      application/resources/text/LICENSE_GPLV3__ES
  47. 0 0
      application/resources/text/LICENSE_GPLV3__EU
  48. 0 0
      application/resources/text/PROJECT_GM_TEXT__EN
  49. 0 0
      application/resources/text/PROJECT_GM_TEXT__ES
  50. 0 0
      application/resources/text/PROJECT_GM_TEXT__EU
  51. 0 0
      application/resources/text/USER_TEXT__EN
  52. 0 0
      application/resources/text/USER_TEXT__ES
  53. 0 0
      application/resources/text/USER_TEXT__EU
  54. 124 0
      application/util/Log.php
  55. 90 0
      application/util/Path.php
  56. 61 0
      application/util/URL.php
  57. 53 50
      application/view/home.php
  58. 11 11
      application/view/inc/footer.php
  59. 9 9
      application/view/inc/header.php
  60. 2 2
      application/view/project.php
  61. 5 3
      application/view/projects.php
  62. 25 6
      public/index.php
  63. 67 34
      sql/01_structure.sql

+ 26 - 0
application/Application.php

@@ -0,0 +1,26 @@
+<?php
+/**
+ * Constants file.
+ *
+ * Provides some predefined values.
+ *
+ * @author Iñigo Valentin <i@inigovalentin.com>
+ * @license https://www.gnu.org/licenses/gpl-3.0.en.html GNU General Public License V3
+ * @package IV
+ */
+
+    class URL{
+        //static $BASE = Net.get_protocol() . $_SERVER["HTTP_HOST"];
+        //static $STATIC = Net.get_protocol() . $_SERVER["HTTP_HOST"];
+        //static $BASE = ((1 == 1) ? 1 : 0);
+        //static $BASE = $_SERVER["HTTP_HOST"];
+    }
+    
+    final class EXCEPTION_CODE{
+        const PDO_UNSUPPORTED = 800001;
+        const PDO_MYSQL_HOSTNAME = 807701;
+        const PDO_MYSQL_DBNAME = 807702;
+        const PDO_MYSQL_PORT = 807703;
+    }
+
+?>

+ 68 - 0
application/Context.php

@@ -0,0 +1,68 @@
+<?php
+/**
+ * Context file.
+ *
+ * Provides a handy class with usefull data to be accessible from anywhere in the app.
+ *
+ * @author Iñigo Valentin <i@inigovalentin.com>
+ * @license https://www.gnu.org/licenses/gpl-3.0.en.html GNU General Public License V3
+ * @package IV
+ */
+
+/**
+ * Application context.
+ *
+ * Stores usefull data to be retrieved anywhere in the app.
+ *
+ * @category Context
+ */
+class Context {
+
+    /**
+     * @var SQLITE3 Database connection.
+     */
+    private $db;
+
+    /**
+     * Retrieves the database connection.
+     *
+     * @return SQLITE3 The database connection.
+     */
+    public function get_db(){
+        return $this->db;
+    }
+
+    /**
+     * Sets the database connection
+     *
+     * @param SQLITE3 Database connection.
+     */
+    protected function set_db($db){
+        $this->db = $db;
+        Log::debug("Context database is set and accesible.");
+        if ($this->db == null){
+            Log::debug("It is null");
+        }
+    }
+    
+    /**
+     * Retrieves the language.
+     * 
+     * @todo Implement
+     * @return string Two letter language code.
+     */
+    public function get_lang(){
+        return "es";
+    }
+    
+    /**
+     * Retrieves the list of available languages.
+     *
+     * @todo Implement
+     * @return string Two letter language code.
+     */
+    public function get_available_langs(){
+        return [];
+    }
+
+}

+ 282 - 12
application/Controller.php

@@ -1,18 +1,283 @@
 <?php
+/**
+ * Controller file.
+ *
+ * Provides a class to handle all posible request.
+ * 
+ * @author Iñigo Valentin <i@inigovalentin.com>
+ * @license https://www.gnu.org/licenses/gpl-3.0.en.html GNU General Public License V3
+ * @package IVV
+ */
 
-    require_once(__DIR__ . "/config/config.php");
-    require_once($path["helper"] . "db.php");
-    require_once($path["helper"] . "text.php");
-    require_once($path["helper"] . "net.php");
+require_once(__DIR__ . "/Context.php");
+require_once(__DIR__ . "/helper/DB_Helper.php");
+require_once(__DIR__ . "/helper/TEXT_Helper.php");
+require_once(__DIR__ . "/helper/NET_Helper.php");
+require_once(__DIR__ . "/helper/HTML_Helper.php");
+require_once(__DIR__ . "/util/Log.php");
+require_once(__DIR__ . "/util/Path.php");
+require_once(__DIR__ . "/util/URL.php");
+//require_once(__DIR__ . "/helper/net.php");
+//require_once(__DIR__ . "/Application.php");
+//require_once(__DIR__ . "/util/Log.php");
 
 
+/**
+ * Application controller.
+ *
+ * Handles every request, creating the required models and selecting the
+ * view.
+ *
+ * @category Controller
+ */
+class Controller extends Context{
+
+    /**
+     * @var string[] GET parameters received by the controller.
+     */
+    private $params = [];
+
+    /**
+     * @var Context Publicly available context.
+     */
+    private $context;
+    
+    private $command = "";
+    
+    private $status = 201;
+    private $message = "OK";
+    
+
+    /**
+     * Retrieves the application context.
+     * 
+     * @return Context The context.
+     */
+    public function get_context(){
+        return $this->context;
+    }
+    
+    /**
+     * Controller constructor.
+     *
+     * Initializes a database connection and sets up the public context.
+     */
+    public function __construct(){
+        $this->context = new Context();
+        session_start();
+        $configuration = parse_ini_file(__DIR__ . "/config/config.ini", true);
+        $this->db = DB::start($configuration["database"]);
+        $this->context->set_db($this->db);
+        Log::debug("Controller created succesfully");
+    }
+    
+    private function execute_action($action){
+        require_once(PATH::ACTION . $action . ".php");
+        action();
+        return;
+    }
+    
     /**
-     * Application controller.
+     * Handles the request parameters.
+     *
+     * Receives the request params and gets itself ready. Authenticates the user (if needed)
+     * and validates the URL.
      *
-     * Handles every request, creating the required models and selecting the
-     * view.
+     * @param string[] $params GET parameters of the request.
      */
-    class Controller {
+    public function prepare($params){
+
+        // Parse parameters.
+        foreach($params as $p){
+            if (strlen($p) > 0){
+                Log::debug("Controller parameter: $p");
+                array_push($this->params, $p);
+            }
+        }
+        
+        // Obtain the command and process it
+        if (sizeof($this->params) > 0){
+            $this->command = strtoupper($this->params[0]);
+        }
+        Log::debug("Controller command set: '" . $this->command . "'");
+        switch ($this->command){
+            case "": // Main page
+                break;
+            case "API": // An API call
+                return; // End preparation here, the API controller will deal with everything.
+                break;
+            case "ACTION": // Execute an action
+                //$auth_required = true;
+                break;
+            case "PLAYER": // A player page
+                if (sizeof($this->params) > 1){
+                    $player_id = $this->params[1];
+                }
+                else{
+                    $this->status = 400;
+                    $this->message = "Bad request";
+                    return;
+                }
+                break;
+            case "LOGIN": // The login page
+                break;
+            case "REGISTER": // Registration page
+                break;
+            case "SEARCH": // Search results
+                break;
+            case "DUNGEON": // Dungeon static pages
+                break;
+            case "BUILDING": // Building static pages
+                break;
+            case "FUSION": // Fusion static pages
+                break;
+            case "HELP": // Help static page
+                break;
+            case "ERROR": // Error page
+                break;
+            default: // Unknown page
+                
+        }
+    }
+    
+    /**
+     * Executes the required action.
+     *
+     * Must be called after {@see Controller::prepare()}.
+     * @return void|number
+     */
+    public function action(){
+        
+        Log::debug("Executing action");
+        
+        // If there was an error in preparation, it's time to throw it.
+        if ($this->status >= 400 && $this->status < 500){
+            Log::debug("Executing action error code: " . $this->status);
+            require_once(PATH::PAGE . "Error_Page.php");
+            $page = new Error_Page($this->status, $this->message);
+            require_once($page->get_view());
+            http_response_code($this->status);
+            return $this->status;
+        }
+        
+        // Process the command
+        $page = null;
+        switch ($this->command){
+            case "": // Main page
+                require_once(PATH::PAGE . "Home_Page.php");
+                $page = new Home_Page();
+                break;
+            case "ACTION": // Execute an action
+                // If no action specified
+                if (sizeof($this->params) < 2){
+                    require_once(PATH::PAGE . "Error_Page.php");
+                    $page = new Error_Page(400, "No action specified.");
+                    require_once($page->get_view());
+                    http_response_code(400);
+                    return 400;
+                }
+                
+                // Process action
+                $action = strtoupper($this->params[1]);
+                switch ($action){
+                    // Special cases where the output must be printed:
+                    case "OPTIMIZE_LIST":
+                        require_once(PATH::ACTION . "Optimize_List_Action.php");
+                        $action = new Optimize_List_Action();
+                        break;
+                    default:
+                        require_once(PATH::PAGE . "Error_Page.php");
+                        $page = new Error_Page(404, "Action not found");
+                        require_once($page->get_view());
+                        http_response_code(404);
+                        return;
+                }
+                $action->execute();
+                if (strlen($action->get_output()) > 0){
+                    echo($action->get_output());
+                }
+                http_response_code($action->get_code());
+                return $action->get_code();
+                break;
+            case "PLAYER": // A player page
+                if ($this->context->get_player() == null || $this->context->get_player()->get_id() == null){
+                    require_once(PATH::PAGE . "Error_Page.php");
+                    $page = new Error_Page(404, "Player not found");
+                    require_once($page->get_view());
+                    http_response_code(404);
+                    return;
+                }
+                if (
+                  $this->context->get_player()->is_public() == false &&
+                  (
+                      $this->context->get_user() == null ||
+                    $this->context->get_player()->get_user() != $this->context->get_user()->get_id()
+                  )
+                ){
+                    require_once(PATH::PAGE . "Error_Page.php");
+                    $page = new Error_Page(401, "Player profile set to private");
+                    require_once($page->get_view());
+                    http_response_code(401);
+                }
+                $subcommand = "";
+                if (sizeof($this->params) > 2){
+                    $subcommand = strtoupper($this->params[2]);
+                }
+                $page = null;
+                switch ($subcommand){
+                    case "": // Player profile
+                        require_once(PATH::PAGE . "Player_Page.php");
+                        $page = new Player_Page($this->context->get_player()->get_id());
+                        break;
+                    
+                    case "TEAMS":
+                        require_once(PATH::PAGE . "Teams_Page.php");
+                        $page = new Teams_Page();
+                        break;
+                }
+                break;
+            
+            case "BUILDINGS": // Building static pages
+                if (sizeof($this->params) > 1){
+                    require_once(PATH::PAGE . "Building_Page.php");
+                    $page = new Building_Page($this->params[1]);
+                }
+                else{
+                    require_once(PATH::PAGE . "Buildings_Page.php");
+                    $page = new Buildings_Page();
+                }
+                break;
+                break;
+            /*case "FUSION": // Fusion static pages
+                require_once(PATH::PAGE . "Fusion_Page.php");
+                $page = new Fusion_Page();
+                break;*/
+            case "HELP": // Help static page
+                require_once(PATH::PAGE . "Help_Page.php");
+                $page = new Help_Page();
+                break;
+        }
+        
+        // If page is not set, it's a not found.
+        if ($page == null){
+            require_once(PATH::PAGE . "Error_Page.php");
+            $page = new Error_Page(404, "Page not found");
+        }
+        
+        // Once the page is loaded, chack if it can be seen
+        // TODO verify
+        if ($page->get_code() >= 400 && $page->get_code() < 600){
+            require_once(PATH::PAGE . "Error_Page.php");
+            $page = new Error_Page($page->get_code(), $page->get_message());
+            http_response_code($page->get_code());
+        }
+        
+        
+        // Load the view, or set an error code.
+        Log::debug("Loading view: " . $page->get_view());
+        require_once($page->get_view());
+        http_response_code($page->get_code());
+    }
 
         /**
          * Constructor.
@@ -20,7 +285,7 @@
          * Handles every request, creating the required models and selecting the
          * view.
          */
-        public function __construct($params){
+        /*public function __construct($params){
 
             global $path;
             global $base_url;
@@ -28,8 +293,13 @@
             global $static_url;
             global $base_dir;
             $page;
-
-            $db = start_db();
+            
+            header("Content-Type: text/html; charset=utf8");
+            
+            // Read configuration
+            // TODO: Read the other params and do something with them.
+            $configuration = parse_ini_file(__DIR__ . "/config/config.ini", true);
+            $db = DB::start($configuration["database"]);
 
             // Parse parameters, get lang and build the route.
             $pars = [];
@@ -87,6 +357,6 @@
                 $page = new Error_Page($db, $lang);
             }
             require_once($page->view);
-        }
+        }*/
     }
 ?>

+ 0 - 8
application/config/auth.php

@@ -1,8 +0,0 @@
-<?php
-    $auth = [
-        "host" => "localhost",
-        "name" => "web",
-        "user" => "web",
-        "pass" => "XXX"
-    ];
-?>

+ 14 - 0
application/config/config.ini

@@ -0,0 +1,14 @@
+[database]
+type = "mysql"
+host = "localhost"
+port = "3306"
+name = "web"
+user = "web"
+pass = "XXX"
+
+[language]
+available = "en|es|eu"
+default = "es"
+
+[maintenance]
+enable = false

+ 0 - 1
application/config/config.php

@@ -1,6 +1,5 @@
 <?php
 
-    require_once(__DIR__ . "/auth.php");
     require_once(__DIR__ . "/../helper/net.php");
 
     /**

+ 21 - 32
application/entity/Entity.php

@@ -1,35 +1,24 @@
 <?php
 
-    /**
-     * Entity superclass.
-     *
-     * Every other entity must inherit from this one. It represents a database
-     * entity.
-     */
-    abstract class Entity{
-
-        /**
-         * Database connection.
-         */
-        public $db;
-
-        /**
-         * The language of the class's text fields (lowercase, two-letter
-         * language code).
-         */
-        public $lang;
-
-        /**
-         * Constructor.
-         *
-         * Sets the database conection and the language.
-         *
-         * @param MySQL_connection $db Connection to the database.
-         * @param string $lang Lowercase, two-letter language code.
-         */
-        public function __construct($db, $lang){
-            $this->db = $db;
-            $this->lang = $lang;
-        }
+/**
+ * Entity superclass.
+ *
+ * Every other entity must inherit from this one. It represents a database
+ * entity.
+ */
+abstract class Entity{
+    protected $loaded = false;
+    protected $complete = false;
+    protected function mark_as_loaded($loaded){
+        $this->loaded = (bool) $loaded;
+    }
+    protected function mark_as_complete($complete){
+        $this->complete = (bool) $complete;
+    }
+    public function is_loaded(){
+        return $this->loaded;
+    }
+    public function is_complete(){
+        return $this->complete;
     }
-?>
+}

+ 107 - 59
application/entity/License.php

@@ -1,72 +1,120 @@
 <?php
 
-    require_once($path["entity"] . "Entity.php");
-    require_once($path["helper"] . "text.php");
+require_once(PATH::ENTITY . "Entity.php");
 
 
+/**
+ * License.
+ *
+ * Represents an object from the table 'license'.
+ */
+class License extends Entity{
+
     /**
-     * License.
-     *
-     * Represents an object from the table 'license'.
+     * @var string License identifier. Usually an abbreviation.
      */
-    class License extends Entity{
-
-        /**
-         * License identifier. Usually an abbreviation.
-         */
-        public $id;
+    private $id;
 
-        /**
-         * Short, easily readable text summarizing the full text of the
-         * license.
-         */
-        public $summary;
+    /**
+     * @var string Short, easily readable text summarizing the full text of
+     * the license.
+     */
+    private $summary;
 
-        /**
-         * Full text of the license.
-         */
-        public $legal;
+    /**
+     * @var string Full text of the license.
+     */
+    private $legal;
 
-        /**
-         * License logo.
-         */
-        public $logo;
+    /**
+     * @var string License logo.
+     */
+    private $logo;
 
-        /**
-         * License icon, small.
-         */
-        public $icon;
+    /**
+     * @var string License icon, small.
+     */
+    private $icon;
 
-        /**
-         * Constructor.
-         *
-         * Searches the database and retrieves the information about the
-         * object, populating it.
-         *
-         * @param MySQL_connection $db Connection to the database.
-         * @param string $lang Lowercase, two-letter language code.
-         * @param string $id Database identifier of the license.
-         */
-        public function __construct($db, $lang, $id){
-            parent::__construct($db, $lang);
-            $s =
-              "SELECT " .
-              "  id, " .
-              "  summary, " .
-              "  legal, " .
-              "  logo, " .
-              "  icon " .
-              "FROM license " .
-              "WHERE id = '$id'; ";
-            $q = mysqli_query($this->db, $s);
-            if (mysqli_num_rows($q) > 0){
-                $r = mysqli_fetch_array($q);
-                $this->id = $r["id"];
-                $this->summary = text($this, $r["summary"]);
-                $this->legal = text($this, $r["legal"]);
-                $this->logo = $r["logo"];
-                $this->icon = $r["icon"];
-            }
+    /**
+     * Constructor.
+     *
+     * Searches the database and retrieves the information about the
+     * object, populating it.
+     *
+     * @param string $id Identifier of the license.
+     */
+    public function __construct($id){
+        $statement = get_context()->get_db()->prepare("
+          SELECT
+            id
+            summary
+            legal
+            logo
+            icon
+          FROM license
+          WHERE id = :id
+        ");
+        $statement->bindValue(':id', $id, PDO::PARAM_INT);
+        $statement->execute();
+        $r_license = $statement->fetch(PDO::FETCH_ASSOC);
+        if ($r_license !== false){
+            $this->id = $r_license["id"];
+            $this->summary = TEXT::get($r_license["summary"]);
+            $this->legal = TEXT::get($r_license["legal"]);
+            $this->logo = $r_license["logo"];
+            $this->icon = $r_license["icon"];
+            $this->mark_as_loaded(true);
+            $this->mark_as_complete(true);
+        }
+        else{
+            Log::warn("License with id '$id' doesn't exist");
         }
     }
-?>
+    
+    /**
+     * Retrieves the license identifier
+     *
+     * @return string License ID.
+     */
+    public function get_id(){
+        return $this->id;
+    }
+    
+    /**
+     * Retrieves a short, easily readable text summarizing the full text of
+     * the license.
+     *
+     * @return string License summary.
+     */
+    public function get_summary(){
+        return $this->summary;
+    }
+    
+    /**
+     * Retrieves the full text of the license
+     *
+     * @return string License legal text.
+     */
+    public function get_legal(){
+        return $this->legal;
+    }
+    
+    /**
+     * Retrieves the license logo.
+     *
+     * @return string The logo filename.
+     */
+    public function get_logo(){
+        return $this->logo;
+    }
+    
+    /**
+     * Retrieves the license icon.
+     *
+     * @return string The icon filename.
+     */
+    public function get_icon(){
+        return $this->icon;
+    }
+}

+ 404 - 142
application/entity/Project.php

@@ -1,150 +1,412 @@
 <?php
 
-    require_once($path["entity"] . "Entity.php");
-    require_once($path["entity"] . "License.php");
-    require_once($path["entity"] . "Project_Type.php");
-    require_once($path["entity"] . "Project_Image.php");
-    require_once($path["entity"] . "Project_Tag.php");
-    require_once($path["entity"] . "Project_Url.php");
-    require_once($path["helper"] . "text.php");
-
-
-    /**
-     * Project.
-     *
-     * Represents an object from the table 'project'.
-     */
-    class Project extends Entity{
-
-        /**
-         * Project identifier.
-         */
-        public $id;
-
-        /**
-         * Permalink for linking the project (relative).
-         */
-        public $permalink;
-
-        /**
-         * Project index for sorting.
-         */
-        public $idx;
-
-        /**
-         * {@see Project_Type} of project.
-         */
-        public $type;
-
-        /**
-         * Project title in the defined language.
-         */
-        public $title;
-
-        /**
-         * Project logo filename.
-         */
-        public $logo;
-
-        /**
-         * Project summary in the defined language.
-         */
-        public $header;
-
-        /**
-         * Project description in the defined language.
-         */
-        public $text;
-
-        /**
-         * Project {@see License}.
-         */
-        public $license;
-
-        /**
-         * Array with the project {@see Project_Image}.
-         */
-        public $image = [];
-
-        /**
-         * Array with the {@see Project_Tag} of the project.
-         */
-        public $tag = [];
-
-        /**
-         * Array with {@see Project_Url} related to the project.
-         */
-        public $url = [];
-
-        /**
-         * Constructor.
-         *
-         * Searches the database and retrieves the information about the
-         * project, populating it and it's items.
-         *
-         * @param MySQL_connection $db Connection to the database.
-         * @param string $lang Lowercase, two-letter language code.
-         * @param string $id Identifier or permalink of the project.
-         */
-        public function __construct($db, $lang, $id){
-            parent::__construct($db, $lang);
-            $s =
-              "SELECT " .
-              "  id, " .
-              "  permalink, " .
-              "  idx, " .
-              "  type, " .
-              "  title, " .
-              "  logo, " .
-              "  header, " .
-              "  text, " .
-              "  license " .
-              "FROM project " .
-              "WHERE " .
-              "  visible = 1 AND " .
-              "  ( " .
-              "    id = '$id' OR " .
-              "    permalink = '$id' " .
-              "  );";
-            $q = mysqli_query($this->db, $s);
-            if (mysqli_num_rows($q) > 0){
-                $r = mysqli_fetch_array($q);
-                $this->id = $r["id"];
-                $this->permalink = $r["permalink"];
-                $this->idx = $r["idx"];
-                $this->title = text($this, $r["title"]);
-                $this->logo = $r["logo"];
-                $this->header = text($this, $r["header"]);
-                $this->text = text($this, $r["text"]);
-                $this->type = new Project_Type($this->db, $this->lang, $r["type"]);
-                $this->license = new License($this->db, $this->lang, $r["license"]);
+require_once(PATH::ENTITY . "Entity.php");
+require_once(PATH::ENTITY . "License.php");
+require_once(PATH::ENTITY . "Project_Type.php");
+require_once(PATH::ENTITY . "Project_Image.php");
+require_once(PATH::ENTITY . "Project_Url.php");
+
+
+/**
+ * Project.
+ *
+ * Represents an object from the table 'project'.
+ */
+class Project extends Entity{
+
+    /**
+     * @var int Project identifier.
+     */
+    private $id;
+
+    /**
+     * @var string Permalink for linking the project (relative).
+     */
+    private $permalink;
+
+    /**
+     * @var int Project index for sorting.
+     */
+    private $index;
+
+    /**
+     * @var int Project type identifier.
+     */
+    private $type_id;
+    
+    /**
+     * @var Project_Type Type of project.
+     */
+    private $type;
+
+    /**
+     * @var string Project title in the selected language.
+     */
+    private $title;
+
+    /**
+     * @var string Project logo filename.
+     */
+    private $logo;
+
+    /**
+     * @var string Project summary in the selected language.
+     */
+    private $header;
+
+    /**
+     * @var String Project description in the selected language.
+     */
+    private $text;
+    
+    /**
+     * @var int License identifier.
+     */
+    private $license_id;
+
+    /**
+     * @var License Project license.
+     */
+    private $license;
+
+    /**
+     * @var Project_Image[] List of project images.
+     */
+    private $images = [];
+
+    /**
+     * @var Project_Tag[] List of project tags.
+     */
+    private $tags = [];
+
+    /**
+     * @var Project_URL[] List of the project URLs
+     */
+    private $urls = [];
+    
+    /**
+     * @var bool[] Control flags to check loading status.
+     */
+    private $load_flags = [
+        "type" => false,
+        "images" => false,
+        "tags" => false,
+        "urls" => false
+    ];
+
+    /**
+     * Constructor.
+     *
+     * Searches the database and retrieves the information about the
+     * project, populating it and it's items.
+     *
+     * @param string $id Identifier or permalink of the project.
+     */
+    public function __construct($id){
+        Log::debug("Loading project with ID: " . $id);
+        $statement = get_context()->get_db()->prepare("
+          SELECT
+            id,
+            permalink,
+            idx,
+            type,
+            title,
+            logo,
+            header,
+            text,
+            license
+          FROM project
+          WHERE
+            id = :id OR
+            permalink = :permalink
+          LIMIT 1
+        ");
+        $statement->bindValue(':id', $id, PDO::PARAM_INT);
+        $statement->bindValue(':permalink', $id, PDO::PARAM_STR);
+        $statement->execute();
+        $r_project = $statement->fetch(PDO::FETCH_ASSOC);
+        if ($r_project !== false){
+            $this->id = $r_project["id"];
+            $this->permalink = $r_project["permalink"];
+            $this->idx = $r_project["idx"];
+            $this->title = Text::get($r_project["title"]);
+            $this->logo = $r_project["logo"];
+            $this->header = Text::get($r_project["header"]);
+            $this->text = Text::get($r_project["text"]);
+            $this->type_id = $r_project["type"];
+            $this->license_id = $r_project["license"];
+            $this->mark_as_loaded(true);
+        }
+        else{
+            Log::debug("Project with id '$id' doesn't exist");
+        }
+    }
+
+    /**
+     * Retrieves the project identifier
+     *
+     * @return int Project ID.
+     */
+    public function get_id(){
+        return $this->id;
+    }
+
+    /**
+     * Retrieves the project permalink
+     *
+     * @return string Project permalink
+     */
+    public function get_permalink(){
+        return $this->permalink;
+    }
+
+    /**
+     * Retrieves the project index for sorting purpuses.
+     *
+     * @return int Project index
+     */
+    public function get_index(){
+        return $this->index;
+    }
+
+    /**
+     * Loads the project type information.
+     * 
+     * Not exposed, must be called before accessing the project type.
+     */
+    private function load_type(){
+        if ($this->get_flag("type") === false){
+            $this->type = new Project_Type($this->type_id);
+            $this->set_flag("type", true);
+        }
+    }
+    
+    /**
+     * Retrieves the project type.
+     *
+     * @return Project_Type The project type.
+     */
+    public function get_type(){
+        if ($this->get_flag("type") === false){
+            $this->load_type();
+        }
+        return $this->type;
+    }
+
+    /**
+     * Retrieves the project title.
+     *
+     * @return string Title.
+     */
+    public function get_title(){
+        return $this->title;
+    }
+
+    /**
+     * Retrieves the project logo.
+     *
+     * @return string The logo filename.
+     */
+    public function get_logo(){
+        return $this->logo;
+    }
+
+    /**
+     * Retrieves a short project description.
+     *
+     * @return string Project header.
+     */
+    public function get_header(){
+        return $this->header;
+    }
+
+    /**
+     * Retrieves the project description.
+     *
+     * @return string Project description text.
+     */
+    public function get_text(){
+        return $this->text;
+    }
+    
+    /**
+     * Loads the project license information.
+     *
+     * Not exposed, must be called before accessing the project license.
+     */
+    private function load_license(){
+        if ($this->get_flag("license") === false){
+            $this->type = new License($this->licenses_id);
+            $this->set_flag("license", true);
+        }
+    }
+
+    /**
+     * Retrieves the project license.
+     *
+     * @return License Project license.
+     */
+    public function get_license(){
+        if ($this->get_flag("license") === false){
+            $this->load_license();
+        }
+        return $this->license;
+    }
+    
+    /**
+     * Loads the project images.
+     *
+     * Not exposed, must be called before accessing the project images.
+     */
+    private function load_images(){
+        if ($this->get_flag("images") === false){
+            $statement = get_context()->get_db()->prepare("
+              SELECT id 
+              FROM project_image
+              WHERE project = :id
+              ORDER BY idx
+            ");
+            $statement->bindValue(':id', $this->id, PDO::PARAM_INT);
+            $statement->execute();
+            while ($r_image = $statement->fetch(PDO::FETCH_ASSOC)){
+                array_push($this->images, new Project_Image($r_image["id"]));
             }
-            $s_image =
-              "SELECT id " .
-              "FROM project_image " .
-              "WHERE project = " . $this->id . " " .
-              "ORDER BY idx; ";
-            $q_image = mysqli_query($this->db, $s_image);
-            while($r_image = mysqli_fetch_array($q_image)){
-                array_push($this->image, new Project_Image($this->db, $r_image["id"]));
+            $this->set_flag("images", true);
+        }
+    }
+
+    /**
+     * Retrieves the projcet images
+     *
+     * @return Project_Image[] List of the project images.
+     */
+    public function get_images(){
+        if ($this->get_flag("images") === false){
+            $this->load_images();
+        }
+        return $this->images;
+    }
+    
+    /**
+     * Loads the project tags.
+     *
+     * Not exposed, must be called before accessing the project tags.
+     */
+    private function load_tags(){
+        if ($this->get_flag("tags") === false){
+            $statement = get_context()->get_db()->prepare("
+              SELECT id
+              FROM project_tag
+              WHERE project = :id
+            ");
+            $statement->bindValue(':id', $this->id, PDO::PARAM_INT);
+            $statement->execute();
+            while ($r_tag = $statement->fetch(PDO::FETCH_ASSOC)){
+                array_push($this->tags, Text::get($r_tag["id"]));
+            }
+            $this->set_flag("tags", true);
+        }
+    }
+
+    /**
+     * Retrieves the project tags.
+     *
+     * @return Project_Tag[] Project tags. 
+     */
+    public function get_tags(){
+        if ($this->get_flag("tags") === false){
+            $this->load_tags();
+        }
+        return $this->tags;
+    }
+    
+    /**
+     * Loads the project URLs.
+     *
+     * Not exposed, must be called before accessing the project URLs.
+     */
+    private function load_urls(){
+        if ($this->get_flag("urls") === false){
+            $statement = get_context()->get_db()->prepare("
+              SELECT id
+              FROM project_url
+              WHERE project = :id
+            ");
+            $statement->bindValue(':id', $this->id, PDO::PARAM_INT);
+            $statement->execute();
+            while ($r_url = $statement->fetch(PDO::FETCH_ASSOC)){
+                array_push($this->urls, new Project_Url($r_url["id"]));
+            }
+            $this->set_flag("urls", true);
+        }
+    }
+
+    /**
+     * Retrieves the project URLs
+     *
+     * @return Project_URL[] List of the project URLs. 
+     */
+    public function get_urls(){
+        if ($this->get_flag("urls") === false){
+            $this->load_urls();
+        }
+        return $this->urls;
+    }
+
+    /**
+     * Retrieves a state flag
+     *
+     * @return boolean Flag value, false if it doesn't exist. 
+     */
+    private function get_flag($flag){
+        $value = false;
+        if (array_key_exists($flag , $this->load_flags)){
+            $value = $this->load_flags[$flag];
+        }
+        return $value;
+    }
+
+    /**
+     * Sets a load status flag.
+     * 
+     * If, once set, all flags are set to true, mark_as_complete(true)
+     * will be automatically called.
+     * 
+     * @param string $flag Flag name.
+     * @param boolean $value Flag name.
+     */
+    private function set_flag($flag, $value){
+        $key_found = false;
+        $keys = array_keys($this->load_flags);
+        foreach($keys as $key){
+            if ($key === $flag){
+                $key_found = true;
+                break;
             }
-            $s_tag =
-              "SELECT tag " .
-              "FROM project_tag " .
-              "WHERE project = " . $this->id . ";";
-            $q_tag = mysqli_query($this->db, $s_tag);
-            while($r_tag = mysqli_fetch_array($q_tag)){
-                array_push($this->tag, new Project_Tag($this->db, $this->lang, $this->id, $r_tag["tag"]));
+        }
+        if ($key_found == false){
+            Log::warn("Trying to set non-existing flag '$flag' in project");
+            return false;
+        }
+        else{
+            if ($value !== true && $value !== false){
+                Log::warn("Trying to set project flag '$flag' to an invalid value: " . $value);
+                return false;
             }
-            $s_url =
-              "SELECT id " .
-              "FROM project_url " .
-              "WHERE project = " . $this->id . ";";
-            error_log($s_url);
-            $q_url = mysqli_query($this->db, $s_url);
-            while($r_url = mysqli_fetch_array($q_url)){
-                array_push($this->url, new Project_Url($this->db, $this->lang, $r_url["id"]));
+            else{
+                $this->load_flags[$flag] = $value;
+                
+                // Once set, loop all keys to check if they are all true
+                $all_loaded = true;
+                foreach($this->load_flags as $value){
+                    if ($value !== true){
+                        $all_loaded = false;
+                        break;
+                    }
+                }
+                $this->mark_as_complete($all_loaded);
+                return true;
             }
         }
     }
-?>
+}

+ 94 - 54
application/entity/Project_Image.php

@@ -1,62 +1,102 @@
 <?php
 
-    require_once($path["entity"] . "Entity.php");
+require_once(PATH::ENTITY . "Entity.php");
 
 
+/**
+ * Image of a project.
+ *
+ * Represents an object from the table 'project_image'.
+ */
+class Project_Image extends Entity{
+
+    /**
+     * @var int Image identifier.
+     */
+    private $id;
+
+    /**
+     * @var int Project identifier.
+     */
+    private $project;
+
+    /**
+     * @var int Indicates the order position among other images.
+     */
+    private $idx;
+
+    /**
+     * @var int Filename of the image.
+     */
+    private $image;
+
     /**
-     * Image of a project.
+     * Constructor.
+     *
+     * Searches the database and retrieves the information about the
+     * image, populating it.
      *
-     * Represents an object from the table 'project_image'.
-     */
-    class Project_Image extends Entity{
-
-        /**
-         * Image identifier.
-         */
-        public $id;
-
-        /**
-         * Project identifier.
-         */
-        public $project;
-
-        /**
-         * Indicates the order position among other images.
-         */
-        public $idx;
-
-        /**
-         * Filename of the image.
-         */
-        public $image;
-
-        /**
-         * Constructor.
-         *
-         * Searches the database and retrieves the information about the
-         * image, populating it.
-         *
-         * @param MySQL_connection $db Connection to the database.
-         * @param int $id Identifier of the image.
-         */
-        public function __construct($db, $id){
-            parent::__construct($db, null);
-            $s =
-              "SELECT " .
-              "  id, " .
-              "  project, " .
-              "  idx, " .
-              "  image " .
-              "FROM project_image " .
-              "WHERE id = $id ;";
-            $q = mysqli_query($this->db, $s);
-            if (mysqli_num_rows($q) > 0){
-                $r = mysqli_fetch_array($q);
-                $this->id = $r["id"];
-                $this->project = $r["project"];
-                $this->idx = $r["idx"];
-                $this->image = $r["image"];
-            }
+     * @param int $id Identifier of the image.
+     */
+    public function __construct($id){
+        $statement = get_context()->get_db()->prepare("
+          SELECT
+            id,
+            project,
+            idx,
+            image
+          FROM project_image
+          WHERE id = :id
+        ");
+        $statement->bindValue(':id', $id, PDO::PARAM_INT);
+        $statement->execute();
+        $r_image = $statement->fetch(PDO::FETCH_ASSOC);
+        if ($r_image !== false){
+            $this->id = $r_image["id"];
+            $this->project = $r_image["title"];
+            $this->idx = $r_image["idx"];
+            $this->image = $r_image["image"];
+            $this->mark_as_loaded(true);
+            $this->mark_as_complete(true);
+        }
+        else{
+            Log::warn("Project image with id '$id' doesn't exist");
         }
     }
-?>
+    
+    /**
+     * Retrieves the image identifier
+     *
+     * @return int Image ID.
+     */
+    public function get_id(){
+        return $this->id;
+    }
+    
+    /**
+     * Retrieves the project identifier
+     *
+     * @return int Project ID.
+     */
+    public function get_project(){
+        return $this->project;
+    }
+    
+    /**
+     * Retrieves the image index for sorting.
+     *
+     * @return int Image index.
+     */
+    public function get_idx(){
+        return $this->idx;
+    }
+    
+    /**
+     * Retrieves the image filename.
+     *
+     * @return string Image filename.
+     */
+    public function get_image(){
+        return $this->image;
+    }
+}

+ 28 - 32
application/entity/Project_Tag.php

@@ -1,41 +1,37 @@
 <?php
 
-    require_once($path["entity"] . "Entity.php");
-    require_once($path["helper"] . "text.php");
+require_once(PATH::ENTITY . "Entity.php");
 
 
+/**
+ * Tag of a project.
+ *
+ * Represents an object from the table 'project_tag'.
+ */
+class Project_Tag extends Entity{
+
     /**
-     * Tag of a project.
-     *
-     * Represents an object from the table 'project_tag'.
+     * Identifier of the project the tag belongs to.
      */
-    class Project_Tag extends Entity{
-
-        /**
-         * Identifier of the project the tag belongs to.
-         */
-        public $project;
+    public $project;
 
-        /**
-         * Tag text.
-         */
-        public $tag;
+    /**
+     * Tag text.
+     */
+    public $tag;
 
-        /**
-         * Constructor.
-         *
-         * Searches the database and retrieves the information about the
-         * tag, populating it.
-         *
-         * @param MySQL_connection $db Connection to the database.
-         * @param string $lang Lowercase, two-letter language code.
-         * @param int $project Identifier of the project the tag belongs to.
-         * @param string $tag Tag identifier. Same as it's text.
-         */
-        public function __construct($db, $lang, $project, $tag){
-            parent::__construct($db, $lang);
-            $this->project = $project;
-            $this->tag = text($this, $tag);
-        }
+    /**
+     * Constructor.
+     *
+     * Searches the database and retrieves the information about the
+     * tag, populating it.
+     *
+     * @param int $project Identifier of the project the tag belongs to.
+     * @param string $tag Tag identifier. Same as it's text.
+     */
+    public function __construct($db, $lang, $project, $tag){
+        parent::__construct($db, $lang);
+        $this->project = $project;
+        $this->tag = text($this, $tag);
     }
-?>
+}

+ 75 - 46
application/entity/Project_Type.php

@@ -1,57 +1,86 @@
 <?php
 
-    require_once($path["entity"] . "Entity.php");
-    require_once($path["helper"] . "text.php");
+require_once(PATH::ENTITY . "Entity.php");
 
 
+/**
+ * Type of a project.
+ *
+ * Represents an object from the table 'project_type'.
+ */
+class Project_Type extends Entity{
+
     /**
-     * Type of a project.
-     *
-     * Represents an object from the table 'project_type'.
+     * @var int Identifier of the project type.
      */
-    class Project_Type extends Entity{
-
-        /**
-         * Identifier of the project type.
-         */
-        public $id;
+    private $id;
 
-        /**
-         * Type denomination, in the defined language.
-         */
-        public $title;
+    /**
+     * @var String Type denomination, in the defined language.
+     */
+    private $title;
 
-        /**
-         * Type description, in the defined language.
-         */
-        public $summary;
+    /**
+     * @var String Type description, in the defined language.
+     */
+    private $summary;
 
-        /**
-         * Constructor.
-         *
-         * Searches the database and retrieves the information about the
-         * type, populating it.
-         *
-         * @param MySQL_connection $db Connection to the database.
-         * @param string $lang Lowercase, two-letter language code.
-         * @param string $id Identifier of the project type.
-         */
-        public function __construct($db, $lang, $id){
-            parent::__construct($db, $lang);
-            $s =
-              "SELECT " .
-              "  id, " .
-              "  title, " .
-              "  summary " .
-              "FROM project_type " .
-              "WHERE id = '$id' ;";
-            $q = mysqli_query($this->db, $s);
-            if (mysqli_num_rows($q) > 0){
-                $r = mysqli_fetch_array($q);
-                $this->id = $r["id"];
-                $this->title = text($this, $r["title"]);
-                $this->summary = text($this, $r["summary"]);
-            }
+    /**
+     * Constructor.
+     *
+     * Searches the database and retrieves the information about the
+     * type, populating it.
+     *
+     * @param string $id Identifier of the project type.
+     */
+    public function __construct($id){
+        $statement = get_context()->get_db()->prepare("
+          SELECT
+            id,
+            title,
+            summary
+          FROM project_type
+          WHERE id = :id;
+        ");
+        $statement->bindValue(':id', $id, PDO::PARAM_INT);
+        $statement->execute();
+        $r_type = $statement->fetch(PDO::FETCH_ASSOC);
+        if ($r_type !== false){
+            $this->id = $r_type["id"];
+            $this->title = TEXT::get($r_type["title"]);
+            $this->summary = TEXT::get($$r_type["summary"]);
+            $this->mark_as_loaded(true);
+            $this->mark_as_complete(true);
+        }
+        else{
+            Log::warn("Project type with id '$id' doesn't exist");
         }
     }
-?>
+    
+    /**
+     * Retrieves the project type identifier
+     *
+     * @return int Project type ID.
+     */
+    public function get_id(){
+        return $this->id;
+    }
+    
+    /**
+     * Retrieves the project type title.
+     *
+     * @return string Type name.
+     */
+    public function get_title(){
+        return $this->title;
+    }
+    
+    /**
+     * Retrieves the project type description.
+     *
+     * @return string Type summary.
+     */
+    public function get_summary(){
+        return $this->summary;
+    }
+}

+ 90 - 52
application/entity/Project_Url.php

@@ -1,64 +1,102 @@
 <?php
 
-    require_once($path["entity"] . "Entity.php");
-    require_once($path["entity"] . "Project_Url_Type.php");
+require_once(PATH::ENTITY . "Entity.php");
 
 
+/**
+ * URL related to a project.
+ *
+ * Represents an object from the table 'project_url'.
+ */
+class Project_Url extends Entity{
+
     /**
-     * URL related to a project.
-     *
-     * Represents an object from the table 'project_url'.
+     * @var int Identifier of the URL.
      */
-    class Project_Url extends Entity{
+    private $id;
 
-        /**
-         * Identifier of the url.
-         */
-        public $id;
-
-        /**
-         * Identifier of the project the URL is related to.
-         */
-        public $project;
+    /**
+     * @var int Identifier of the project the URL is related to.
+     */
+    private $project;
 
-        /**
-         * URL {@see Project_Url_Type}.
-         */
-        public $type;
+    /**
+     * @var Project_Url_Type URL type.
+     */
+    private $type;
 
-        /**
-         * Full URL address.
-         */
-        public $url;
+    /**
+     * @var string Full URL address.
+     */
+    private $url;
 
-        /**
-         * Constructor.
-         *
-         * Searches the database and retrieves the information about the
-         * url, populating it and its items.
-         *
-         * @param MySQL_connection $db Connection to the database.
-         * @param string $lang Lowercase, two-letter language code.
-         * @param int $id Identifier of the url.
-         */
-        public function __construct($db, $lang, $id){
-            parent::__construct($db, $lang);
-            $s =
-              "SELECT " .
-              "  id, " .
-              "  project, " .
-              "  type, " .
-              "  url " .
-              "FROM project_url " .
-              "WHERE id = $id ;";
-            $q = mysqli_query($this->db, $s);
-            if (mysqli_num_rows($q) > 0){
-                $r = mysqli_fetch_array($q);
-                $this->id = $r["id"];
-                $this->project = $r["project"];
-                $this->url = $r["url"];
-                $this->type = new Project_Url_Type($this->db, $this->lang, $r["type"]);
-            }
+    /**
+     * Constructor.
+     *
+     * Searches the database and retrieves the information about the
+     * url, populating it and its items.
+     *
+     * @param int $id Identifier of the URL.
+     */
+    public function __construct($id){
+        $statement = get_context()->get_db()->prepare("
+          SELECT
+            id
+            project
+            type
+            url
+          FROM project_url
+          WHERE id = :id
+        ");
+        $statement->bindValue(':id', $id, PDO::PARAM_INT);
+        $statement->execute();
+        $r_url = $statement->fetch(PDO::FETCH_ASSOC);
+        if ($r_url !== false){
+            $this->id = $r_url["id"];
+            $this->project = $r_url["project"];
+            $this->type = new Project_Url_Type($r_url["type"]);
+            $this->url = $r_url["url"];
+            $this->mark_as_loaded(true);
+            $this->mark_as_complete(true);
+        }
+        else{
+            Log::warn("Project URL with id '$id' doesn't exist");
         }
     }
-?>
+    
+    /**
+     * Retrieves the URL identifier
+     *
+     * @return int URL ID.
+     */
+    public function get_id(){
+        return $this->id;
+    }
+    
+    /**
+     * Retrieves the project identifier
+     *
+     * @return int Project ID.
+     */
+    public function get_project(){
+        return $this->project;
+    }
+    
+    /**
+     * Retrieves the url type.
+     *
+     * @return Project_Url_Type URL type.
+     */
+    public function get_type(){
+        return $this->type;
+    }
+    
+    /**
+     * Retrieves the URL
+     *
+     * @return string The URL.
+     */
+    public function get_url(){
+        return $this->url;
+    }
+}

+ 51 - 53
application/entity/Project_Url_Type.php

@@ -1,65 +1,63 @@
 <?php
 
-    require_once($path["entity"] . "Entity.php");
-    require_once($path["helper"] . "text.php");
+require_once(PATH::ENTITY . "Entity.php");
 
 
+/**
+ * Type of URL.
+ *
+ * Represents an object from the table 'project_url_type'.
+ */
+class Project_Url_Type extends Entity{
+
     /**
-     * Type of URL.
-     *
-     * Represents an object from the table 'project_url_type'.
+     * URL type identifier.
      */
-    class Project_Url_Type extends Entity{
+    public $id;
 
-        /**
-         * URL type identifier.
-         */
-        public $id;
-
-        /**
-         * URL denomination, in the defined language.
-         */
-        public $title;
+    /**
+     * URL denomination, in the defined language.
+     */
+    public $title;
 
-        /**
-         * URL short explanation, in the defined language.
-         */
-        public $summary;
+    /**
+     * URL short explanation, in the defined language.
+     */
+    public $summary;
 
-        /**
-         * Filename of the logo of the URL. Usually, the logo of the site it
-         * points to.
-         */
-        public $logo;
+    /**
+     * Filename of the logo of the URL. Usually, the logo of the site it
+     * points to.
+     */
+    public $logo;
 
-        /**
-         * Constructor.
-         *
-         * Searches the database and retrieves the information about the
-         * url type, populating it and its items.
-         *
-         * @param MySQL_connection $db Connection to the database.
-         * @param string $lang Lowercase, two-letter language code.
-         * @param int $id Identifier of the url type.
-         */
-        public function __construct($db, $lang, $id){
-            parent::__construct($db, $lang);
-            $s =
-              "SELECT " .
-              "  id, " .
-              "  title, " .
-              "  summary, " .
-              "  logo " .
-              "FROM project_url_type " .
-              "WHERE id = '$id' ;";
-            $q = mysqli_query($this->db, $s);
-            if (mysqli_num_rows($q) > 0){
-                $r = mysqli_fetch_array($q);
-                $this->id = $r["id"];
-                $this->title = text($this, $r["title"]);
-                $this->summary = text($this, $r["summary"]);
-                $this->logo = $r["logo"];
-            }
+    /**
+     * Constructor.
+     *
+     * Searches the database and retrieves the information about the
+     * url type, populating it and its items.
+     *
+     * @param MySQL_connection $db Connection to the database.
+     * @param string $lang Lowercase, two-letter language code.
+     * @param int $id Identifier of the url type.
+     */
+    public function __construct($db, $lang, $id){
+        parent::__construct($db, $lang);
+        $s =
+          "SELECT " .
+          "  id, " .
+          "  title, " .
+          "  summary, " .
+          "  logo " .
+          "FROM project_url_type " .
+          "WHERE id = '$id' ;";
+        $q = mysqli_query($this->db, $s);
+        if (mysqli_num_rows($q) > 0){
+            $r = mysqli_fetch_array($q);
+            $this->id = $r["id"];
+            $this->title = text($this, $r["title"]);
+            $this->summary = text($this, $r["summary"]);
+            $this->logo = $r["logo"];
         }
     }
-?>
+}

+ 126 - 0
application/helper/DB_Helper.php

@@ -0,0 +1,126 @@
+<?php
+/**
+ * DB helper file.
+ *
+ * Provides a helper to perform database operations.
+ *
+ * @author Iñigo Valentin <i@inigovalentin.com>
+ * @license https://www.gnu.org/licenses/gpl-3.0.en.html GNU General Public License V3
+ * @package IV
+ */
+
+require_once(__DIR__ . "/Helper.php");
+
+/**
+ * Database helper.
+ *
+ * Contains database utilities to connect.
+ *
+ * @category Helper.
+ */
+final class DB extends Helper{
+
+    private static function generate_dsn($db_configuration){
+        
+        $type = $db_configuration["type"];
+        $host = $db_configuration["host"];
+        $port = $db_configuration["port"];
+        $name = $db_configuration["name"];
+        Log::debug("Database type: $type");
+        Log::debug("Database host: $host");
+        Log::debug("Database port: $port");
+        Log::debug("Database name: $name");
+        $dsn = "";
+        switch ($type){
+            case "mysql":
+                $dsn = "mysql:";
+                if (filter_var($host, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME) == false){
+                    Log::fatal(
+                      "MySQL driver specified, but hostname is invalid: $host",
+                      EXCEPTION_CODE::PDO_MYSQL_HOSTNAME
+                      );
+                    throw new UnexpectedValueException(
+                      "MySQL driver specified, but hostname is invalid: $host",
+                      EXCEPTION_CODE::PDO_MYSQL_HOSTNAME
+                    );
+                }
+                else{
+                    $dsn .= "host=$host;";
+                }
+                if (
+                  strlen($name) > 64 ||
+                  strpos($name, "\\") !== false ||
+                  strpos($name, "/") !== false ||
+                  strpos($name, ".") !== false
+                ){
+                    Log::fatal(
+                      "MySQL driver specified, but database name is invalid: $name",
+                      EXCEPTION_CODE::PDO_MYSQL_DBNAME
+                    );
+                    throw new UnexpectedValueException(
+                      "MySQL driver specified, but database name is invalid: $name",
+                      EXCEPTION_CODE::PDO_MYSQL_DBNAME
+                    );
+                }
+                else{
+                    $dsn .= "dbname=$name;";
+                }
+                if (intval($port) != 0){
+                    $port = intval($port);
+                    if ($port < 1 || $port > 65534){
+                        Log::fatal(
+                          "MySQL driver specified, but port number name is invalid: $port",
+                          EXCEPTION_CODE::PDO_MYSQL_PORT
+                        );
+                        throw new UnexpectedValueException(
+                          "MySQL driver specified, but port number name is invalid: $port",
+                          EXCEPTION_CODE::PDO_MYSQL_PORT
+                        );
+                    }
+                    else{
+                        $dsn .= "port=$port;";
+                    }
+                }
+                
+                if ($host != null && strlen($host) > 0){
+                    $dsn .= "host=$host;";
+                }
+                break;
+            default:
+                Log::fatal(
+                  "Unsupported batabase driver '$type'.",
+                  EXCEPTION_CODE::PDO_UNSUPPORTED
+                );
+                throw new UnexpectedValueException(
+                  "Unsupported batabase driver '$type'.",
+                  EXCEPTION_CODE::PDO_UNSUPPORTED
+                );
+        }
+        $dsn .= "charset=UTF8;";
+        Log::debug("Database dsn: $dsn");
+        return $dsn;
+    }
+    
+    /**
+     * Creates a database connection using the data in the config files.
+     * 
+     * @return resource Connection to the database.
+     */
+    public static function start($db_configuration){
+        //ini_set('mssql.charset', 'UTF8');
+        $dsn = DB::generate_dsn($db_configuration);
+        $user = $db_configuration["user"];
+        $pass = $db_configuration["pass"];
+        
+        try{
+            $db = new PDO($dsn, $user, $pass, array(PDO::ATTR_PERSISTENT => true));
+            Log::info("Successfully connected with database");
+            //$db->exec('SET NAMES utf8');
+            //$db->exec('SET CHARACTER SET utf8');
+            return $db;
+        }
+        catch(Exception $e){
+            Log::fatal("Connection failed" . $e->getMessage());
+        }
+    }
+}

+ 97 - 0
application/helper/HTML_Helper.php

@@ -0,0 +1,97 @@
+<?php
+/**
+ * HTML helper file.
+ *
+ * Provides utilities to generate HTML content.
+ *
+ * @author Iñigo Valentin <i@inigovalentin.com>
+ * @license https://www.gnu.org/licenses/gpl-3.0.en.html GNU General Public License V3
+ * @package IV
+ */
+
+require_once(__DIR__ . "/Helper.php");
+
+
+/**
+ * HTML helper.
+ *
+ * Contains utilities to generate HTML content.
+ *
+ * @category Helper.
+ */
+final class HTML extends Helper{
+
+    /**
+     * This function closes all the opened HTML tags in a given string.
+     * 
+     * @param string html The string with HTML tags.
+     * @return string HTML with closed tags.
+     */
+    public static function close_tags($html) {
+        $result = [];
+        preg_match_all("#<(?!meta|img|br|hr|input\b)\b([a-z]+)(?: .*)?(?<![/|/ ])>#iU", $html, $result);
+        $openedtags = $result[1];
+        preg_match_all("#</([a-z]+)>#iU", $html, $result);
+        $closedtags = $result[1];
+        $len_opened = count($openedtags);
+        if (count($closedtags) == $len_opened) {
+            return $html;
+        }
+        $openedtags = array_reverse($openedtags);
+        for ($i=0; $i < $len_opened; $i++) {
+            if (!in_array($openedtags[$i], $closedtags)) {
+                $html .= "</".$openedtags[$i].">";
+            } else {
+                unset($closedtags[array_search($openedtags[$i], $closedtags)]);
+            }
+        }
+        return $html;
+    }
+
+
+    /**
+     * Text shortener. Given a string, it trims in the proximity of the
+     * desired string, up to the next white character. If indicated, it will
+     * append a link to the full text.
+     * 
+     * @param string $text The text to shorten.
+     * @param int $length The desired length.
+     * @param string $link_text Text for the link. Optional.
+     * @param string $link URI of the link.
+     * @return string Shortened text.
+     */
+     function cut_text($text, $length, $link_text = "", $link = ""){
+        if (strlen($text) < $length){
+            return $text;
+        }
+        $cut = substr($text, 0, strpos($text, " ", $length));
+        $cut = close_tags($cut);
+        if (strlen($cut) == 0){
+            $cut = $text;
+        }
+        if (strlen($text) != strlen($cut) && strlen($link) > 0 && strlen($link_text) > 0){
+            $cut = $cut . "... <a href='$link'>$link_text</a>";
+        }
+        return $cut;
+    }
+
+    /**
+     * Creates the srcset attribute for content images.
+     * Creates 9 different resolutions, from 100px to 900px.
+     * 
+     * @param string $file Path to the file, relative to $path["img"]["content"]).
+     * @return string srcset atttribute content.
+     */
+    public static function srcset($file){
+        global $static;
+        $srcset = "";
+        $dir = dirname($file);
+        $name = basename($file);
+        foreach (range(1, 9) as $d) {
+            $srcset = $srcset . URL::IMG["CONTENT"] . $dir . "/x" . $d . "00/" . $name . " " . $d . "00px, ";
+        }
+        $srcset = rtrim($srcset, ", ");
+        return $srcset;
+    }
+
+}

+ 20 - 0
application/helper/Helper.php

@@ -0,0 +1,20 @@
+<?php
+/**
+ * Base helper file.
+ *
+ * Provides an empty helper.
+ *
+ * @author Iñigo Valentin <i@inigovalentin.com>
+ * @license https://www.gnu.org/licenses/gpl-3.0.en.html GNU General Public License V3
+ * @package SWDB
+ */
+
+/**
+ * Helper superclass.
+ *
+ * Every other entity must inherit from this one. 
+ * 
+ * @abstract
+ * @category Helper
+ */
+abstract class Helper{}

+ 59 - 0
application/helper/NET_Helper.php

@@ -0,0 +1,59 @@
+<?php
+/**
+ * Netowrk helper file.
+ *
+ * Provides a helper to perform network operations.
+ *
+ * @author Iñigo Valentin <i@inigovalentin.com>
+ * @license https://www.gnu.org/licenses/gpl-3.0.en.html GNU General Public License V3
+ * @package IV
+ */
+
+require_once(__DIR__ . "/Helper.php");
+
+/**
+ * Netowrk helper.
+ *
+ * Contains netowrk related utilities.
+ *
+ * @category Helper.
+ */
+final class NET extends Helper{
+    
+    /**
+     * Finds out the protocol the user is connecting to the site with.
+     *
+     * @return string "http://" or "https://".
+     */
+    public static function get_protocol(){
+        if (isset($_SERVER["HTTPS"]) && ($_SERVER["HTTPS"] == "on" || $_SERVER["HTTPS"] == 1) || isset($_SERVER["HTTP_X_FORWARDED_PROTO"]) && $_SERVER["HTTP_X_FORWARDED_PROTO"] == "https") {
+            $protocol = "https://";
+        }
+        else {
+            $protocol = "http://";
+        }
+        return $protocol;
+    }
+    
+    /**
+     * Finds out the IP address of the client.
+     *
+     * @return string Client IP address.
+     */
+    public static function get_ip(){
+        $client  = @$_SERVER["HTTP_CLIENT_IP"];
+        $forward = @$_SERVER["HTTP_X_FORWARDED_FOR"];
+        $remote  = $_SERVER["REMOTE_ADDR"];
+        if(filter_var($client, FILTER_VALIDATE_IP)){
+            $ip = $client;
+        }
+        elseif(filter_var($forward, FILTER_VALIDATE_IP)){
+            $ip = $forward;
+        }
+        else{
+            $ip = $remote;
+        }
+        return $ip;
+    }
+    
+}

+ 53 - 13
application/helper/text.php → application/helper/TEXT_Helper.php

@@ -1,5 +1,25 @@
 <?php
+/**
+ * DB helper file.
+ *
+ * Provides a helper to perform database operations.
+ *
+ * @author Iñigo Valentin <i@inigovalentin.com>
+ * @license https://www.gnu.org/licenses/gpl-3.0.en.html GNU General Public License V3
+ * @package IV
+ */
 
+require_once(__DIR__ . "/Helper.php");
+
+
+/**
+ * Text helper.
+ *
+ * Contains utilities to show and manipulate texts.
+ *
+ * @category Helper.
+ */
+final class TEXT extends Helper{
 
     /**
      * Selects the language the page will be displayed on.
@@ -8,10 +28,9 @@
      * set, get the browser language preference list. If none is provided or
      * they are not supported, it will use the default language.
      * 
-     * @param MySQL_connection $db Connection to the database.
      * @return string Lowercase, two-letter language code.
      */
-    function select_language($db){
+    function select_language(){
 
         // Get available languages from db
         $available_languages = array();
@@ -142,19 +161,41 @@
     /**
      * Retrieves a text fragment from the database.
      * 
-     * @param Page|Entity $model An initialized data model.
      * @param string $id Text identifier.
+     * @param bool $html True to decode for HTML.
      * @returns string The text.
      */
-    function text($model, $id){
-        global $path;
-        $q = mysqli_query($model->db, "SELECT text, file FROM text WHERE id = '$id' AND lang = '" . $model->lang . "';");
-        $r = mysqli_fetch_array($q);
-        if (strlen($r["file"]) > 0){
-            return file_get_contents($path["string"] . $r["file"]);
+    public static function get($id, $html = true){
+        $statement = get_context()->get_db()->prepare("
+          SELECT
+            text,
+            file
+          FROM text
+          WHERE
+            id = :id AND
+            lang = :lang
+        ");
+        $statement->bindValue(':id', $id, PDO::PARAM_STR);
+        $statement->bindValue(':lang', get_context()->get_lang(), PDO::PARAM_STR);
+        $statement->execute();
+        $row = $statement->fetch(PDO::FETCH_ASSOC);
+        if ($row !== false){
+            $text = "";
+            if (strlen($row["file"]) > 0){
+                $text = file_get_contents(PATH::TEXT . $row["file"]);
+            }
+            else{
+                $text = $row["text"];
+            }
+            $text = utf8_decode($text);
+            if ($html === true){
+                $text = htmlentities($text, ENT_QUOTES);
+            }
+            return $text;
         }
         else{
-            return $r["text"];
+            Log::error("Text resource with id '$id' not found.");
+            return false;
         }
     }
 
@@ -220,7 +261,7 @@
      * @param string $file Path to the file, relative to $path["img"]["content"]).
      * @return string srcset atttribute content.
      */
-    function srcset($file){
+    public static function srcset($file){
         global $static;
         $srcset = "";
         $dir = dirname($file);
@@ -232,5 +273,4 @@
         return $srcset;
     }
 
-?>
-
+}

+ 21 - 4
application/helper/db.php

@@ -1,13 +1,31 @@
 <?php
 
-
     /**
      * Creates a database connection using the data in the config files.
      * 
      * @return MySQL_connection Connection to the database.
      */
-    function start_db(){
-        global $auth;
+    function start_db($db_configuration){
+        $type = $db_configuration["type"];
+        $host = $db_configuration["host"];
+        $port = $db_configuration["port"];
+        $name = $db_configuration["name"];
+        $user = $db_configuration["user"];
+        $pass = $db_configuration["pass"];
+        
+        $dbHost="localhost";
+        $dbName="myDB";
+        $dbUser="root";      //by default root is user name.
+        $dbPassword="";     //password is blank by default
+        try{
+            $dbConn= new PDO("mysql:host=$dbHost;dbname=$dbName",$dbUser,$dbPassword);
+            Echo "Successfully connected with myDB database";
+        } catch(Exception $e){
+            Echo "Connection failed" . $e->getMessage();
+        }  
+        
+        
+        // TODO: Types and port
         $db = mysqli_connect($auth["host"], $auth["user"], $auth["pass"], $auth["name"]);
 
         // Check connection
@@ -18,7 +36,6 @@
 
         //Set encoding options
         mysqli_set_charset($db, "utf-8");
-        header("Content-Type: text/html; charset=utf8");
         mysqli_query($db, "SET NAMES utf8;");
 
         return $db;

+ 0 - 117
application/helper/net.php

@@ -1,117 +0,0 @@
-<?php
-
-
-    /**
-     * Finds out the protocol the user is connecting to the site with.
-     *
-     * @return string "http://" or "https://".
-     */
-    function get_protocol(){
-        if (isset($_SERVER["HTTPS"]) && ($_SERVER["HTTPS"] == "on" || $_SERVER["HTTPS"] == 1) || isset($_SERVER["HTTP_X_FORWARDED_PROTO"]) && $_SERVER["HTTP_X_FORWARDED_PROTO"] == "https") {
-            $protocol = "https://";
-        }
-        else {
-            $protocol = "http://";
-        }
-        return $protocol;
-    }
-
-
-    /**
-     * Finds out the IP address of the client.
-     * 
-     * @return string Client IP address.
-     */
-    function get_ip(){
-        $client  = @$_SERVER["HTTP_CLIENT_IP"];
-        $forward = @$_SERVER["HTTP_X_FORWARDED_FOR"];
-        $remote  = $_SERVER["REMOTE_ADDR"];
-        if(filter_var($client, FILTER_VALIDATE_IP)){
-            $ip = $client;
-        }
-        elseif(filter_var($forward, FILTER_VALIDATE_IP)){
-            $ip = $forward;
-        }
-        else{
-            $ip = $remote;
-        }
-        return $ip;
-    }
-
-
-    /**
-     * Registers the page viewed by the user. If it is first page shown in his
-     * visit, it also registers the visit. 
-     * 
-     * @param MySQL_connection $db Connection to the database.
-     * @param string $section Site section being visited.
-     * @param string $id ID of the entry being visited.
-     */
-    function stats($db, $section, $id){
-
-        // Get client data
-        $ip = get_ip();
-        $browser_data = get_browser(null, true);
-        $os = $browser_data["platform"];
-        $browser = $browser_data["browser"];
-        $uagent = $browser_data["browser_name_pattern"];
-
-        //If bot, do nothing
-        $bot_kw = Array();
-        $bot_kw[0] = "bot";
-        $bot_kw[1] = "spider";
-        $bot_kw[2] = "crawl";
-        $bot_kw[3] = ".com";
-        $bot_kw[4] = ".ru";
-        $bot_kw[5] = "baidu";
-        $bot_kw[6] = "survey";
-        $bot_kw[7] = "scan";
-        $bot_kw[8] = "feed";
-        $bot_kw[9] = "bing";
-        $bot_kw[10] = "yahoo";
-        $bot_kw[11] = "engine";
-        $bot_kw[12] = "preview";
-        $bot_kw[13] = "checker";
-        $bot_kw[14] = "catalog";
-        $bot_kw[15] = "accelerator";
-        $bot_kw[16] = "python";
-        $bot_kw[14] = "qt";
-        $bot_kw[15] = "webdav";
-        $bot_kw[16] = "http";
-        $bot_kw[17] = "url";
-        $bot_kw[18] = "fake";
-        $bot_kw[19] = "library";
-        $bot_kw[20] = "commerce";
-        $bot_kw[21] = "htmlbot";
-        $bot_kw[22] = "fetch";
-        $bot_kw[23] = "googleb";
-        $bot_kw[24] = "facebook";
-        $bot_kw[25] = "whatsapp";
-        $bot_kw[26] = "pinterest";
-        $bot_kw[27] = "scrapy";
-        $bot_kw[28] = "libwww";
-        $bot_kw[29] = "java standard library";
-        $bot_kw[30] = "default browser";
-        $bot_kw[31] = "twingly recon";
-        $bot_kw[32] = "siteexplorer";
-        $bot_kw[33] = "yandex";
-        $bot_kw[34] = "wotbox";
-        $bot_kw[35] = "apache synapse";
-        $bot_kw[36] = "catexplorador";
-        $bot_kw[37] = "internet archive";
-        if (in_array(strtolower($uagent), $bot_kw)){
-            return;
-        }
-
-        //Look for a visit with the same IP in the last 30 mins.
-        $q = mysqli_query($db, "SELECT stat_visit.id AS visitid FROM stat_view, stat_visit WHERE visit = stat_visit.id AND stat_view.dtime > DATE_SUB(now(), INTERVAL 30 MINUTE) AND ip = '$ip' AND uagent = '$uagent';");
-        if (mysqli_num_rows($q) == 0){
-            mysqli_query($db, "INSERT INTO stat_visit (ip, uagent, os, browser) VALUES ('$ip', '$uagent', '$os', '$browser');");
-            $q = mysqli_query($db, "SELECT stat_visit.id AS visitid FROM stat_visit WHERE ip = '$ip' AND uagent = '$uagent' ORDER BY stat_visit.id DESC LIMIT 1;");
-        }
-        $r = mysqli_fetch_array($q);
-        $visit = $r['visitid'];
-        mysqli_query($db, "INSERT INTO stat_view (visit, section, entry) VALUES ($visit, '$section', '$id');");
-    }
-
-?>

+ 50 - 38
application/page/Home_Page.php

@@ -1,48 +1,60 @@
 <?php
 
-    require_once($path["page"] . "Page.php");
-    require_once($path["entity"] . "Project.php");
-    require_once($path["helper"] . "text.php");
+require_once(PATH::PAGE . "Page.php");
+require_once(PATH::ENTITY . "Project.php");
 
+/**
+ * Home page model.
+ */
+class Home_Page extends Page{
+
+    private $max_projects = 3;
 
     /**
-     * Home page model.
+     * List of recent projects.
      */
-    class Home_Page extends Page{
-
-        private $max_projects = 3;
+    private $projects = [];
 
-        /**
-         * List of recent projects.
-         */
-        public $project = [];
+    /**
+     * Constructor.
+     *
+     * Retrieves the data and initializes the variables.
+     */
+    public function __construct(){
+        $this->view = PATH::VIEW . "home.php";
+        parent::__construct();
+        $this->set_view("home.php");
+        $this->set_title(Text::get("USER_NAME"));
+        $this->set_description(Text::get("USER_NAME"));
+        $this->set_canonical("");
+        $this->add_css("home.css");
+        $this->set_code(200);
+        $this->set_message("OK");
 
-        /**
-         * Constructor.
-         *
-         * Retrieves the data and initializes the variables.
-         *
-         * @param MySQL_connection $db Connection to the database.
-         * @param string $lang Lowercase, two-letter language code.
-         */
-        public function __construct($db, $lang){
-            global $path;
-            global $base_url;
-            parent::__construct($db, $lang);
-            $this->view = $path["view"] . "home.php";
-            $s_project =
-              "SELECT id " .
-              "FROM project " .
-              "WHERE visible = 1 " .
-              "ORDER BY idx DESC " .
-              "LIMIT " . $this->max_projects . ";";
-            $q_project = mysqli_query($this->db, $s_project);
-            while($r_project = mysqli_fetch_array($q_project)){
-                array_push($this->project, new Project($this->db, $this->lang, $r_project["id"]));
-            }
-            $this->title = text($this, "USER_NAME");
-            $this->description = text($this, "USER_NAME") . " - " . text($this, "USER_TAGLINE");
-            $this->canonical = $base_url . "/";
+        $statement = get_context()->get_db()->prepare("
+          SELECT id
+          FROM project
+          WHERE visible = 1
+          ORDER BY idx DESC
+          LIMIT :limit
+        ");
+        $statement->bindValue(':limit', $this->max_projects, PDO::PARAM_INT);
+        
+        
+        $statement->execute();
+        while ($r_project = $statement->fetch(PDO::FETCH_ASSOC)) {
+            Log::debug("Instantiating new project: " . $r_project["id"]);
+            array_push($this->project, new Project($r_project["id"]));
         }
+        
     }
-?>
+    /**
+     * Retrieves the projects to show on the page.
+     *
+     * @return Project[] Project list.
+     */
+    public function get_projects(){
+        return $this->projects;
+    }
+
+}

+ 306 - 87
application/page/Page.php

@@ -1,95 +1,314 @@
 <?php
+/**
+ * Page superclass file.
+ *
+ * Provides a class with all the properties and methods to display the page.
+ *
+ * @author Iñigo Valentin <i@inigovalentin.com>
+ * @license https://www.gnu.org/licenses/gpl-3.0.en.html GNU General Public License V3
+ * @package IVV
+ */
 
-    require_once($path["entity"] . "Lang.php");
+/**
+ * Page superclass.
+ *
+ * Every visitable page type must inherit from this one.
+ *
+ * @category Page
+ */
+abstract class Page{
 
     /**
-     * Page superclass.
-     *
-     * Every visitable page type must inherit from this one.
+     * @var string[] List of CSS files required for the page.
      */
-    abstract class Page{
-
-        /**
-         * Database connection.
-         */
-        public $db;
-
-        /**
-         * View associated with the page.
-         */
-        public $view;
-
-        /**
-         * Language the page will be served on (lowercase, two-letter
-         * language code).
-         */
-        public $lang;
-
-        /**
-         * List of available {@see Lang}uages (lowercase, two-letter language code).
-         */
-        public $available_langs = [];
-
-        /**
-         * Title of the page, in the defined language.
-         */
-        public $title;
-
-        /**
-         * Site name.
-         */
-        public $name;
-
-        /**
-         * Page description, in the defined language.
-         */
-        public $description;
-
-        /**
-         * Path to the site favicon.
-         */
-        public $favicon;
-
-        /**
-         * Path to the page icon or main image.
-         */
-        public $icon;
-
-        /**
-         * Canonical URL.
-         */
-        public $canonical;
-
-        /**
-         * Author URL.
-         */
-        public $author;
-
+    private $css = [];
+    
+    /**
+     * @var string Path to the view associated with the page.
+     */
+    private $view;
+    
+    /**
+     * @var string Title of the page, in the defined language.
+     */
+    private $title;
+    
+    /**
+     * @var string Site name.
+     */
+    private $name;
+    
+    /**
+     * @var string Page description, in the defined language.
+     */
+    private $description;
+    
+    /**
+     * @var string URL to the site favicon.
+     */
+    private $favicon;
+    
+    /**
+     * @var string URL to the page icon or main image.
+     */
+    private $icon;
+    
+    /**
+     * @var string Canonical URL.
+     */
+    private $canonical;
+    
+    /**
+     * @var string Autjhor URL.
+     */
+    private $author;
+    
+    /**
+     * @var integer HTTP status code.
+     */
+    private $code = 0;
+    
+    /**
+     * @var string HTTP status message.
+     */
+    private $message = "";
+    
+    /**
+     * Constructor.
+     */
+    public function __construct(){
+        $this->favicon = URL::IMG["LOGO"] . "sw.png";
+        $this->icon = URL::IMG["LOGO"] . "sw.png";
+        $this->name = "SWDB";
+        $this->author = URL::BASE;
+        $this->add_css("ui.css");
+    }
 
-        /**
-         * Constructor.
-         *
-         * @param MySQL_connection $db Connection to the database.
-         * @param string $lang Lowercase, two-letter language code.
-         */
-        public function __construct($db, $lang){
-            global $static;
-            global $base_url;
-            $this->db = $db;
-            $this->lang = $lang;
-            $s_lang =
-              "SELECT code AS id " .
-              "FROM lang " .
-              "WHERE active = 1; ";
-            $q_lang = mysqli_query($db, $s_lang);
-            while($r_lang = mysqli_fetch_array($q_lang)){
-                array_push($this->available_langs, new Lang($db, $r_lang["id"]));
-            }
-            $this->favicon = $static["layout"] . "logo/logo.svg";
-            $this->icon = $static["layout"] . "logo/logo.svg";
-            $this->name = text($this, "USER_NAME");
-            $this->author = $base_url;
-            $this->name = text($this, "USER_NAME");
+    /**
+     * Adds a CSS file to the list.
+     *
+     * @param string $css Css file. Can be the absolute URL, or just the filename.
+     */
+    protected function add_css($css){
+        if (strripos($css, URL::CSS) === false){
+            $file = URL::CSS . $css;
+        }
+        else{
+            $file = $css;
+        }
+        array_push($this->css, $file);
+    }
+    
+    /**
+     * Retrieves the CSS file URLs for the page.
+     *
+     * @return string[] CSS files (by absolute URL.)
+     */
+    public function get_css(){
+        return $this->css;
+    }
+    
+    /**
+     * Sets the page view.
+     *
+     * @param string $view View file. Can be the relative path, or just the filename.
+     */
+    protected function set_view($view){
+        if (strripos($view, PATH::VIEW) === false){
+            $file = PATH::VIEW . $view;
+        }
+        else{
+            $file = $view;
+        }
+        $this->view = $file;
+    }
+    
+    /**
+     * Retrieves the page view file.
+     *
+     * @return string Path to the view file.
+     */
+    public function get_view(){
+        return $this->view;
+    }
+    
+    /**
+     * Sets the page title.
+     *
+     * The string " - SWDB" will always be added at the end.
+     *
+     * @param string $title Page title.
+     */
+    protected function set_title($title){
+        $this->title = htmlentities($title, ENT_QUOTES) . " - SWDB";
+    }
+    
+    /**
+     * Retrieves the page title.
+     *
+     * @return string Page title.
+     */
+    public function get_title(){
+        return $this->title;
+    }
+    
+    /**
+     * Retrieves the site name.
+     *
+     * @return string Site name.
+     */
+    public function get_name(){
+        return $this->name;
+    }
+    
+    /**
+     * Sets the page description.
+     *
+     * The string " - Summoners War DataBase" will always be added at the end.
+     *
+     * @param string $description Page description text.
+     */
+    protected function set_description($description){
+        $this->description = htmlentities($description, ENT_QUOTES) . " - " . Text::get("USER_TAGLINE");
+    }
+    
+    /**
+     * Retrieves the page description
+     *
+     * @return string Page description.
+     */
+    public function get_description(){
+        return $this->description;
+    }
+    
+    /**
+     * Retrieves the URL to the favicon.
+     *
+     * @return string Favicon URL.
+     */
+    public function get_favicon(){
+        return $this->favicon;
+    }
+    
+    /**
+     * Retrieves the URL to the thumbnail image.
+     *
+     * @return string Thmbnail icon URL.
+     */
+    public function get_icon(){
+        return $this->icon;
+    }
+    
+    /**
+     * Sets the page canonical URL.
+     *
+     * @param string $canonical Canonical URL, absolute or relative.
+     */
+    protected function set_canonical($canonical){
+        if (strripos($canonical, URL::BASE) === false){
+            $file = URL::BASE . $canonical;
+        }
+        else{
+            $file = $canonical;
+        }
+        $this->canonical = $file;
+    }
+    
+    /**
+     * Retrieves the canonical URL to the page.
+     *
+     * @return string Canonical URL.
+     */
+    public function get_canonical(){
+        return $this->canonical;
+    }
+    
+    /**
+     * Retrieves the author URL (site main URL).
+     *
+     * @return string AUthor URL.
+     */
+    public function get_author(){
+        return $this->author;
+    }
+    
+    /**
+     * Sets the HTTP status code for the page to return.
+     *
+     * @param int HTTP status code.
+     */
+    protected function set_code($code){
+        if (is_int($code) && $code >= 200 && $code <= 500){
+            $this->code = $code;
+        }
+    }
+    
+    /**
+     * Retrieves the page HTTP status code.
+     *
+     * @return int HTTP status code
+     */
+    public function get_code(){
+        return $this->code;
+    }
+    
+    /**
+     * Sets the HTTP status message for the page to return.
+     *
+     * @param string HTTP status message.
+     */
+    protected function set_message($mesage){
+        $this->message = strval($mesage);
+    }
+    
+    /**
+     * Retrieves the page HTTP status message.
+     *
+     * @return string HTTP status code
+     */
+    public function get_message(){
+        return $this->message;
+    }
+    
+    /**
+     * Generates the content to be inserted in the HTML <head> section.
+     *
+     * It must go between the <head> and </head> tags, and anything can go behind it before closing
+     * the section. It includes page title, description, css files and metadata.
+     *
+     * @return string HTML head content.
+     */
+    public function generate_head(){
+        $head = "
+          <meta content='text/html; charset=utf-8' http-equiv='content-type'/>
+          <meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1'/>
+          <title>" . $this->get_title() . "</title>
+          <link rel='shortcut icon' href='" . $this->get_favicon() . "'/>
+          <!-- CSS files -->";
+        foreach ($this->get_css() as $css){
+            $head .= "
+          <link rel='stylesheet' type='text/css' href='$css'/>";
         }
+        $head .= "
+          <!-- Meta tags -->
+          <link rel='canonical' href='" . $this->get_canonical() . "'/>
+          <link rel='author' href='" . $this->get_author() . "'/>
+          <link rel='publisher' href='" . $this->get_author() . "'/>
+          <meta name='description' content='" . $this->get_description() . "'/>
+          <meta property='og:title' content='" . $this->get_title() . "'/>
+          <meta property='og:url' content='" . $this->get_canonical() . "'/>
+          <meta property='og:description' content='" . $this->get_description() . "'/>
+          <meta property='og:image' content='" . $this->get_icon() . "'/>
+          <meta property='og:site_name' content='" . $this->get_name() . "'/>
+          <meta property='og:type' content='website'/>
+          <meta property='og:locale' content='en'/>
+          <meta name='twitter:card' content='summary'/>
+          <meta name='twitter:title' content='" . $this->get_title() . "'/>
+          <meta name='twitter:description' content='" . $this->get_description() . "'/>
+          <meta name='twitter:image' content='" . $this->get_icon() . "'/>
+          <meta name='twitter:url' content='" . $this->get_canonical() . "'/>
+          <meta name='robots' content='index follow'/>\n";
+        return $head;
     }
-?>
+    
+}

+ 0 - 0
application/view/string/HELP_COOKIES_TEXT__EN → application/resources/text/HELP_COOKIES_TEXT__EN


+ 0 - 0
application/view/string/HELP_COOKIES_TEXT__ES → application/resources/text/HELP_COOKIES_TEXT__ES


+ 0 - 0
application/view/string/HELP_COOKIES_TEXT__EU → application/resources/text/HELP_COOKIES_TEXT__EU


+ 0 - 0
application/view/string/HELP_INFO_TEXT__EN → application/resources/text/HELP_INFO_TEXT__EN


+ 0 - 0
application/view/string/HELP_INFO_TEXT__ES → application/resources/text/HELP_INFO_TEXT__ES


+ 0 - 0
application/view/string/HELP_INFO_TEXT__EU → application/resources/text/HELP_INFO_TEXT__EU


+ 0 - 0
application/view/string/HELP_LICENSE_TEXT__EN → application/resources/text/HELP_LICENSE_TEXT__EN


+ 0 - 0
application/view/string/HELP_LICENSE_TEXT__ES → application/resources/text/HELP_LICENSE_TEXT__ES


+ 0 - 0
application/view/string/HELP_LICENSE_TEXT__EU → application/resources/text/HELP_LICENSE_TEXT__EU


+ 0 - 0
application/view/string/HELP_PRIVACY_TEXT__EN → application/resources/text/HELP_PRIVACY_TEXT__EN


+ 0 - 0
application/view/string/HELP_PRIVACY_TEXT__ES → application/resources/text/HELP_PRIVACY_TEXT__ES


+ 0 - 0
application/view/string/HELP_PRIVACY_TEXT__EU → application/resources/text/HELP_PRIVACY_TEXT__EU


+ 0 - 0
application/view/string/LICENSE_CC-BY-SA_SUMMARY__EN → application/resources/text/LICENSE_CC-BY-SA_SUMMARY__EN


+ 0 - 0
application/view/string/LICENSE_CC-BY-SA_SUMMARY__ES → application/resources/text/LICENSE_CC-BY-SA_SUMMARY__ES


+ 0 - 0
application/view/string/LICENSE_CC-BY-SA_SUMMARY__EU → application/resources/text/LICENSE_CC-BY-SA_SUMMARY__EU


+ 0 - 0
application/view/string/LICENSE_CC-BY-SA__EN → application/resources/text/LICENSE_CC-BY-SA__EN


+ 0 - 0
application/view/string/LICENSE_CC-BY-SA__ES → application/resources/text/LICENSE_CC-BY-SA__ES


+ 0 - 0
application/view/string/LICENSE_CC-BY-SA__EU → application/resources/text/LICENSE_CC-BY-SA__EU


+ 0 - 0
application/view/string/LICENSE_GPLV3_SUMMARY__EN → application/resources/text/LICENSE_GPLV3_SUMMARY__EN


+ 0 - 0
application/view/string/LICENSE_GPLV3_SUMMARY__ES → application/resources/text/LICENSE_GPLV3_SUMMARY__ES


+ 0 - 0
application/view/string/LICENSE_GPLV3_SUMMARY__EU → application/resources/text/LICENSE_GPLV3_SUMMARY__EU


+ 0 - 0
application/view/string/LICENSE_GPLV3__EN → application/resources/text/LICENSE_GPLV3__EN


+ 0 - 0
application/view/string/LICENSE_GPLV3__ES → application/resources/text/LICENSE_GPLV3__ES


+ 0 - 0
application/view/string/LICENSE_GPLV3__EU → application/resources/text/LICENSE_GPLV3__EU


+ 0 - 0
application/view/string/PROJECT_GM_TEXT__EN → application/resources/text/PROJECT_GM_TEXT__EN


+ 0 - 0
application/view/string/PROJECT_GM_TEXT__ES → application/resources/text/PROJECT_GM_TEXT__ES


+ 0 - 0
application/view/string/PROJECT_GM_TEXT__EU → application/resources/text/PROJECT_GM_TEXT__EU


+ 0 - 0
application/view/string/USER_TEXT__EN → application/resources/text/USER_TEXT__EN


+ 0 - 0
application/view/string/USER_TEXT__ES → application/resources/text/USER_TEXT__ES


+ 0 - 0
application/view/string/USER_TEXT__EU → application/resources/text/USER_TEXT__EU


+ 124 - 0
application/util/Log.php

@@ -0,0 +1,124 @@
+<?php
+/**
+ * Logger file.
+ *
+ * Provides an utility to log messages.
+ *
+ * @author Iñigo Valentin <i@inigovalentin.com>
+ * @license https://www.gnu.org/licenses/gpl-3.0.en.html GNU General Public License V3
+ * @package IV
+ */
+
+/**
+ * Logger class.
+ *
+ * Logs different types of messages.
+ *
+ * @category Util
+ */
+final class Log{
+    
+    /**
+     * Reads the log configuration in ../config/config.ini
+     * 
+     * @return mixed[] Log configuration.
+     */
+    private static function read_config(){
+        return $configuration = parse_ini_file(__DIR__ . "/../config/config.ini", true)["log"];
+    }
+    
+    /**
+     * Formats a message for logging, including the current time.
+     * 
+     * @param string $tag Log type tag.
+     * @param string $message Message to log.
+     * @param int $code Log message associated code, optional.
+     * @return string Message, ready to be written.
+     */
+    private static function format($tag, $message, $code = null){
+        $msg = "[" . $tag . "][" . date("c") . "]";
+        if (is_int($code)){
+            $msg .= "[$code]";
+        }
+        $msg .= " $message\n";
+        return $msg;
+    }
+    
+    /**
+     * Sends a debug message.
+     * 
+     * Will do nothing unless the log level is set to DEBUG.
+     * 
+     * @param string $message Message to log.
+     * @param int $code Message associated code, optional.
+     */
+    public static function debug($message, $code = null){
+        $config = Log::read_config();
+        if ($config["level"] == "debug"){
+            $msg = Log::format("DEBUG", $message, $code);
+            file_put_contents($config["file"], $msg, FILE_APPEND);
+        }
+    }
+
+    /**
+     * Sends an informative message.
+     *
+     * Will do nothing unless the log level is set to DEBUG or INFO.
+     *
+     * @param string $message Message to log.
+     * @param int $code Message associated code, optional.
+     */
+    public static function info($message, $code = null){
+        $config = Log::read_config();
+        if (in_array($config["level"], ["debug", "info"])){
+            $msg = Log::format("INFO ", $message, $code);
+            file_put_contents($config["file"], $msg, FILE_APPEND);
+        }
+    }
+
+    /**
+     * Sends a warning message.
+     *
+     * Will do nothing unless the log level is set to WARN.
+     *
+     * @param string $message Message to log.
+     * @param int $code Message associated code, optional.
+     */
+    public static function warn($message, $code = null){
+        $config = Log::read_config();
+        if (in_array($config["level"], ["debug", "info", "warn"])){
+            $msg = Log::format("WARN ", $message, $code);
+            file_put_contents($config["file"], $msg, FILE_APPEND);
+        }
+    }
+
+    /**
+     * Sends an error message.
+     *
+     * Will do nothing if the log level is set to FATAL.
+     *
+     * @param string $message Message to log.
+     * @param int $code Message associated code, optional.
+     */
+    public static function error($message, $code = null){
+        $config = Log::read_config();
+        if (in_array($config["level"], ["debug", "info", "warn", "error"])){
+            $msg = Log::format("ERROR", $message, $code);
+            file_put_contents($config["file"], $msg, FILE_APPEND);
+        }
+    }
+
+    /**
+     * Sends a fatal error message.
+     *
+     * Fatal messages are always logged.
+     *
+     * @param string $message Message to log.
+     * @param int $code Message associated code, optional.
+     */
+    public static function fatal($message, $code = null){
+        $config = Log::read_config();
+        $msg = Log::format("FATAL", $message, $code);
+        file_put_contents($config["file"], $msg, FILE_APPEND);
+    }
+}

+ 90 - 0
application/util/Path.php

@@ -0,0 +1,90 @@
+<?php
+/**
+ * Path constants file.
+ *
+ * Provides paths to everything in the code tree.
+ *
+ * @author Iñigo Valentin <i@inigovalentin.com>
+ * @license https://www.gnu.org/licenses/gpl-3.0.en.html GNU General Public License V3
+ * @package IV
+ */
+
+define('BASE_PATH', $_SERVER["DOCUMENT_ROOT"] . "/");
+
+/**
+ * In-app directories (filesystem, not url).
+ *
+ * @category Data
+ */
+final class PATH{
+    
+    /**
+     * Path to the application direcory.
+     */
+    const APPLICATION = BASE_PATH . "../application/";
+    
+    /**
+     * Path to the public direcory.
+     */
+    const BASE = BASE_PATH;
+    
+    /**
+     * Path to the controller file.
+     */
+    const CONTROLLER = BASE_PATH . "../application/Controller.php";
+    
+    /**
+     * Path to the entity files.
+     */
+    const ENTITY = BASE_PATH . "../application/entity/";
+    
+    /**
+     * Path to the page files.
+     */
+    const PAGE = BASE_PATH . "../application/page/";
+    
+    /**
+     * Path to the action files.
+     */
+    const ACTION = BASE_PATH . "../application/action/";
+    
+    /**
+     * Path to the view files.
+     */
+    const VIEW = BASE_PATH . "../application/view/";
+    
+    /**
+     * Paths to image resources.
+     */
+    const IMG = array(
+        "BASE" => BASE_PATH . "img/",
+        "UNKNOWN" => BASE_PATH . "img/unknown.png",
+        "ICON" => BASE_PATH . "img/icon/",
+        "LOGO" => BASE_PATH . "img/logo/",
+        "CURRENCY" => BASE_PATH . "img/currency/",
+        "UNIT" => BASE_PATH . "img/unit/",
+        "AREA" => BASE_PATH . "img/area/",
+        "SKILL" => BASE_PATH . "img/skill/",
+        "ESSENCE" => BASE_PATH . "img/essence/",
+        "RUNE" => BASE_PATH . "img/rune/",
+        "GRIND" => BASE_PATH . "img/grind/",
+        "ELEMENT" => BASE_PATH . "img/element/",
+        "SKILL_GUILD" => BASE_PATH . "img/skill_guild/",
+        "DECORATION" => BASE_PATH . "img/decoration/",
+        "SOURCE" => BASE_PATH . "img/source/",
+        "SKILL_LEADER" => BASE_PATH . "img/skill_leader/",
+        "INVENTORY" => BASE_PATH . "img/inventory/",
+        "EFFECT" => BASE_PATH . "img/effect/",
+        "BUILDING" => BASE_PATH . "img/building/",
+    );
+    
+    /**
+     * Path to the helper files.
+     */
+    const HELPER = BASE_PATH . "../application/helper/";
+    
+    /**
+     * Path to the text resources.
+     */
+    const TEXT = BASE_PATH . "../application/resources/text/";
+}

+ 61 - 0
application/util/URL.php

@@ -0,0 +1,61 @@
+<?php
+/**
+ * URL constants file.
+ *
+ * Provides the app URLs.
+ *
+ * @author Iñigo Valentin <i@inigovalentin.com>
+ * @license https://www.gnu.org/licenses/gpl-3.0.en.html GNU General Public License V3
+ * @package IV
+ */
+
+/**
+ * The site base URL.
+ */
+define('BASE_URL', NET::get_protocol() . $_SERVER["HTTP_HOST"] . "/");
+
+
+/**
+ * In-app URLs.
+ *
+ * @category Data
+ */
+final class URL{
+
+    /**
+     * Domain, with protcol (includes final '/').
+     */
+    const BASE = BASE_URL;
+    
+    /**
+     * Profile page.
+     */
+    const PROFILE = BASE_URL . "/profile/";
+    
+    /**
+     * Projects page.
+     */
+    const PROJECTS = BASE_URL . "/projects/";
+
+    /**
+     * Fonts full url (includes final '/').
+     */
+    const FONTS = BASE_URL . "/fonts/";
+
+    /**
+     * CSS full url (includes final '/').
+     */
+    const CSS = BASE_URL . "css/";
+
+    /**
+     * URLs to image resources.
+     */
+    const IMG = array(
+        "BASE" => BASE_URL . "img/",
+        "UNKNOWN" => BASE_URL . "img/unknown.png",
+        "ICON" => BASE_URL . "img/icon/",
+        "LOGO" => BASE_URL . "img/logo/",
+        "LAYOUT" => BASE_URL . "img/layout/",
+        "CONTENT" => BASE_URL . "img/content/",
+    );
+}

+ 53 - 50
application/view/home.php

@@ -1,89 +1,92 @@
+<?php
+/**
+ * Home view.
+ *
+ * Contains the view layout and some code to present the data.
+ *
+ * @author Iñigo Valentin <i@inigovalentin.com>
+ * @license https://www.gnu.org/licenses/gpl-3.0.en.html GNU General Public License V3
+ * @package IV
+ * @category View
+ * @property Home_Page $page The page model.
+ * @var Home_Page $page The page model.
+ */
+?>
 <!DOCTYPE html>
-<html lang='<?=$page->lang?>'>
+<html lang='en'>
     <head>
-        <meta content='text/html; charset=utf-8' http-equiv='content-type'/>
-        <meta charset='utf-8'/>
-        <meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1'/>
-        <title><?=$page->title?></title>
-        <link rel='shortcut icon' href='<?=$page->favicon?>'/>
-        <!-- CSS files -->
-        <link rel='stylesheet' type='text/css' href='<?=$static["css"]?>ui.css'/>
-        <link rel='stylesheet' type='text/css' href='<?=$static["css"]?>home.css'/>
-        <!-- Script files -->
-        <script type="text/javascript" src="<?=$static["js"]?>ui.js"></script>
-        <!-- Meta tags -->
-        <link rel='canonical' href='<?=$page->canonical?>'/>
-        <link rel='author' href='<?=$page->author?>'/>
-        <link rel='publisher' href='<?=$page->author?>'/>
-        <meta name='description' content='<?=$page->description?>'/>
-        <meta property='og:title' content='<?=$page->title?>'/>
-        <meta property='og:url' content='<?=$page->canonical?>'/>
-        <meta property='og:description' content='<?=$page->description?>'/>
-        <meta property='og:image' content='<?=$page->icon?>'/>
-        <meta property='og:site_name' content='<?=$page->name?>'/>
-        <meta property='og:type' content='website'/>
-        <meta property='og:locale' content='<?=$page->lang?>'/>
-        <meta name='twitter:card' content='summary'/>
-        <meta name='twitter:title' content='<?=$page->title?>'/>
-        <meta name='twitter:description' content='<?=$page->description?>'/>
-        <meta name='twitter:image' content='<?=$page->icon?>'/>
-        <meta name='twitter:url' content='<?=$page->canonical?>'/>
-        <meta name='robots' content='index follow'/>
+        <?=$page->generate_head()?>
     </head>
     <body>
+        <header>
 <?php
-        include $path["inc"] . "header.php";
+            include __DIR__ . "/inc/header.php";
 ?>
+        </header>
         <main>
             <section id='profile'>
                 <h3>
-                    <?=$page->name?>
+                    <?=$page->get_title()?>
                 </h3>
                 <!-- TODO: Profile form model -->
                 <article>
-                    <img id='profile_image' srcset='<?=srcset("profile/profile.png")?>' src='<?=$static["content"]?>profile/x400/profile.png' alt='<?=text($page, "USER_NAME");?>' title='<?=text($page, "USER_NAME");?>' />
+                    <img
+                      id='profile_image'
+                      srcset='<?=HTML::srcset("profile/profile.png")?>'
+                      src='<?=URL::IMG["CONTENT"]?>profile/x400/profile.png'
+                      alt='<?=TEXT::get("USER_NAME");?>'
+                      title='<?=TEXT::get("USER_NAME");?>'
+                    />
                     <div id='profile_content'>
-                        <h4 id='tagline'><?=text($page, "USER_TAGLINE");?></h4>
-                        <p id='bio'><?=text($page, "USER_BIO");?></p>
+                        <h4 id='tagline'><?=TEXT::get("USER_TAGLINE");?></h4>
+                        <p id='bio'><?=TEXT::get("USER_BIO");?></p>
                     </div>
                     <div class='buttons'>
                         <a class='a_button' title='eMail' href='mailto:i@inigovalentin.com'>
-                            <?=text($page, "INDEX_PROFILE_MAIL");?>
-                            <img class='footer_social_icon' src='<?=$static["layout"]?>social/email.svg' alt='eMail' title='eMail'/>
+                            <?=TEXT::get("INDEX_PROFILE_MAIL");?>
+                            <img class='footer_social_icon' src='<?=URL::IMG["LAYOUT"]?>social/email.svg' alt='eMail' title='eMail'/>
                         </a>
-                        <a class='a_button' href='<?=$base_url?>/profile/'>
-                            <?=text($page, "INDEX_PROFILE_MORE");?>
+                        <a class='a_button' href='<?=URL::PROFILE?>'>
+                            <?=TEXT::get("INDEX_PROFILE_MORE");?>
                         </a>
                     </div>
                 </article>
             </section>
             <section id='projects'>
                 <h3>
-                    <?=text($page, "INDEX_PROJECTS");?>
+                    <?=TEXT::get("INDEX_PROJECTS");?>
                 </h3>
 <?php
-            foreach ($page->project as $project){
+            foreach ($page->get_projects() as $project){
 ?>
                 <article class='project'>
                     <table>
                         <tr>
 <?php
-                            if (strlen($project->logo) > 0){
+                            if (strlen($project->get_logo()) > 0){
 ?>
                                 <td>
-                                    <a href='<?=$base_url?>/project/<?=$project->permalink?>'>
-                                        <img class='project_image' alt='<?=text($page, $project->title)?>' title='<?=text($page, $project->title)?>' srcset='<?=srcset("project/" . $project->logo)?>' src='<?=$static["content"]?>project/x200/<?=$project->logo?>'/>
+                                    <a href='<?=URL::PROJECTS . $project->get_permalink()?>'>
+                                        <img
+                                          class='project_image'
+                                          alt='<?=TEXT::get($project->get_title())?>'
+                                          title='<?=TEXT::get($project->get_title())?>'
+                                          srcset='<?=HTML::srcset("project/" . $project->get_logo())?>'
+                                          src='<?=URL::IMG["CONTENT"]?>project/x200/<?=$project->get_logo()?>'
+                                        />
                                     </a>
                                 </td>
 <?php
                             }
 ?>
                             <td class='project_details'>
-                                <a href='<?=$base_url?>/project/<?=$project->permalink?>'>
-                                    <h4 class='project_name'><?=$project->title?></h4>
-                                </a>
+                                <h4 class='project_name'>
+                                    <a href='<?=URL::PROJECTS . $project->get_permalink()?>'>
+                                        <?=$project->get_title()?>
+                                    </a>
+                                </h4>
                                 <span>
-                                    <?=$project->header?>
+                                    <?=$project->get_header()?>
                                 </span>
                             </td>
                         </tr>
@@ -93,14 +96,14 @@
                 }
 ?>
                 <div class='buttons'>
-                    <a class='a_button' href='<?=$base_url?>/project/'>
-                        <?=text($page, "INDEX_PROJECTS_ALL");?>
+                    <a class='a_button' href='<?=URL::PROJECTS?>'>
+                        <?=TEXT::get("INDEX_PROJECTS_ALL");?>
                     </a>
                 </div>
             </section>
         </main>
 <?php
-        include $path["inc"] . "footer.php";
+        include __DIR__ . "/inc/footer.php";
 ?>
     </body>
 </html>

+ 11 - 11
application/view/inc/footer.php

@@ -5,39 +5,39 @@
         <tr>
             <td id='footer_left'>
                 <span id='footer_follow' class='desktop'>
-                    <?=text($page, "FOOTER_FOLLOW");?>
+                    <?=TEXT::get("FOOTER_FOLLOW");?>
                 </span>
                 <a target='_blank' title='eMail' href='mailto:i@inigovalentin.com'>
-                    <img class='footer_social_icon' src='<?=$static["layout"]?>social/email.svg' alt='eMail' title='eMail'/>
+                    <img class='footer_social_icon' src='<?=URL::IMG["LAYOUT"]?>social/email.svg' alt='eMail' title='eMail'/>
                 </a>
                 <a target='_blank' title='Telegram' href='https://telegram.me/InigoValentin'>
-                    <img class='footer_social_icon' src='<?=$static["layout"]?>social/telegram.svg' alt='Telegram' title='Telegram'/>
+                    <img class='footer_social_icon' src='<?=URL::IMG["LAYOUT"]?>social/telegram.svg' alt='Telegram' title='Telegram'/>
                 </a>
                 <a target='_blank' title='Github' href='https://github.com/InigoValentin'>
-                    <img class='footer_social_icon' src='<?=$static["layout"]?>social/github.svg' alt='Github' title='Github'/>
+                    <img class='footer_social_icon' src='<?=URL::IMG["LAYOUT"]?>social/github.svg' alt='Github' title='Github'/>
                 </a>
                 <a target='_blank' title='LinkedIn' href='https://www.linkedin.com/in/ivalentin'>
-                    <img class='footer_social_icon' src='<?=$static["layout"]?>social/linkedin.svg' alt='LinkedIn' title='LinkedIn'/>
+                    <img class='footer_social_icon' src='<?=URL::IMG["LAYOUT"]?>social/linkedin.svg' alt='LinkedIn' title='LinkedIn'/>
                 </a>
             </td>
             <td id='footer_center'>
-                <?=str_replace("#YEAR#", date("Y"), text($page, "FOOTER_COPY"));?>
+                <?=str_replace("#YEAR#", date("Y"), TEXT::get("FOOTER_COPY"));?>
             </td>
             <td id='footer_right'>
-                <a id='footer_help' href='<?=$base_url?>/help/'>
-                    <?=text($page, "FOOTER_HELP");?>
+                <a id='footer_help' href='<?=URL::BASE?>/help/'>
+                    <?=TEXT::get("FOOTER_HELP");?>
                 </a>
                 <br/><br/>
 <?php
-                foreach ($page->available_langs as $lang){
+                foreach (get_context()->get_available_langs() as $lang){
                     $uri = $_SERVER["REQUEST_URI"];
                     if (substr($uri, 3, 1) == '/'){
                         $uri = substr($uri, 3);
                     }
-                    $dest = $static_url . "/" . $lang->code . $uri;
+                    $dest = URL::BASE . "/" . $lang->code . $uri;
 ?>
                     <a class='lang' href='<?=$dest?>'>
-                        <img src='<?=$static["layout"]?>lang/<?=$lang->code?>.svg' alt='<?=$lang->name?>' title='<?=$lang->name?>'/>
+                        <img src='<?=URL::IMG["LAYOUT"]?>lang/<?=$lang->code?>.svg' alt='<?=$lang->name?>' title='<?=$lang->name?>'/>
                     </a>
 <?php
                 }

+ 9 - 9
application/view/inc/header.php

@@ -4,17 +4,17 @@
         inigoValentin
     </div>
     <nav>
-        <a href='<?=$base_url?>/'>
-            <img src='<?=$static["layout"]?>icon/home.svg'/>
-            <?=text($page, "SECTION_HOME")?>
+        <a href='<?=URL::BASE?>/'>
+            <img src='<?=URL::IMG["LAYOUT"]?>icon/home.svg'/>
+            <?=TEXT::get("SECTION_HOME")?>
         </a>
-        <a href='<?=$base_url?>/profile/'/>
-            <img src='<?=$static["layout"]?>icon/profile.svg'/>
-            <?=text($page, "SECTION_ME")?>
+        <a href='<?=URL::BASE?>/profile/'>
+            <img src='<?=URL::IMG["LAYOUT"]?>icon/profile.svg'/>
+            <?=TEXT::get("SECTION_ME")?>
         </a>
-        <a href='<?=$base_url?>/project/'>
-            <img src='<?=$static["layout"]?>icon/project.svg'/>
-            <?=text($page, "SECTION_PROJECTS")?>
+        <a href='<?=URL::BASE?>/project/'>
+            <img src='<?=URL::IMG["LAYOUT"]?>icon/project.svg'/>
+            <?=TEXT::get("SECTION_PROJECTS")?>
         </a>
     </nav>
 </header>

+ 2 - 2
application/view/project.php

@@ -76,12 +76,12 @@
                                 foreach($page->project->url as $url){
                                     if ($url->type->id != "E"){
 ?>
-                                        <td>
+                                        <div>
                                             <a target='_blank' class='link' href='<?=$url->url?>'>
                                                 <img class='link' title='<?=$url->type->title?>' alt='<?=$url->type->title?>' src='<?=$static["layout"]?>social/<?=$url->type->logo?>'/>
                                                 <?=$url->type->summary?>
                                             </a>
-                                        </td>
+                                        </div>
 <?php
                                     }
                                 }

+ 5 - 3
application/view/projects.php

@@ -55,9 +55,11 @@
                                     }
 ?>
                                 <td class='project_details'>
-                                    <a href='<?=$base_url?>/project/<?=$project->permalink?>'>
-                                        <h4 class='project_name'><?=$project->title?></h4>
-                                    </a>
+                                    <h4 class='project_name'>
+                                        <a href='<?=$base_url?>/project/<?=$project->permalink?>'>
+                                            <?=$project->title?>
+                                        </a>
+                                    </h4>
                                     <span>
                                         <?=$project->header?>
                                     </span>

+ 25 - 6
public/index.php

@@ -1,7 +1,26 @@
 <?php
-    include "../application/config/config.php";
-    include_once($path["controller"]);
-    $request = $_SERVER['REQUEST_URI'];
-    $params = explode('/', $request);
-    $controller = new Controller($params);
-?>
+
+/**
+ * Index page.
+ *
+ * Receives every request and instantiates a {@see Constroller}.
+ *
+ * @category Public
+ */
+
+require_once(__DIR__ . "/../application/Controller.php");
+
+$controller = new Controller();
+
+$request = parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
+$params = explode('/', $request);
+
+function get_context(){
+    global $controller;
+    return $controller->get_context();
+}
+
+$request = parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
+$params = explode('/', $request);
+$controller->prepare($params);
+$controller->action();

+ 67 - 34
sql/01_structure.sql

@@ -1,4 +1,4 @@
-DROP DATABASE IF EXISTS web;
+DROP DATABASE IF EXISTS webpre;
 CREATE DATABASE web CHARACTER SET utf8 COLLATE utf8_bin;
 
 SET NAMES utf8;
@@ -12,85 +12,117 @@ CREATE TABLE lang (
 );
 
 CREATE TABLE text (
-    id       VARCHAR(32),
-    lang     VARCHAR(2)     REFERENCES lang.code,
+    id       VARCHAR(32)    NOT NULL,
+    lang     VARCHAR(2)     NOT NULL,
     section  VARCHAR(32),
     text     VARCHAR(5000),
     file     VARCHAR(32),
-    PRIMARY KEY(id, lang)
+    PRIMARY KEY(id, lang),
+    FOREIGN KEY (lang) REFERENCES lang(code)
 );
+
+CREATE TABLE user (
+    id          INT             AUTO_INCREMENT PRIMARY KEY,
+    name        VARCHAR(32)     NOT NULL UNIQUE,
+    mail        VARCHAR(128)    NOT NULL UNIQUE,
+    first_name  VARCHAR(64),
+    lat_name    VARCHAR(64),
+    passord     VARCHAR(256),
+    salt        VARCHAR(256)
+);
+
 CREATE INDEX i_text ON text(id, lang);
 
 CREATE TABLE share(
     id       INT           AUTO_INCREMENT PRIMARY KEY,
     idx      INT           NOT NULL UNIQUE,
     visible  BOOLEAN       NOT NULL DEFAULT 1,
-    title    VARCHAR(32)   NOT NULL UNIQUE REFERENCES text.id,
-    text     VARCHAR(32)   REFERENCES text.id,
+    title    VARCHAR(32)   NOT NULL UNIQUE,
+    text     VARCHAR(32),
     icon     VARCHAR(16)   NOT NULL,
-    url      VARCHAR(256)  NOT NULL
+    url      VARCHAR(256)  NOT NULL,
+    FOREIGN KEY (title) REFERENCES text(id),
+    FOREIGN KEY (text)  REFERENCES text(id)
 );
 
 CREATE TABLE license (
     id       VARCHAR(32)  PRIMARY KEY,
-    summary  VARCHAR(32)  NOT NULL REFERENCES text.id,
-    legal    VARCHAR(32)  NOT NULL REFERENCES text.id,
+    summary  VARCHAR(32)  NOT NULL,
+    legal    VARCHAR(32)  NOT NULL,
     logo     VARCHAR(32),
-    icon     VARCHAR(32)
+    icon     VARCHAR(32),
+    FOREIGN KEY (summary)   REFERENCES text(id),
+    FOREIGN KEY (legal)     REFERENCES text(id)
 );
 
 CREATE TABLE project_type (
     id       VARCHAR(1)   NOT NULL PRIMARY KEY,
-    title    VARCHAR(32)  NOT NULL REFERENCES text.id,
-    summary  VARCHAR(32)  NOT NULL REFERENCES text.id
+    title    VARCHAR(32)  NOT NULL,
+    summary  VARCHAR(32)  NOT NULL,
+    FOREIGN KEY (title)     REFERENCES text(id),
+    FOREIGN KEY (summary)   REFERENCES text(id)
 );
 
 CREATE TABLE project (
     id         INT           AUTO_INCREMENT PRIMARY KEY,
     permalink  VARCHAR(300)  NOT NULL,
     idx        INT           NOT NULL UNIQUE,
-    type       VARCHAR(1)    NOT NULL REFERENCES project_type.id,
-    title      VARCHAR(32)   NOT NULL REFERENCES text.id,
+    type       VARCHAR(1)    NOT NULL,
+    title      VARCHAR(32)   NOT NULL,
     logo       VARCHAR(32),
-    header     VARCHAR(32)   REFERENCES text.id,
-    text       VARCHAR(32)   REFERENCES text.id,
-    license    VARCHAR(32)   REFERENCES license.id,
-    user       INT           NOT NULL REFERENCES user.id,
-    visible    BOOLEAN       NOT NULL DEFAULT 1
+    header     VARCHAR(32),
+    text       VARCHAR(32),
+    license    VARCHAR(32),
+    user       INT           NOT NULL,
+    visible    BOOLEAN       NOT NULL DEFAULT 1,
+    FOREIGN KEY (type)      REFERENCES project_type(id),
+    FOREIGN KEY (title)     REFERENCES text(id),
+    FOREIGN KEY (header)    REFERENCES text(id),
+    FOREIGN KEY (text)      REFERENCES text(id),
+    FOREIGN KEY (license)   REFERENCES license(id),
+    FOREIGN KEY (user)      REFERENCES user(id)
 );
 
 CREATE TABLE project_url_type (
     id       VARCHAR(1)   NOT NULL PRIMARY KEY,
-    title    VARCHAR(32)  REFERENCES text.id,
-    summary  VARCHAR(32)  REFERENCES text.id,
-    logo     VARCHAR(32)
+    title    VARCHAR(32),
+    summary  VARCHAR(32),
+    logo     VARCHAR(32),
+    FOREIGN KEY (title) REFERENCES text(id),
+    FOREIGN KEY (summary) REFERENCES text(id)
 );
 
 CREATE TABLE project_url (
     id       INT            AUTO_INCREMENT PRIMARY KEY,
-    project  INT            NOT NULL REFERENCES project.id,
-    type     VARCHAR(1)     NOT NULL REFERENCES project_url_type,
-    url      VARCHAR(1024)  NOT NULL
+    project  INT            NOT NULL,
+    type     VARCHAR(1)     NOT NULL,
+    url      VARCHAR(1024)  NOT NULL,
+    FOREIGN KEY (project) REFERENCES project(id),
+    FOREIGN KEY (type) REFERENCES project_url_type(id)
 );
 
 CREATE TABLE project_image (
     id       INT           AUTO_INCREMENT PRIMARY KEY,
-    project  INT           NOT NULL REFERENCES project.id,
+    project  INT           NOT NULL,
     idx      INT           NOT NULL,
-    image    VARCHAR(200)  NOT NULL
+    image    VARCHAR(200)  NOT NULL,
+    FOREIGN KEY (project) REFERENCES project(id)
 );
 
 CREATE TABLE project_tag (
-    project  INT          NOT NULL REFERENCES project.id,
-    tag      VARCHAR(32)  NOT NULL REFERENCES text.id,
-    PRIMARY KEY (project, tag)
+    project  INT          NOT NULL,
+    tag      VARCHAR(32)  NOT NULL,
+    PRIMARY KEY (project, tag),
+    FOREIGN KEY (project)   REFERENCES project(id),
+    FOREIGN KEY (tag)       REFERENCES text(id)
 );
 
 CREATE TABLE cv (
     id         INT           AUTO_INCREMENT PRIMARY KEY,
-    lang       VARCHAR(2)    REFERENCES lang.code,
+    lang       VARCHAR(2)    NOT NULL,
     file       VARCHAR(300)  NOT NULL,
-    visible    BOOLEAN       NOT NULL DEFAULT 1
+    visible    BOOLEAN       NOT NULL DEFAULT 1,
+    FOREIGN KEY (lang) REFERENCES lang(code)
 );
 
 CREATE TABLE settings (
@@ -110,10 +142,11 @@ CREATE TABLE stat_visit (
 
 CREATE TABLE stat_view (
     id       INT           NOT NULL AUTO_INCREMENT PRIMARY KEY,
-    visit    INT           NOT NULL REFERENCES stat_visit.id,
+    visit    INT           NOT NULL,
     section  VARCHAR(80)   NOT NULL,
     entry    VARCHAR(100)  NOT NULL,
-    dtime    TIMESTAMP     NOT NULL DEFAULT now()
+    dtime    TIMESTAMP     NOT NULL DEFAULT now(),
+    FOREIGN KEY (visit) REFERENCES stat_visit(id)
 );
 CREATE INDEX i_visit ON stat_view(visit, dtime);