Просмотр исходного кода

Merge branch 'INTERNAL' into V1

Iñigo Valentin 8 лет назад
Родитель
Сommit
d6b88ede8d
44 измененных файлов с 3783 добавлено и 4 удалено
  1. 2 0
      .gitignore
  2. 3 3
      sql/02_DATA.SQL
  3. 68 0
      www-admin/authentication.php
  4. 31 0
      www-admin/blog/delete.php
  5. 28 0
      www-admin/blog/deletecomment.php
  6. 288 0
      www-admin/blog/edit.php
  7. 149 0
      www-admin/blog/index.php
  8. 20 0
      www-admin/blog/new.php
  9. 28 0
      www-admin/blog/uploadimage.php
  10. 29 0
      www-admin/blog/visible.php
  11. 116 0
      www-admin/css/blog.css
  12. 29 0
      www-admin/css/home.css
  13. 0 0
      www-admin/css/m/ui.css
  14. 169 0
      www-admin/css/projects.css
  15. 5 0
      www-admin/css/stats.css
  16. 481 0
      www-admin/css/ui.css
  17. BIN
      www-admin/fonts/Nunito-Bold.ttf
  18. BIN
      www-admin/fonts/Nunito-Light.ttf
  19. BIN
      www-admin/fonts/Nunito-Regular.ttf
  20. 93 0
      www-admin/fonts/OFL.txt
  21. 354 0
      www-admin/functions.php
  22. 37 0
      www-admin/functions/save.php
  23. 87 0
      www-admin/header.php
  24. BIN
      www-admin/img/icon/cancel.png
  25. BIN
      www-admin/img/icon/edit.png
  26. BIN
      www-admin/img/icon/save.png
  27. 101 0
      www-admin/index.php
  28. 14 0
      www-admin/login.php
  29. 33 0
      www-admin/projects/delete.php
  30. 28 0
      www-admin/projects/deletecomment.php
  31. 28 0
      www-admin/projects/deleteversion.php
  32. 421 0
      www-admin/projects/edit.php
  33. 161 0
      www-admin/projects/index.php
  34. 20 0
      www-admin/projects/new.php
  35. 28 0
      www-admin/projects/uploadimage.php
  36. 29 0
      www-admin/projects/visible.php
  37. 69 0
      www-admin/script/blog.js
  38. 136 0
      www-admin/script/projects.js
  39. 227 0
      www-admin/script/stats.js
  40. 204 0
      www-admin/script/ui.js
  41. 0 0
      www-admin/settings/index.php
  42. 0 0
      www-admin/settings/profile.php
  43. 266 0
      www-admin/stats/index.php
  44. 1 1
      www/functions.php

+ 2 - 0
.gitignore

@@ -32,3 +32,5 @@ www-admin/.htpasswd
 www/.htaccess_local
 www-admin/.htaccess_local
 
+#3rd party software
+www-admin/script/ckeditor

+ 3 - 3
sql/02_DATA.SQL

@@ -14,10 +14,10 @@ INSERT INTO text VALUES ('MONTH_03', 'es', 'DATE', 'marzo', null);
 INSERT INTO text VALUES ('MONTH_03', 'en', 'DATE', 'March', null);
 INSERT INTO text VALUES ('MONTH_03', 'eu', 'DATE', 'martxoa', null);
 INSERT INTO text VALUES ('MONTH_04', 'es', 'DATE', 'abril', null);
-INSERT INTO text VALUES ('MONTH_04', 'ee', 'DATE', 'April', null);
+INSERT INTO text VALUES ('MONTH_04', 'en', 'DATE', 'April', null);
 INSERT INTO text VALUES ('MONTH_04', 'eu', 'DATE', 'apirila', null);
 INSERT INTO text VALUES ('MONTH_05', 'es', 'DATE', 'mayo', null);
-INSERT INTO text VALUES ('MONTH_05', 'ee', 'DATE', 'May', null);
+INSERT INTO text VALUES ('MONTH_05', 'en', 'DATE', 'May', null);
 INSERT INTO text VALUES ('MONTH_05', 'eu', 'DATE', 'maiatza', null);
 INSERT INTO text VALUES ('MONTH_06', 'es', 'DATE', 'junio', null);
 INSERT INTO text VALUES ('MONTH_06', 'en', 'DATE', 'June', null);
@@ -35,7 +35,7 @@ INSERT INTO text VALUES ('MONTH_10', 'es', 'DATE', 'octubre', null);
 INSERT INTO text VALUES ('MONTH_10', 'en', 'DATE', 'October', null);
 INSERT INTO text VALUES ('MONTH_10', 'eu', 'DATE', 'urria', null);
 INSERT INTO text VALUES ('MONTH_11', 'es', 'DATE', 'noviembre', null);
-INSERT INTO text VALUES ('MONTH_11', 'en', 'DATE', 'november', null);
+INSERT INTO text VALUES ('MONTH_11', 'en', 'DATE', 'November', null);
 INSERT INTO text VALUES ('MONTH_11', 'eu', 'DATE', 'azaroa', null);
 INSERT INTO text VALUES ('MONTH_12', 'es', 'DATE', 'diciembre', null);
 INSERT INTO text VALUES ('MONTH_12', 'en', 'DATE', 'December', null);

+ 68 - 0
www-admin/authentication.php

@@ -0,0 +1,68 @@
+<?php
+    session_start();
+    $http_host = $_SERVER["HTTP_HOST"];
+    $doc_root = $_SERVER["DOCUMENT_ROOT"] . "/";
+    include $doc_root . "functions.php";
+    $proto = get_protocol();
+    $con = start_db();
+    $server = $proto . $http_host;
+    $pserver = substr($server, 0, strpos($http_host, ':')); // public server
+    if (check_session($con)){
+        header("Location: /");
+        exit(-1);
+    }
+    else{
+?>
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta content="text/html; charset=windows-1252" http-equiv="content-type"/>
+        <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1">
+        <link rel='shortcut icon' href='<?=$pserver?>/img/logo/x2/favicon.ico'/>
+        <title>Administration</title>
+        <style>
+<?php
+            include $doc_root . "css/ui.css";
+            include $doc_root . "css/home.css";
+?>
+        </style>
+        <!-- CSS for mobile version -->
+        <style media='(max-width : 990px)'>
+<?php
+            include $doc_root . "css/m/ui.css";
+            include $doc_root . "css/m/home.css";
+?>
+        </style>
+        <!-- Script files -->
+        <script type='text/javascript'>
+<?php
+            include $doc_root . "script/ui.js";
+?>
+        </script>
+    </head>
+    <body>
+        <div class='section' id='authentication'>
+            <h3 class='section_title'>Log In</h3>
+            <div class='entry'>
+                <form method='post' action='/login.php'>
+                    <table>
+                        <tr>
+                            <td>User:</td>
+                            <td><input name="user" type="text"/></td>
+                        </tr>
+                        <tr>
+                            <td>Password:</td>
+                            <td><input name="pass" type="password"/></td>
+                        </tr>
+                        <tr>
+                            <td></td><td><input type="submit" value="Entrar"/></td>
+                        </tr>
+                    </table>
+                </form>
+            </div>
+        </div>
+    </body>
+</html>
+<?php
+    }
+?>

+ 31 - 0
www-admin/blog/delete.php

@@ -0,0 +1,31 @@
+<?php
+    session_start();
+    $http_host = $_SERVER["HTTP_HOST"];
+    $doc_root = $_SERVER["DOCUMENT_ROOT"] . "/";
+    include $doc_root . "functions.php";
+    $proto = get_protocol();
+    $con = start_db();
+    $server = $proto . $http_host;
+    $pserver = substr($server, 0, strpos($http_host, ':')); // public server
+    if (!check_session($con)){
+        http_response_code(401); // Not logged in: Forbidden.
+    }
+    else{
+        $id = intval(mysqli_real_escape_string($con, $_GET["p"]));
+        if (strlen($id) < 1){
+            http_response_code(400); // No or bad ID: Bad request.
+        }
+        else{
+            if (mysqli_num_rows(mysqli_query($con, "SELECT id FROM post WHERE id = $id;")) == 0){
+                http_response_code(404); // Unexistent id: Not found.
+            }
+            else{
+                mysqli_query($con, "DELETE FROM post_tag WHERE project = $id;");
+                mysqli_query($con, "DELETE FROM post_image WHERE project = $id;");
+                mysqli_query($con, "DELETE FROM post_comment WHERE project = $id;");
+                mysqli_query($con, "DELETE FROM post WHERE id = $id;");
+                http_response_code(200); //OK.
+            }
+        }
+    }
+?>

+ 28 - 0
www-admin/blog/deletecomment.php

@@ -0,0 +1,28 @@
+<?php
+    session_start();
+    $http_host = $_SERVER["HTTP_HOST"];
+    $doc_root = $_SERVER["DOCUMENT_ROOT"] . "/";
+    include $doc_root . "functions.php";
+    $proto = get_protocol();
+    $con = start_db();
+    $server = $proto . $http_host;
+    $pserver = substr($server, 0, strpos($http_host, ':')); // public server
+    if (!check_session($con)){
+        http_response_code(401); // Not logged in: Forbidden.
+    }
+    else{
+        $id = intval(mysqli_real_escape_string($con, $_GET["i"]));
+        if (strlen($id) < 1){
+            http_response_code(400); // No or bad ID: Bad request.
+        }
+        else{
+            if (mysqli_num_rows(mysqli_query($con, "SELECT id FROM post_comment WHERE id = $id;")) == 0){
+                http_response_code(404); // Unexistent id: Not found.
+            }
+            else{
+                mysqli_query($con, "DELETE FROM post_comment WHERE id = $id;");
+                http_response_code(200); //OK.
+            }
+        }
+    }
+?>

+ 288 - 0
www-admin/blog/edit.php

@@ -0,0 +1,288 @@
+<?php
+    session_start();
+    $http_host = $_SERVER["HTTP_HOST"];
+    $doc_root = $_SERVER["DOCUMENT_ROOT"] . "/";
+    include $doc_root . "functions.php";
+    $proto = get_protocol();
+    $lang = "en";
+    $con = start_db();
+    $server = $proto . $http_host;
+    $pserver = $proto . substr($http_host, 0, strpos($http_host, ':')); // public server
+    if (!check_session($con)){
+        http_response_code(401);
+        header("Location: /authentication.php");
+    }
+    else{
+        $id = intval(mysqli_real_escape_string($con, $_GET["p"]));
+        if (strlen($id) < 1){
+            http_response_code(400); // No or bad ID: Bad request.
+            header("Location: /blog/");
+        }
+        else{
+            $q_post = mysqli_query($con, "SELECT * FROM post WHERE id = $id;");
+            if (mysqli_num_rows($q_post) == 0){
+                http_response_code(404); // Unexistent id: Not found.
+                header("Location: /blog/");
+            }
+            else{
+                $r_post = mysqli_fetch_array($q_post);
+                $id = $r_post["id"];
+                $title = text($con, $r_post["title"], $lang);
+                $q_lang = mysqli_query($con, "SELECT * FROM lang;");
+?>
+<html>
+    <head>
+        <meta content='text/html; charset=windows-1252' http-equiv='content-type'/>
+        <meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1'>
+        <link rel='shortcut icon' href='<?=$pserver?>/img/logo/x2/favicon.ico'/>
+        <title><?=$title?>I&ntilde;igo Valentin - Administration Panel</title>
+        <style>
+<?php
+            include $doc_root . "css/ui.css";
+            include $doc_root . "css/blog.css";
+?>
+        </style>
+        <!-- CSS for mobile version -->
+        <style media='(max-width : 990px)'>
+<?php
+            include $doc_root . "css/m/ui.css";
+            include $doc_root . "css/m/blog.css";
+?>
+        </style>
+        <!-- Script files -->
+        <script type='text/javascript'>
+<?php
+            include $doc_root . "script/ui.js";
+            include $doc_root . "script/blog.js";
+?>
+        </script>
+         <script src='/script/ckeditor/ckeditor.js'></script>
+    </head>
+    <body>
+<?php
+        include $doc_root . "header.php";
+?>
+        <div id='content'>
+            <div class='section' id='post_edit'>
+                <h3 class='section_title'><?=$title?></h3>
+                <div class='entry'>
+                    <div id='lang_tabs'>
+                        <table>
+                            <tr>
+<?php
+                                mysqli_data_seek($q_lang, 0);
+                                $active = 'lang_tabs_active';
+                                while ($r_lang = mysqli_fetch_array($q_lang)){
+?>
+                                    <td class='pointer <?=$active?>' id='lang_tab_<?=$r_lang["code"]?>' onclick='showLanguage("<?=$r_lang["code"]?>");'>
+                                        <?=$r_lang["name"]?>
+                                    </td>
+<?php
+                                    $active = '';
+                                }
+?>
+                            </tr>
+                        </table>
+                    </div> <!-- #lang_tabs -->
+                    <span class='bold'>URL:</span>
+                    <?=$pserver?>/blog/
+                    <span class='editable'>
+                        <span class='field_result'><?=$r_post["permalink"]?></span>
+                        <img class='button_edit' src='<?=$server?>/img/icon/edit.png' onclick='enableEdit(this);'/>
+                        <input class='field_editable' type='text' value='<?=$r_post["permalink"]?>'/>
+                        <img class='button_save' src='<?=$server?>/img/icon/save.png' onclick='saveEdit(this, "post", "permalink", <?=$id?>);'/>
+                        <img class='button_cancel' src='<?=$server?>/img/icon/cancel.png' onclick='cancelEdit(this);'/>
+                    </span>
+                    <hr class='separator'/>
+<?php
+                    mysqli_data_seek($q_lang, 0);
+                    $active = "content_lang_active";
+                    while ($r_lang = mysqli_fetch_array($q_lang)){
+?>
+                        <div class='content_lang_<?=$r_lang["code"]?> content_lang <?=$active?>'>
+                            <div class='editable'>
+                                <span class='bold'>Title (<?=$r_lang["name"]?>):</span>
+                                <span class='field_result'><?=text($con, $r_post["title"], $r_lang["code"])?></span>
+                                <img class='button_edit' src='<?=$server?>/img/icon/edit.png' onclick='enableEdit(this);'/>
+                                <input class='field_editable' type='text' value='<?=text($con, $r_post["title"], $r_lang["code"])?>'/>
+                                <img class='button_save' src='<?=$server?>/img/icon/save.png' onclick='saveEdit(this, "text", "<?=$r_lang["code"]?>", "<?=$r_post["title"]?>");'/>
+                                <img class='button_cancel' src='<?=$server?>/img/icon/cancel.png' onclick='cancelEdit(this);'/>
+                            </div>
+                            <hr class='separator'/>
+                            <div class='editable'>
+                                <span class='bold'>Text (<?=$r_lang["name"]?>):</span>
+                                <img class='button_edit' src='<?=$server?>/img/icon/edit.png' onclick='enableEdit(this);'/>
+                                <img class='button_save' src='<?=$server?>/img/icon/save.png' onclick='saveEdit(this, "text", "<?=$r_lang["code"]?>", "<?=$r_post["text"]?>");'/>
+                                <img class='button_cancel' src='<?=$server?>/img/icon/cancel.png' onclick='cancelEdit(this);'/>
+                                <br/>
+                                <p class='field_result'><?=text($con, $r_post["text"], $r_lang["code"])?></p>
+                                <textarea id='text_<?=$r_lang["code"]?>' class='field_editable'><?=text($con, $r_post["text"], $r_lang["code"])?></textarea>
+                                <script>CKEDITOR.replace('text_<?=$r_lang["code"]?>');</script>
+                            </div>
+                        </div> <!-- .content_lang_<?=$r_lang["code"]?> -->
+<?php
+                        $active = '';
+                    }
+?>
+                    <hr class='separator'/>
+                    <h4>Images</h4>
+                    <div id='image_reel' class='horizontal_scroll'>
+<?php
+                        $q_image = mysqli_query($con, "SELECT id, image FROM post_image WHERE post = $id;");
+                        while ($r_image = mysqli_fetch_array($q_image)){
+?>
+                            <span id='post_image_<?=$r_image["id"]?>'>
+                                <img alt='<?=$title?>' title='<?=$title?>' src='<?=$pserver?>/imgblog/x3/<?=$r_image["image"]?>' />
+                                <img class='pointer' onClick='deleteImage(<?=$r_image["id"]?>)' alt='Delete image' title='Delete image' src='<?=$server?>/img/icon/cancel.png' />
+                            </span>
+<?php
+                        }
+?>
+                        <span id='post_image_add'>
+                            <img onClick='addImage(<?=$id?>)' alt='Add image' title='Add image' src='<?=$server?>/img/icon/addimage.png' />
+                        </span> <!-- #image_reel -->
+                    </div>
+                    <hr class='separator'/>
+                    <h4>Tags</h4>
+                    TODO
+                    <hr class='separator'/>
+                    <h4>Settings</h4>
+<?php
+                    if ($r_post["visible"] == 1){
+                        $v = "Yes";
+                        $selected1 = "selected";
+                        $selected0 = "";
+                    }
+                    else{
+                        $v = "No";
+                        $selected1 = "";
+                        $selected0 = "selected";
+                    }
+?>
+                    <div class='editable'>
+                        <span class='bold'>Visible:</span>
+                        <span class='field_result'><?=$v?></span>
+                        <img class='button_edit' src='<?=$server?>/img/icon/edit.png' onclick='enableEdit(this);'/>
+                        <select class='field_editable'>
+                            <option value='1' <?=$selected1?>>Yes</option>
+                            <option value='0' <?=$selected0?>>No</option>
+                        </select>
+                        <img class='button_save' src='<?=$server?>/img/icon/save.png' onclick='saveEdit(this, "post", "visible", "<?=$id?>");'/>
+                        <img class='button_cancel' src='<?=$server?>/img/icon/cancel.png' onclick='cancelEdit(this);'/>
+                    </div>
+<?php
+                    if ($r_post["comments"] == 1){
+                        $v = "Yes";
+                        $selected1 = "selected";
+                        $selected0 = "";
+                    }
+                    else{
+                        $v = "No";
+                        $selected1 = "";
+                        $selected0 = "selected";
+                    }
+?>
+                    <div class='editable'>
+                        <span class='bold'>Comments enabled:</span>
+                        <span class='field_result'><?=$v?></span>
+                        <img class='button_edit' src='<?=$server?>/img/icon/edit.png' onclick='enableEdit(this);'/>
+                        <select class='field_editable'>
+                            <option value='1' <?=$selected1?>>Yes</option>
+                            <option value='0' <?=$selected0?>>No</option>
+                        </select>
+                        <img class='button_save' src='<?=$server?>/img/icon/save.png' onclick='saveEdit(this, "post", "comments", "<?=$id?>");'/>
+                        <img class='button_cancel' src='<?=$server?>/img/icon/cancel.png' onclick='cancelEdit(this);'/>
+                    </div>
+                    <input type='button' onclick='deletePost(<?=$id?>, "<?=$title?>");' value='Delete post'/>
+                    <h4>Comments</h4>
+<?php
+                        $q_comment = mysqli_query($con, "SELECT post_comment.id AS id, text, dtime, user, username, lang, (SELECT name FROM lang WHERE code = post_comment.lang) AS lang_name, approved, ip, browser, os, uagent FROM post_comment, stat_visit WHERE stat_visit.id = visit AND post = $id;");
+                        if (mysqli_num_rows($q_comment) == 0){
+?>
+                            <span>No comments yet</span>
+<?php
+                        }
+                        else{
+?>
+                            <table id='comments'>
+<?php
+                            while ($r_comment = mysqli_fetch_array($q_comment)){
+?>
+                                <tr>
+                                    <td class='text'>
+                                        <p><?=$r_comment["text"]?></p>
+                                    </td>
+                                    <td class='details'>
+                                        On <?=format_date($r_comment["dtime"], $lang, true)?>
+                                        <br/>
+<?php
+                                        if (strlen($r_comment["user"]) > 0){
+                                            $q_user = mysqli_query($con, "SELECT concat(fname, concat(' ', lname)) AS name, username FROM user WHERE id = $r_comment[user];");
+                                            $r_user = mysqli_fetch_array($q_user);
+?>
+                                            By <?=$r_user["name"]?> (<span class='bold'><?=$r_user["username"]?></span>)
+<?php
+                                        }
+                                        else{
+?>
+                                            By <?=$r_comment["username"]?>
+<?php
+                                        }
+                                        if (strlen($r_comment["lang_name"]) > 0){
+                                            $strlang = $r_comment["lang_name"];
+                                        }
+                                        else{
+                                            $strlang = $r_comment["lang"];
+                                        }
+                                        if (file_exists($doc_root . "img/lang/" . $r_comment["lang"] . ".gif")){
+                                            $imgpath = $doc_root . "img/lang/" . $r_comment["lang"] . ".gif";
+                                            $strlang = $strlang . " <img class='lang' title='$r_comment[lang]' alt='$r_comment[lang]' src='$server/$imgpath' />";
+                                        }
+?>
+                                        <br/>
+                                        Language: <?=$strlang?>
+<?php
+                                        if ($r_post["visible"] == 1){
+                                            $v = "Yes";
+                                            $selected1 = "selected";
+                                            $selected0 = "";
+                                        }
+                                        else{
+                                            $v = "No";
+                                            $selected1 = "";
+                                            $selected0 = "selected";
+                                        }
+?>
+                                        <div class='editable'>
+                                            Approved:
+                                            <span class='field_result'><?=$v?></span>
+                                            <img class='button_edit' src='<?=$server?>/img/icon/edit.png' onclick='enableEdit(this);'/>
+                                            <select class='field_editable'>
+                                                <option value='1' <?=$selected1?>>Yes</option>
+                                                <option value='0' <?=$selected0?>>No</option>
+                                            </select>
+                                            <img class='button_save' src='<?=$server?>/img/icon/save.png' onclick='saveEdit(this, "post_comment", "approved", "<?=$r_comment["id"]?>");'/>
+                                            <img class='button_cancel' src='<?=$server?>/img/icon/cancel.png' onclick='cancelEdit(this);'/>
+                                        </div>
+                                    </td>
+                                    <td class='actions'>
+                                        <input type='button' onclick='deletePostComment(<?=$id?>);' value='Delete'/>
+                                    </td>
+                                </tr>
+<?php
+                            }
+?>
+                            </table>
+<?php
+                        }
+?>
+                </div> <!-- .entry -->
+            </div> <!-- .section -->
+        </div> <!-- #content -->
+    </body>
+</html>
+<?php
+            }
+        }
+    }
+?>

+ 149 - 0
www-admin/blog/index.php

@@ -0,0 +1,149 @@
+<?php
+    session_start();
+    $http_host = $_SERVER["HTTP_HOST"];
+    $doc_root = $_SERVER["DOCUMENT_ROOT"] . "/";
+    include $doc_root . "functions.php";
+    $proto = get_protocol();
+    $con = start_db();
+    $server = $proto . $http_host;
+    $pserver = substr($server, 0, strpos($http_host, ':')); // public server
+    $lang = "en";
+    if (!check_session($con)){
+        header("Location: /authentication.php");
+        exit(-1);
+    }
+    else{
+?>
+<html>
+    <head>
+        <meta content='text/html; charset=windows-1252' http-equiv='content-type'/>
+        <meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1'>
+        <link rel='shortcut icon' href='<?=$pserver?>/img/logo/x2/favicon.ico'/>
+        <title>Blog - I&ntilde;igo Valentin - Administration Panel</title>
+        <style>
+<?php
+            include $doc_root . "css/ui.css";
+            include $doc_root . "css/blog.css";
+?>
+        </style>
+        <!-- CSS for mobile version -->
+        <style media='(max-width : 990px)'>
+<?php
+            include $doc_root . "css/m/ui.css";
+            include $doc_root . "css/m/blog.css";
+?>
+        </style>
+        <!-- Script files -->
+        <script type='text/javascript'>
+<?php
+            include $doc_root . "script/ui.js";
+            include $doc_root . "script/blog.js";
+?>
+        </script>
+    </head>
+    <body>
+<?php
+        include $doc_root . "header.php";
+?>
+        <div id='content'>
+            <div class='section'>
+                <h3 class='section_title'>Post list</h3>
+                <div class='entry'>
+                    <table id='post_list'>
+                        <tr>
+                            <th>Post</th>
+                            <th>Detalles</th>
+                            <th>Acciones</th>
+                        </tr>
+<?php
+                        $q_post = mysqli_query($con, "SELECT post.id AS id, permalink, title, text, dtime, comments, (SELECT count(id) FROM post_comment WHERE post = post.id) AS comment_count, concat(fname, concat(' ', lname)) AS user, visible, comments, (SELECT image FROM post_image WHERE post = post.id ORDER BY idx LIMIT 1) AS image, (SELECT count(image) FROM post_image WHERE post = post.id) AS image_count FROM post, user WHERE user = user.id");
+                        while ($r_post = mysqli_fetch_array($q_post)){
+                            $title = text($con, $r_post["title"], $lang);
+?>
+                            <tr>
+                                <td class='td_post'>
+                                    <h4>
+                                        <a title='View <?=$title?> on a new tab' target='_blank' href='<?=$pserver?>/blog/<?=$r_post["permalink"]?>'>
+                                            <?=$title?>
+                                        </a>
+                                    </h4>
+                                    <p><?=cut_text(text($con, $r_post["text"], $lang), 200)?></p>
+<?php
+                                    if ($r_post["image_count"] > 0){
+?>
+                                        <img class='post_image' alt='<?=$title?>' title='<?=$title?>' src='<?=$pserver?>/img/blog/x2/<?=$r_post["image"]?>' />
+<?php
+                                        if ($r_post["image_count"] == 2){
+?>
+                                            + other image.
+<?php
+                                        }
+                                        elseif ($r_post["image_count"] > 2){
+                                            $pl = $r_post["image_count"] - 1;
+?>
+                                            + <?=$pl?> other images.
+<?php
+                                        }
+                                    }
+?>
+                                </td>
+                                <td class='td_details'>
+                                    By <?=$r_post["user"]?>.<br/>
+                                    Posted <?=format_date($r_post["dtime"], $lang, true)?>.<br/>
+                                    Visible:
+<?php
+                                    if ($r_post["visible"] == 1){
+?>
+                                        Yes
+<?php
+                                    }
+                                    else{
+?>
+                                        No
+<?php
+                                    }
+?>
+                                    <br/>Comments: <?=$r_post["comment_count"]?><br/>
+                                    Comments allowed:
+<?php
+                                    if ($r_post["comments"] == 1){
+?>
+                                        Yes
+<?php
+                                    }
+                                    else{
+?>
+                                        No
+<?php
+                                    }
+?>
+                                </td>
+                                <td class='td_actions'>
+                                    <input type='button' onclick='managePost(<?=$r_post["id"]?>);' value='Manage / Translate'/>
+                                    <input type='button' onclick='deletePost(<?=$r_post["id"]?>, "<?=$title?>");' value='Delete'/>
+                                    <?php
+                                    if ($r_post["visible"] == 1){
+?>
+                                        <input type='button' onclick='visiblePost(<?=$r_post["id"]?>, "<?=$title?>", 0);' value='Unpublish'/>
+<?php
+                                    }
+                                    else{
+?>
+                                        <input type='button' onclick='visiblePost(<?=$r_post["id"]?>, "<?=$title?>", 1);' value='Publish'/>
+<?php
+                                    }
+?>
+                                </td>
+                            </tr>
+<?php
+                        }
+?>
+                    </table>
+                </div> <!-- .entry -->
+            </div> <!-- .section -->
+        </div> <!-- #content -->
+    </body>
+</html>
+<?php
+    }
+?>

+ 20 - 0
www-admin/blog/new.php

@@ -0,0 +1,20 @@
+<?php
+    session_start();
+    $http_host = $_SERVER["HTTP_HOST"];
+    $doc_root = $_SERVER["DOCUMENT_ROOT"] . "/";
+    include $doc_root . "functions.php";
+    $proto = get_protocol();
+    $con = start_db();
+    $server = $proto . $http_host;
+    $pserver = substr($server, 0, strpos($http_host, ':')); // public server
+    if (!check_session($con)){
+        http_response_code(401);
+        header("Location: /authentication.php");
+    }
+    else{
+        $user = mysqli_real_escape_string($_SESSION['id']);
+        mysqli_query($con, "INSERT INTO post (id, permalink, title, text, user, dtime, visible) VALUES ((SELECT max(id) +1 FROM post p), concat((SELECT max(id) +1 FROM post p), ''), '', '', $user, now(), 0);");
+        $id = mysqli_fetch_array(mysqli_query($con, "SELECT max(id) AS id FROM project;"))["id"];
+        header("Location: /blog/edit.php?p=" + $id);
+    }
+?>

+ 28 - 0
www-admin/blog/uploadimage.php

@@ -0,0 +1,28 @@
+<?php
+    session_start();
+    $http_host = $_SERVER["HTTP_HOST"];
+    $doc_root = $_SERVER["DOCUMENT_ROOT"] . "/";
+    include $doc_root . "functions.php";
+    $proto = get_protocol();
+    $lang = "en";
+    $con = start_db();
+    $server = $proto . $http_host;
+    $pserver = $proto . substr($http_host, 0, strpos($http_host, ':')); // public server
+    if (!check_session($con)){
+        http_response_code(401);
+        header("Location: /authentication.php");
+    }
+    else{
+        foreach ($_POST as $key => $value){
+            error_log("{$key} = {$value}");
+        }
+        $name = mysqli_real_escape_string($con, $_GET["name"]);
+        $base64string = $_POST["image"];
+        error_log("FILENAME: $name");
+        error_log("PATH: $doc_root../www/img/blog/DEBUG_$name");
+        error_log("B64: $base64string");
+        file_put_contents("$doc_root../www/img/blog/DEBUG_$name", base64_decode($base64string));
+
+        echo("OK");
+    }
+?>

+ 29 - 0
www-admin/blog/visible.php

@@ -0,0 +1,29 @@
+<?php
+    session_start();
+    $http_host = $_SERVER["HTTP_HOST"];
+    $doc_root = $_SERVER["DOCUMENT_ROOT"] . "/";
+    include $doc_root . "functions.php";
+    $proto = get_protocol();
+    $con = start_db();
+    $server = $proto . $http_host;
+    $pserver = substr($server, 0, strpos($http_host, ':')); // public server
+    if (!check_session($con)){
+        http_response_code(401); // Not logged in: Forbidden.
+    }
+    else{
+        $id = intval(mysqli_real_escape_string($con, $_GET["p"]));
+        $visible = intval(mysqli_real_escape_string($con, $_GET["v"]));
+        if (strlen($id) < 1 || ($visible != 1 && $visible != 0)){
+            http_response_code(400); // No or bad ID, visible not 1 or 0: Bad request.
+        }
+        else{
+            if (mysqli_num_rows(mysqli_query($con, "SELECT id FROM post WHERE id = $id;")) == 0){
+                http_response_code(404); // Unexistent id: Not found.
+            }
+            else{
+                mysqli_query($con, "UPDATE post SET visible = $visible WHERE id = $id;");
+                http_response_code(200); //OK.
+            }
+        }
+    }
+?>

+ 116 - 0
www-admin/css/blog.css

@@ -0,0 +1,116 @@
+/* /blog/index.php */
+
+table#post_list{
+    width: 100%;
+    border-spacing: 0;
+    border-collapse: collapse;
+}
+
+table#post_list th{
+    font-weight: bold;
+    background: linear-gradient(to bottom, #4e9987 0%,#69d8b9 89%,#60caac 97%,#ade8f9 100%);
+    border: 0.1em solid #335533;
+}
+
+table#post_list td{
+    border: 0.1em solid #335533;
+    padding: 0.2em;
+}
+
+table#post_list td.td_post{
+    width: 30%;
+}
+
+table#post_list td.td_post h4{
+    margin: 0.2em;
+}
+
+table#post_list td.td_post h4 img{
+    max-height: 2em;
+    max-width: 2em;
+    vertical-align: middle;
+}
+
+table#post_list td.td_post img.post_image{
+    border: 0.2em solid #335533;
+    border-radius: 0.4em;
+    max-height: 3em;
+    max-width: 3em;
+}
+
+table#post_list td.td_post{
+    width: 50%;
+    font-size: 80%;
+}
+
+table#post_list td.td_actions{
+    width: 20%;
+    text-align: center;
+}
+
+table#post_list td.td_actions input{
+    padding: 0.1em;
+    display: block;
+    margin: auto;
+    width: 90%;
+}
+
+
+/* /blog/edit.php */
+
+div#post_edit hr.separator{
+    margin: 1.5em 20% 1.5em 20%;
+    opacity: 0.5;
+}
+
+div#post_edit div#image_reel{
+    overflow-x: scroll;
+    border: 0.1em solid #003400;
+    border-radius: 0.3em;
+    height: 7em;
+    background-color: #334433;
+    padding: 0.5em;
+}
+
+div#post_edit div#image_reel img{
+    vertical-align: middle;
+    max-height: 5em;
+    max-width: 7em;
+    border: 0.2em solid #003400;
+    border-radius: 0.1em;
+    margin: 0.5em;
+    background-color: #ffffff;
+}
+
+div#post_edit table#comments{
+    border-collapse: collapse;
+    width: 100%;
+}
+
+div#post_edit table#comments td{
+    border-right: 0.1em solid #334433;
+    border-left: 0.1em solid #334433;
+    border-bottom: 0.2em solid #334433;
+    border-top: 0.2em solid #334433;
+}
+
+div#post_edit table#comments td.text{
+    width: 35%;
+}
+
+div#post_edit table#comments td.details{
+    width: 50%;
+    line-height: 2
+}
+
+div#post_edit table#comments td.actions{
+    width: 15%;
+    text-align: center;
+}
+
+div#post_edit table#comments td.actions input{
+    width: 90%;
+    margin: auto;
+    padding: 0.2em;
+    display: block;
+}

+ 29 - 0
www-admin/css/home.css

@@ -0,0 +1,29 @@
+body{
+    text-align: center;
+}
+
+div#section_table{
+    display: table;
+    width: 100%;
+}
+
+div.section_row{
+    display: table-row;
+}
+
+div.section_cell{
+    display: table-cell;
+    width: 50%;
+}
+
+div.section{
+    display: block;
+    vertical-align: top;
+    text-align: left;
+    margin:  2em;
+}
+
+div.section#authentication{
+    display: inline-block;
+    margin: 8em auto auto auto;
+}

+ 0 - 0
www-admin/css/m/ui.css


+ 169 - 0
www-admin/css/projects.css

@@ -0,0 +1,169 @@
+/* /projects/index.php */
+
+table#project_list{
+    width: 100%;
+    border-spacing: 0;
+    border-collapse: collapse;
+}
+
+table#project_list th{
+    font-weight: bold;
+    background: linear-gradient(to bottom, #4e9987 0%,#69d8b9 89%,#60caac 97%,#ade8f9 100%);
+    border: 0.1em solid #335533;
+}
+
+table#project_list td{
+    border: 0.1em solid #335533;
+    padding: 0.2em;
+}
+
+table#project_list td.td_project{
+    width: 30%;
+}
+
+table#project_list td.td_project h4{
+    margin: 0.2em;
+}
+
+table#project_list td.td_project h4 img{
+    max-height: 2em;
+    max-width: 2em;
+    vertical-align: middle;
+}
+
+table#project_list td.td_project img.project_image{
+    border: 0.2em solid #335533;
+    border-radius: 0.4em;
+    max-height: 3em;
+    max-width: 3em;
+}
+
+table#project_list td.td_details{
+    width: 50%;
+    font-size: 80%;
+}
+
+table#project_list td.td_actions{
+    width: 20%;
+    text-align: center;
+}
+
+table#project_list td.td_actions input{
+    padding: 0.1em;
+    display: block;
+    margin: auto;
+    width: 90%;
+}
+
+
+/* /projects/edit.php */
+
+div#project_edit hr.separator{
+    margin: 1.5em 20% 1.5em 20%;
+    opacity: 0.5;
+}
+
+div#project_edit img#icon{
+    vertical-align: middle;
+    width: 4em;
+    height: 4em;
+    border: 0.3em solid #003400;
+    border-radius: 1em;
+}
+
+div#project_edit input#icon_file{
+    width: 0;
+    height: 0;
+    opacity: 0;
+}
+
+div#project_edit div#image_reel{
+    overflow-x: scroll;
+    border: 0.1em solid #003400;
+    border-radius: 0.3em;
+    height: 7em;
+    background-color: #334433;
+    padding: 0.5em;
+}
+
+div#project_edit div#image_reel img{
+    vertical-align: middle;
+    max-height: 5em;
+    max-width: 7em;
+    border: 0.2em solid #003400;
+    border-radius: 0.1em;
+    margin: 0.5em;
+    background-color: #ffffff;
+}
+
+div#project_edit input#new_version{
+    width: 16em;
+    margin: 0.5em auto 2em auto;
+    display: block;
+}
+
+div#project_edit table#versions{
+    border-collapse: collapse;
+    width: 100%;
+}
+
+div#project_edit table#versions td{
+    border-right: 0.1em solid #334433;
+    border-left: 0.1em solid #334433;
+    border-bottom: 0.2em solid #334433;
+    border-top: 0.2em solid #334433;
+}
+
+div#project_edit table#versions td.version_name{
+    width: 15%;
+}
+
+div#project_edit table#versions td.version_details{
+    width: 70%;
+    line-height: 2
+}
+
+div#project_edit table#versions td.version_actions{
+    width: 15%;
+    text-align: center;
+}
+
+div#project_edit table#versions td.version_actions input{
+    width: 90%;
+    margin: auto;
+    padding: 0.2em;
+    display: block;
+}
+
+div#project_edit table#comments{
+    border-collapse: collapse;
+    width: 100%;
+}
+
+div#project_edit table#comments td{
+    border-right: 0.1em solid #334433;
+    border-left: 0.1em solid #334433;
+    border-bottom: 0.2em solid #334433;
+    border-top: 0.2em solid #334433;
+}
+
+div#project_edit table#comments td.text{
+    width: 35%;
+}
+
+div#project_edit table#comments td.details{
+    width: 50%;
+    line-height: 2
+}
+
+div#project_edit table#comments td.actions{
+    width: 15%;
+    text-align: center;
+}
+
+div#project_edit table#comments td.actions input{
+    width: 90%;
+    margin: auto;
+    padding: 0.2em;
+    display: block;
+}

+ 5 - 0
www-admin/css/stats.css

@@ -0,0 +1,5 @@
+div.pie_chart{
+    display: inline-block;
+    vertical-align: top;
+    margin: 0.5em;
+}

+ 481 - 0
www-admin/css/ui.css

@@ -0,0 +1,481 @@
+/**********************************
+ * Fonts used across the page     *
+ **********************************/
+
+/* Font */
+@font-face {
+    font-family: 'Nunito'; 
+    src: url('/fonts/Nunito-Regular.ttf'); 
+}
+
+/**********************************
+ * Styles to be applied across    *
+ * all the site for all the       *
+ * elements of it's type          *
+ **********************************/
+
+/* Styles for the body of the page */
+body{
+    background-color: #111111;
+}
+
+/* Style for all links */
+a{
+    color: #555555;
+    text-shadow: 0 0 0.5em #7777ff;
+    text-decoration: none;
+}
+
+/* Elements fith bold font */
+.bold{
+    font-weight: bold;
+}
+
+.pointer {
+    cursor: pointer;
+}
+
+/* Hidden elements */
+.hidden{
+    display: none;
+}
+
+img.slider{
+    width: 1em;
+    height: 1em;
+    vertial-align: middle;
+    transition: all .3s ease-in-out;
+}
+
+div#cover{
+    display: none;
+    background-color: #000000bb;
+    opacity: 0;
+    transition: opacity .3s ease-in-out;
+    position: fixed;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+}
+
+div#dialog{
+    display: none;
+    position: fixed;
+    top: 0;
+    left: 0;
+    right: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    opacity: 0;
+    transition: opacity .3s ease-in-out;
+    text-align: center;
+    z-index: 1;
+}
+
+div#dialog div#dialog_background{
+    position: fixed;
+    top: 0;
+    left: 0;
+    right: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    background-color: #00000088;
+    z-index: -1;
+}
+
+div#dialog div.section{
+    width: 50%;
+    height: 50%;
+    margin: 5em auto auto auto;
+    z-index: 1;
+}
+
+
+/**********************************
+ * Header style                  *
+ **********************************/
+
+div#header{
+    background-color: #000000;
+    margin: 0;
+    width: 100%;
+    height: 2em;
+    padding: 0;
+}
+
+div#header img{
+    height: 1.6em;
+    vertical-align: center;
+}
+
+div#header table{
+    width: 100%;
+    height: 100%;
+    margin-left: auto;
+    margin-right: auto;
+    border-spacing: 0;
+    border-collapse: collapse;
+    margin: 0;
+    table-layout: fixed;
+}
+
+div#header td{
+    color: #dddddd;
+    font-weight: bold;
+    text-align: center;
+}
+
+div#header td a{
+    color: #dddddd;
+    font-weight: bold;
+    text-align: center;
+    text-shadow: none;
+    text-decoration: none;
+    transition: all .3s ease-in-out;
+}
+
+
+/******************************************
+ * Secondary header.                     *
+ ******************************************/
+div.secondary_header{
+    background-color: #333333;
+    margin: 0;
+    display: none;
+    width: 100%;
+    height: 2em;
+}
+
+div.secondary_header table{
+    width: 90%;
+    margin-left: auto;
+    margin-right: auto;
+    table-layout: fixed;
+}
+
+div.secondary_header td{
+    text-align: center;
+}
+
+div.secondary_header td a{
+    color: #dddddd;
+    font-weight: bold;
+    text-align: center;
+    text-shadow: none;
+    text-decoration: none;
+    transition: all .3s ease-in-out;
+}
+
+div.secondary_header td a:hover{
+    color: #dddddd;
+    text-shadow: none;
+    text-decoration: none;
+}
+
+
+/**********************************
+ * Styles for the elements that   *
+ * compose the basic strucure of  *
+ * the site.                      *
+ **********************************/
+
+/* The content is everything between the header and the footer */
+div#content{
+    position: relative;
+    width: 100%;
+    height: 100%;
+    margin-bottom: 1.875em;
+}
+
+div.section{
+    margin: 0.5em;
+    padding: 0;
+    background-color: #ffffff;
+    border: 0.063em solid #005500;
+    border-radius: 1em 1em;
+    box-shadow: 0 0 0.75em #003300;
+    overflow: hidden;
+}
+
+div.section h3.section_title{
+    font-weight: bold;
+    color: #ccddff;
+    text_align: left;
+    padding: 0.2em 0.4em 0.2em 0.4em;
+    margin: 0;
+    text-shadow: 0 0 0.4em #000022;
+    font-size: 150%;
+    background: linear-gradient(to bottom, #1e9957 0%,#29d889 89%,#20ca7c 97%,#7de8b9 100%);
+}
+
+/* .entry is a style only class to show content. Must be allways children os a .lighted element */
+div.section div.entry{
+    border: 0.1em solid #aaffaa00;
+    border-radius: 0.3em;
+    padding: 0.5em;
+    margin: 0.5em;
+    transition: all .4s ease-in-out;
+}
+
+div.section div.entry_list:hover{
+    background-color: #efffef;
+    border: 0.1em solid #aaffaaff;
+}
+
+
+
+/* Style for the mathces in search results */
+span.result_counter{
+    margin-left: 1.2em;
+    margin-bottom: 0.5em;
+    font-weight: bold;
+    font-style: italic;
+}
+
+/* Shown when no records on database */
+div.unavailable{
+    margin-left: 4em;
+    font-style: italic;
+}
+
+
+/**********************************
+ * Styles to be applied to some   *
+ * horizontally scrollable        *
+ * elements                       *
+ **********************************/
+
+/* Horizontall scrollbar that apears when a project has lots of renders */
+.slider::-webkit-scrollbar{
+    width: 0.5em;
+    height: 0.25em;
+    background-color: #444444;
+} 
+.slider::-webkit-scrollbar-thumb{
+    background-color: #cccccc;
+    border-radius: 0.714em;
+}
+.slider::-webkit-scrollbar-thumb:hover{
+    background-color: #cccccc;
+    border: 0.143em solid #000000;
+}
+.slider::-webkit-scrollbar-thumb:active{
+    background-color: #cccccc;
+    border: 0.143em solid #000000;
+}
+
+/* Icons for sharing page in a social network */
+img.social_icon{
+    width: 4.286em;
+    height: 4.286em;
+}
+
+/* Elements that should have the cursor as pointer */
+.pointer{
+    cursor: pointer;
+}
+
+
+
+/**********************************
+ * Styles for diverse input       *
+ * elements. Type-assigned        *
+ **********************************/
+
+/* Font for all the input elements */
+input{
+    font-family: 'Nunito';
+}
+
+/* Style forr buttons */
+input[type=button], input[type=submit]{
+    font-weight: bold;
+    color: #cccccc;
+    background-color: #555555;
+    border: 0.143em solid #bbbbbb;
+    border-radius: 0.429em;
+    padding: 0.5em;
+    text-shadow: 0 0 0.75em #000000;
+    cursor: pointer;
+}
+
+/* Style for textboxes */
+input[type=text], input[type=password]{
+    font-weight: bold;
+    color: #224422;
+    background-color: #bbccbb;
+    border: 0.286em solid #ddeedd; 
+    border-color: #dddddd;
+    border-radius: 0.429em;
+    padding: 0.25em;
+    text-shadow: 0 0 0.5em #ffffff;
+}
+
+input[type=checkbox]{
+    width: 1em;
+    height: 1em;
+    border-radius: 0.2em;
+    border: 0.063em solid #005500;
+}
+
+/* Style for textareas */
+textarea{
+    width: 80%;
+    font-weight: bold;
+    color: #444444;
+    background-color: #bbbbbb;
+    border: 0.286em solid #dddddd; 
+    border-color: #dddddd;
+    border-radius: 0.286em;
+    padding: 0.25em;
+    text-shadow: 0 0 0.5em #ffffff;
+    resize: none;
+}
+
+/* Style for selects */
+select{
+    font-weight: bold;
+    color: #444444;
+    background-color: #bbbbbb;
+    border: 0.286em solid #dddddd; 
+    border-radius: 0.286em;
+    padding: 0.25em;
+    text-shadow: 0 0 0.5em #ffffff;
+}
+
+/* Style for placeholders in input fields */
+::-webkit-input-placeholder { /* WebKit browsers */
+    color: #777777;
+}
+:-moz-placeholder { /* Mozilla Firefox 4 to 18 */
+    color: #777777;
+}
+::-moz-placeholder { /* Mozilla Firefox 19+ */
+    color: #777777;
+}
+:-ms-input-placeholder { /* Internet Explorer 10+ */
+    color: #777777;
+}
+
+/* Style-only class user to indicate that a field is incorrect. Ususally this class is added to an element with js */
+.error{
+    border-color: #ffaaaa;
+}
+
+/* Style for the textbox input when the entered text is not the expected one */
+input[type=text].error, input[type=password].error{
+    border-color: #ffaaaa;
+}
+
+/* Style for the textared input when the entered text is not the expected one */
+textarea.error{
+    border-color: #ffaaaa;
+}
+
+/* Style for buttons that act as links */
+a.a_button{
+    display: inline-block;
+    color: #cccccc;
+    background-color: #555555;
+    border: 0.143em solid #bbbbbb;
+    border-radius: 0.429em;
+    padding: 0.5em;
+    text-shadow: 0 0 0.75em #000000;
+    text-decoration: none;
+    font: -webkit-small-control;
+    font-family: 'Nunito';
+    font-weight: bold;
+}
+
+
+
+/**********************************
+ * Styles for editable items  .   *
+ * horizontally scrollable        *
+ * elements                       *
+ **********************************/
+
+/* Field value, not editing */
+.editable .field_result{
+    display: initial;
+}
+
+/* Button to start editing*/
+.editable .button_edit{
+    display: initial;
+    cursor: pointer;
+    max-width: 1.2em;
+    vertical-align: middle;
+    opacity: 0.5;
+}
+
+/* Field value, editing. */
+.editable .field_editable{
+    display: none;
+}
+
+/* Field value, editing, textarea <-> cke */
+.editable .cke{
+    display: none;
+}
+
+/* Button to save edition. */
+.editable .button_save{
+    display: none;
+    cursor: pointer;
+    max-width: 1.2em;
+    vertical-align: middle;
+}
+
+/* Button to save edition. */
+.editable .button_cancel{
+    display: none;
+    cursor: pointer;
+    max-width: 1.2em;
+    vertical-align: middle;
+}
+
+/******************************************
+* Language selectors                      *
+******************************************/
+
+div#lang_tabs{
+    margin-top: 2em;
+    margin-bottom: 2em;
+    text-align: center;
+}
+
+div#lang_tabs table{
+    border-collapse: collapse;
+    border: 0.2em solid #1e9957;
+    border-radius: 0.2em;
+    margin: 0.5em auto 1em auto;
+}
+
+div#lang_tabs td{
+    background-color: #ffffff;
+    color: #29d889;
+    font-weight: bold;
+    width: 7em;
+    margin: 0;
+    padding: 0.2em;
+    border: 0.05em solid #1e9957;
+}
+
+div#lang_tabs td.lang_tabs_active{
+    background-color: #29d889;
+    color: #ffffff;
+}
+
+div.content_lang{
+    display: none;
+    margin: 0;
+    padding: 0;
+}
+
+div.content_lang_active{
+    display: initial;
+}

BIN
www-admin/fonts/Nunito-Bold.ttf


BIN
www-admin/fonts/Nunito-Light.ttf


BIN
www-admin/fonts/Nunito-Regular.ttf


+ 93 - 0
www-admin/fonts/OFL.txt

@@ -0,0 +1,93 @@
+Copyright (c) 2011, Vernon Adams (vern@newtypography.co.uk),
+with Reserved Font Name Nunito.
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded, 
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.

+ 354 - 0
www-admin/functions.php

@@ -0,0 +1,354 @@
+<?php
+
+    $IMG_SIZE_PREVIEW = 600;
+    $IMG_SIZE_MINIATURE = 200;
+
+
+    /*****************************************************
+     * This function is called from almost everywhere at *
+     * the beggining of the page. It initializes the     *
+     * session variables, connect to the db, enabling    *
+     * the variable $con for futher use everywhere in    *
+     * the php code, and populates the arrays $user      *
+     * and $permission, with info about the user.        *
+     *                                                   *
+     * @return: (db connection): The connection handler. *
+     *****************************************************/
+    function start_db(){
+        //Include the db configuration file. It's somehow like this
+        /*
+        <?php
+            $db_host = "XXXX";
+            $db_name = "XXXX";
+            $db_user = "XXXX";
+            $db_pass = "XXXX";
+        ?>
+        */
+        include ".htpasswd";
+
+        $con = mysqli_connect($db_host, $db_user, $db_pass, $db_name);
+
+        // Check connection
+        if (mysqli_connect_errno()){
+            error_log("Failed to connect to database: " . mysqli_connect_error());
+            return -1;
+        }
+
+        //Set encoding options
+        mysqli_set_charset($con, "utf-8");
+        header("Content-Type: text/html; charset=utf8");
+        mysqli_query($con, "SET NAMES utf8;");
+
+        //Return the db connection
+        return $con;
+    }
+
+
+    /*****************************************************
+     * Finds out the protocol the user is connecting to  *
+     * the site with (http or https)                     *
+     * @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;
+    }
+
+
+    /****************************************************
+    * This function turns a date string into a human    *
+    * readable string, deppending o the specified       *
+    * language.                                         *
+    * @params:                                          *
+    *    strdate: (string): Date string.                *
+    *    lang: (string): Language code (es, en, eu).    *
+    *    http_accept_language: (string) Raw header with *
+    *                          info about client        *
+    *                          language preferences.    *
+    *    time: (boolean): Append the time at the end.   *
+    * @return: (string array) List of the languages     *
+    *          offered, sorted by prefference.          *
+    ****************************************************/
+    function format_date($strdate, $lang, $time = true){
+        $date = strtotime($strdate);
+        $year = date('o', $date);
+        $month = date('n', $date);
+        $month --;
+        $day = date('j', $date);
+        $wday = date('N', $date);
+        $wday --;
+        $hour = date('H', $date);
+        $minute = date('i', $date);
+        switch ($lang){
+            case 'en':
+                $week = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
+                $months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
+                if ($time){
+                    $str = "$week[$wday], $months[$month] $day, $year at $hour:$minute";
+                }
+                else{
+                    $str = "$week[$wday], $months[$month] $day, $year";
+                }
+                break;
+            case 'eu':
+                $week = ['astelehena', 'asteartea', 'asteazkena', 'osteguna', 'ostirala', 'larumbata', 'igandea'];
+                $months = ['urtarrilaren', 'otsailaren', 'martxoaren', 'apirilaren', 'maiatzaren', 'ekainaren', 'uztailaren', 'abuztuaren', 'irailaren', 'urriaren', 'azaroaren', 'abenduaren'];
+                if ($time){
+                    $str = $year . "ko $months[$month] $day" . "an, $week[$wday], $hour:$minute";
+                }
+                else{
+                    $str = $year . "ko $months[$month] $day" . "an, $week[$wday]";
+                }
+                break;
+            default:
+                $week = ['Lunes', 'Martes', 'Mi&eacute;rcoles', 'Jueves', 'Viernes', 'S&aacute;bado', 'Domingo'];
+                $months = ['enero', ' febrero', 'marzo', 'abril', 'mayo', 'junio', 'julio', 'agosto', 'septiembre', 'octubre', 'noviembre', 'diciembre'];
+                if ($time){
+                    $str = "$week[$wday] $day de $months[$month] de $year a las $hour:$minute";
+                }
+                else{
+                    $str = "$week[$wday] $day de $months[$month] de $year";
+                }
+        }
+        return $str;
+    }
+
+
+    /*****************************************************
+     * Generates a URL-valid string from a regular one.  *
+     *                                                   *
+     * @params:                                          *
+     *    text: (string): Original string.               *
+     * @return: (string): URL-valid string.              *
+     *****************************************************/
+    function permalink($text){
+        $unwanted_array = array("Š"=>"S", "š"=>"s", "Ž"=>"Z", "ž"=>"z", "À"=>"A", "Á"=>"A", "Â"=>"A", "Ã"=>"A", "Ä"=>"A", "Å"=>"A", "Æ"=>"A", "Ç"=>"C", "È"=>"E", "É"=>"E",
+                            "Ê"=>"E", "Ë"=>"E", "Ì"=>"I", "Í"=>"I", "Î"=>"I", "Ï"=>"I", "Ñ"=>"N", "Ò"=>"O", "Ó"=>"O", "Ô"=>"O", "Õ"=>"O", "Ö"=>"O", "Ø"=>"O", "Ù"=>"U",
+                            "Ú"=>"U", "Û"=>"U", "Ü"=>"U", "Ý"=>"Y", "Þ"=>"B", "ß"=>"Ss", "à"=>"a", "á"=>"a", "â"=>"a", "ã"=>"a", "ä"=>"a", "å"=>"a", "æ"=>"a", "ç"=>"c",
+                            "è"=>"e", "é"=>"e", "ê"=>"e", "ë"=>"e", "ì"=>"i", "í"=>"i", "î"=>"i", "ï"=>"i", "ð"=>"o", "ñ"=>"n", "ò"=>"o", "ó"=>"o", "ô"=>"o", "õ"=>"o",
+                            "ö"=>"o", "ø"=>"o", "ù"=>"u", "ú"=>"u", "û"=>"u", "ý"=>"y", "þ"=>"b", "ÿ"=>"y" );
+        $str = strtr( $text, $unwanted_array );
+        $str = preg_replace("/[^\da-zA-Z ]/i", "", $str);
+        $str = str_replace(" ", "-", $str);
+        return $str;
+    }
+
+
+    /*****************************************************
+     * Retrieves a text fragment from the database.      *
+     *                                                   *
+     * @params:                                          *
+     *    con (Mysql connection): Connection handler.    *
+     *    id: (string): Text identifier.                 *
+     *    lang: (string): Language identifier.           *
+     * @return: (string): Text.                          *
+     *****************************************************/
+    function text($con, $id, $lang){
+        $q = mysqli_query($con, "SELECT text, file FROM text WHERE id = '$id' AND lang = '$lang';");
+        $r = mysqli_fetch_array($q);
+        if (strlen($r["file"]) > 0){
+            return file_get_contents($_SERVER["DOCUMENT_ROOT"] . "string/" . $r["file"]);
+        }
+        else{
+            return $r["text"];
+        }
+    }
+
+
+    /*****************************************************
+     * This function closes all the opened HTML tags in  *
+     * a given string.                                   *
+     *                                                   *
+     * @params:                                          *
+     *    html: (string): The string with HTML tags.     *
+     * @return: (string): HTML with closed tags.         *
+     *****************************************************/
+    function close_tags($html) {
+        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, ut to the next   *
+     * white character. If indicated, it will append a   *
+     * link to the full text.                            *
+     *                                                   *
+     * @params:                                          *
+     *    text: (string): The text to shorten.           *
+     *    length: (int): The desired length.             *
+     *    linktext: (string): Text fot the link.         *
+     *    link: (string): URI of the full text.          *
+     * @return: (string): Cutted text.                   *
+     *****************************************************/
+     function cut_text($text, $length, $linktext, $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)){
+            $cut = $cut . "... <a href='$link'>$linktext</a>";
+        }
+        return $cut;
+    }
+
+
+    /*****************************************************
+     * 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;
+    }
+
+
+    /*****************************************************
+     * Tries to login a user to the internal page.       *
+     *                                                   *
+     * @params:                                          *
+     *    con (Mysql connection): Connection handler.    *
+     *    user (string): Username, plain text.           *
+     *    pass (string): Password SHA-1 sum.             *
+     * @return: (boolean): True if user pass/match,      *
+     *          false otherwise.                         *
+     *****************************************************/
+    function login($con, $user, $pass){
+        session_start();
+        $q = mysqli_query($con,"SELECT id, salt, username AS username, sha1(salt) AS s FROM user WHERE (lower(username) = lower('$user') OR lower(email) = lower('$user')) AND password = sha1(concat('$pass', sha1(salt)));");
+        if (mysqli_num_rows($q) == 1){
+            $r = mysqli_fetch_array($q);
+            $_SESSION['id'] = $r['id'];
+            $_SESSION['salt'] = $r['s'];
+            $_SESSION['name'] = $r['username'];
+            return true;
+        }
+        else{
+            error_log("Invalid login. username = $user, id = $r[id];");
+            return false;
+        }
+    }
+
+
+    /*****************************************************
+     * Checks if a user is logged in.                    *
+     *                                                   *
+     * @params:                                          *
+     *    con (Mysql connection): Connection handler.    *
+     * @return: (boolean): True if the user is signed    *
+     *          in, false otherwise.                     *
+     *****************************************************/
+    function check_session($con){
+        session_start(['cookie_lifetime' => 1800,]);
+        $qr = mysqli_query($con, "SELECT id FROM user WHERE id = '$_SESSION[id]' AND sha1(salt) = '$_SESSION[salt]';");
+        //error_log("SELECT id FROM user WHERE id = '$_SESSION[id]' AND md5(salt) = '$_SESSION[salt]';");
+        if (mysqli_num_rows($qr) == 1)
+            return true;
+        else{
+            return false;
+            error_log("Invalid session. username = $_SESSION[name], id = $_SESSION[id]");
+        }
+    }
+
+
+    /*****************************************************
+     * Performs an update in the database.               *
+     *                                                   *
+     * @params:                                          *
+     *    con (Mysql connection): Connection handler.    *
+     *    s_table (string): Table name.                  *
+     *    s_column (string): Column name.                *
+     *    type (string): Column type (VARCHAR, NUMBER,   *
+     *         DATE or TIME. If the value to insert in   *
+     *         null, type must be NULL).                 *
+     *    s_value (string): New value. Ignoered if type  *
+     *            is NULL.                               *
+     *    id (string): Row id. If the table doesn't have *
+     *       a column named 'id', this can't be used.    *
+     *****************************************************/
+    function db_update($con, $s_table, $s_column, $type, $s_value, $id){
+        $table = mysqli_real_escape_string($con, $s_table);
+        $column = mysqli_real_escape_string($con, $s_column);
+        switch (strtoupper($type)){
+            case "NULL":
+                $value = "null";
+                break;
+            case "VARCHAR":
+                $value = "'" . mysqli_real_escape_string($con, $s_value) . "'";
+                break;
+            case "NUMBER":
+                $value = intval(mysqli_real_escape_string($con, $s_value));
+                break;
+            case "DATE":
+                if (!preg_match("/^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1]) \d{2}:\d{2}:\d{2}$/",$s_value)){
+                    return -2;
+                }
+                $value = "str_to_date('$s_value', '%Y-%m-%d')";
+                break;
+            case "TIME":
+                if (!preg_match("^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1]) (0[0-9]|1[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])$",$s_value)){
+                    return -2;
+                }
+                $value = "str_to_date('$s_value', '%Y-%m-%d %H:%i:%s')";
+                break;
+            default:
+                return -1;
+        }
+        $q = mysqli_query($con, "UPDATE $table SET $column = $value WHERE id = $id;");
+        return 0;
+    }
+
+
+    /*****************************************************
+     * Performs a delete                .                *
+     *                                                   *
+     * @params:                                          *
+     *    con (Mysql connection): Connection handler.    *
+     *    s_table (string): Table name.                  *
+     *    id (string): Row id. If the table doesn't have *
+     *       a column named 'id', this can't be used.    *
+     *****************************************************/
+    function db_delete($con, $s_table, $id){
+        $table = mysqli_real_escape_string($con, $s_table);
+        $q = mysqli_query($con, "DELETE FROM $table WHERE id = $id;");
+        return 0;
+    }
+?>

+ 37 - 0
www-admin/functions/save.php

@@ -0,0 +1,37 @@
+<?php
+    session_start();
+    $http_host = $_SERVER["HTTP_HOST"];
+    $doc_root = $_SERVER["DOCUMENT_ROOT"] . "/";
+    include $doc_root . "functions.php";
+    $proto = get_protocol();
+    $con = start_db();
+    $server = $proto . $http_host;
+    $pserver = substr($server, 0, strpos($http_host, ':')); // public server
+    if (!check_session($con)){
+        http_response_code(401);
+        header("Location: /authentication.php");
+    }
+    else{
+        $table = mysqli_real_escape_string($con, $_GET["t"]);
+        $column = mysqli_real_escape_string($con, $_GET["c"]);
+        $value = htmlspecialchars_decode(mysqli_real_escape_string($con, $_GET["v"]));
+        $id = mysqli_real_escape_string($con, $_GET["i"]);
+        // TODO: Validation!
+        if ($table == 'text'){
+            if (mysqli_query($con, "UPDATE text SET text = '$value' WHERE id = '$id' AND lang = '$column';")){
+                http_response_code(200); // OK.
+            }
+            else{
+                http_response_code(500); // Generic internal error.
+            }
+        }
+        else{
+            if (mysqli_query($con, "UPDATE $table SET $column = '$value' WHERE id = $id;")){
+                http_response_code(200); // OK.
+            }
+            else{
+                http_response_code(500); // Generic internal error.
+            }
+        }
+    }
+?>

+ 87 - 0
www-admin/header.php

@@ -0,0 +1,87 @@
+<div id='dialog'>
+    <div id='dialog_background'>
+    </div>
+    <div class='section'>
+        <h3 id='dialog_title' class='section_title'></h3>
+        <div class='entry'>
+            <p id='dialog_text'></p>
+            <div id='dialog_button_a_container'>
+                <input type='button' id='dialog_accept' value='Aceptar'/>
+            </div>
+            <div id='dialog_button_yn_container'>
+                <input type='button' id='dialog_yes' value='Yes'/>
+                <input type='button' id='dialog_no' value='No'/>
+            </div>
+        </div> <!-- .entry -->
+    </div> <!-- .section -->
+</div>
+<div id='header'>
+    <table>
+        <tr>
+            <td>
+                <img src='<?=$server?>/img/misc/logo.png'/>
+                <span style='font-weight:normal;font-style:italic;font-size:90%;'><?=$_SESSION['name']?></span>
+            </td>
+            <td>
+                <a href='<?=$server?>/' class='pointer header_section'>Inicio</a>
+            </td>
+            <td class='pointer header_section' onClick='showHeader("blog", this);'>
+                <span class='pointer'>Blog</span>
+            </td>
+            <td class='pointer header_section' onClick='showHeader("projects", this);'>
+                <span class='pointer'>Projects</span>
+            </td>
+            <td class='pointer header_section' onClick='showHeader("stats", this);'>
+                <span class='pointer'>Stats</span>
+            </td>
+            <td class='pointer header_section' onClick='showHeader("settings", this);'>
+                <span class='pointer'>Settings</span>
+            </td>
+        </tr>
+    </table>
+</div>
+<div class='secondary_header' id='header_blog'>
+    <table>
+        <tr>
+            <td>
+                <a href='<?=$server?>/blog/add/'>New post</a>
+            </td>
+            <td>
+                <a href='<?=$server?>/blog/'>Manage posts</a>
+            </td>
+        </tr>
+    </table>
+</div>
+<div class='secondary_header' id='header_projects'>
+    <table>
+        <tr>
+            <td>
+                <a href='<?=$server?>/projects/add/'>New project</a>
+            </td>
+            <td>
+                <a href='<?=$server?>/projects/'>Manage projects</a>
+            </td>
+        </tr>
+    </table>
+</div>
+<div class='secondary_header' id='header_stats'>
+    <table>
+        <tr>
+            <td>
+                <a href='<?=$server?>/stats/'>View stats</a>
+            </td>
+        </tr>
+    </table>
+</div>
+<div class='secondary_header' id='header_settings'>
+    <table>
+        <tr>
+            <td>
+                <a href='<?=$server?>/settings/'>Page settings</a>
+            </td>
+            <td>
+                <a href='<?=$server?>/settings/profile.php'>Profile settings</a>
+            </td>
+        </tr>
+    </table>
+</div>

BIN
www-admin/img/icon/cancel.png


BIN
www-admin/img/icon/edit.png


BIN
www-admin/img/icon/save.png


+ 101 - 0
www-admin/index.php

@@ -0,0 +1,101 @@
+<?php
+    session_start();
+    $http_host = $_SERVER["HTTP_HOST"];
+    $doc_root = $_SERVER["DOCUMENT_ROOT"] . "/";
+    include $doc_root . "functions.php";
+    $proto = get_protocol();
+    $con = start_db();
+    $server = $proto . $http_host;
+    $pserver = substr($server, 0, strpos($http_host, ':')); // public server
+    if (!check_session($con)){
+        header("Location: /authentication.php");
+        exit(-1);
+    }
+    else{
+?>
+<html>
+    <head>
+        <meta content='text/html; charset=windows-1252' http-equiv='content-type'/>
+        <meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1'>
+        <link rel='shortcut icon' href='<?=$pserver?>/img/logo/x2/favicon.ico'/>
+        <title>I&ntilde;igo Valentin - Administration Panel</title>
+        <style>
+<?php
+            include $doc_root . "css/ui.css";
+            include $doc_root . "css/home.css";
+?>
+        </style>
+        <!-- CSS for mobile version -->
+        <style media='(max-width : 990px)'>
+<?php
+            include $doc_root . "css/m/ui.css";
+            include $doc_root . "css/m/home.css";
+?>
+        </style>
+        <!-- Script files -->
+        <script type='text/javascript'>
+<?php
+            include $doc_root . "script/ui.js";
+?>
+        </script>
+    </head>
+    <body>
+<?php
+        include $doc_root . "header.php";
+?>
+        <div id='content'>
+            <div id='section_table'>
+                <div class='section_row'>
+                    <div class='section_cell'>
+                        <div class='section'>
+                            <h3 class='section_title'>Blog</h3>
+                            <div class='entry'>
+                                <ul>
+                                    <li><a href='<?=$server?>/blog/add/'>New post</a></li>
+                                    <li><a href='<?=$server?>/blog/'>Manage posts</a></li>
+                                </ul>
+                            </div>
+                        </div>
+                    </div>
+                    <div class='section_cell'>
+                        <div class='section'>
+                            <h3 class='section_title'>Projects</h3>
+                            <div class='entry'>
+                                <ul>
+                                    <li><a href='<?=$server?>/projects/add/'>New project</a></li>
+                                    <li><a href='<?=$server?>/projects/'>Manage projects</a></li>
+                                </ul>
+                            </div>
+                        </div>
+                    </div>
+                </div> <!-- .section_row -->
+                <div class='section_row'>
+                    <div class='section_cell'>
+                        <div class='section'>
+                            <h3 class='section_title'>Stats</h3>
+                            <div class='entry'>
+                                <ul>
+                                    <li><a href='<?=$server?>/stats/'>View stats</a></li>
+                                </ul>
+                            </div>
+                        </div>
+                    </div>
+                    <div class='section_cell'>
+                        <div class='section'>
+                            <h3 class='section_title'>Settings</h3>
+                            <div class='entry'>
+                                <ul>
+                                    <li><a href='<?=$server?>/settings/'>Page settings</a></li>
+                                    <li><a href='<?=$server?>/settings/profile.php'>Profile settings</a></li>
+                                </ul>
+                            </div>
+                        </div>
+                    </div>
+                </div> <!-- .section_row -->
+            </div> <!-- #section_table -->
+        </div> <!-- #content -->
+    </body>
+</html>
+<?php
+    }
+?>

+ 14 - 0
www-admin/login.php

@@ -0,0 +1,14 @@
+<?php
+    $doc_root = $_SERVER["DOCUMENT_ROOT"] . "/";
+    include $doc_root . "functions.php";
+    $con = start_db();
+    $user = mysqli_real_escape_string($con, $_POST["user"]);
+    $pass = mysqli_real_escape_string($con, $_POST["pass"]);
+    $pass = sha1($pass);
+    if (login($con, $user, $pass)){
+        header("Location: /index.php");
+    }
+    else{
+        header("Location: /authentication.php");
+    }
+?>

+ 33 - 0
www-admin/projects/delete.php

@@ -0,0 +1,33 @@
+<?php
+    session_start();
+    $http_host = $_SERVER["HTTP_HOST"];
+    $doc_root = $_SERVER["DOCUMENT_ROOT"] . "/";
+    include $doc_root . "functions.php";
+    $proto = get_protocol();
+    $con = start_db();
+    $server = $proto . $http_host;
+    $pserver = substr($server, 0, strpos($http_host, ':')); // public server
+    if (!check_session($con)){
+        http_response_code(401); // Not logged in: Forbidden.
+    }
+    else{
+        $id = intval(mysqli_real_escape_string($con, $_GET["p"]));
+        if (strlen($id) < 1){
+            http_response_code(400); // No or bad ID: Bad request.
+        }
+        else{
+            if (mysqli_num_rows(mysqli_query($con, "SELECT id FROM project WHERE id = $id;")) == 0){
+                http_response_code(404); // Unexistent id: Not found.
+            }
+            else{
+                mysqli_query($con, "DELETE FROM project_version WHERE project = $id;");
+                mysqli_query($con, "DELETE FROM project_url WHERE project = $id;");
+                mysqli_query($con, "DELETE FROM project_tag WHERE project = $id;");
+                mysqli_query($con, "DELETE FROM project_image WHERE project = $id;");
+                mysqli_query($con, "DELETE FROM project_comment WHERE project = $id;");
+                mysqli_query($con, "DELETE FROM project WHERE id = $id;");
+                http_response_code(200); //OK.
+            }
+        }
+    }
+?>

+ 28 - 0
www-admin/projects/deletecomment.php

@@ -0,0 +1,28 @@
+<?php
+    session_start();
+    $http_host = $_SERVER["HTTP_HOST"];
+    $doc_root = $_SERVER["DOCUMENT_ROOT"] . "/";
+    include $doc_root . "functions.php";
+    $proto = get_protocol();
+    $con = start_db();
+    $server = $proto . $http_host;
+    $pserver = substr($server, 0, strpos($http_host, ':')); // public server
+    if (!check_session($con)){
+        http_response_code(401); // Not logged in: Forbidden.
+    }
+    else{
+        $id = intval(mysqli_real_escape_string($con, $_GET["i"]));
+        if (strlen($id) < 1){
+            http_response_code(400); // No or bad ID: Bad request.
+        }
+        else{
+            if (mysqli_num_rows(mysqli_query($con, "SELECT id FROM project_comment WHERE id = $id;")) == 0){
+                http_response_code(404); // Unexistent id: Not found.
+            }
+            else{
+                mysqli_query($con, "DELETE FROM project_comment WHERE id = $id;");
+                http_response_code(200); //OK.
+            }
+        }
+    }
+?>

+ 28 - 0
www-admin/projects/deleteversion.php

@@ -0,0 +1,28 @@
+<?php
+    session_start();
+    $http_host = $_SERVER["HTTP_HOST"];
+    $doc_root = $_SERVER["DOCUMENT_ROOT"] . "/";
+    include $doc_root . "functions.php";
+    $proto = get_protocol();
+    $con = start_db();
+    $server = $proto . $http_host;
+    $pserver = substr($server, 0, strpos($http_host, ':')); // public server
+    if (!check_session($con)){
+        http_response_code(401); // Not logged in: Forbidden.
+    }
+    else{
+        $id = intval(mysqli_real_escape_string($con, $_GET["v"]));
+        if (strlen($id) < 1){
+            http_response_code(400); // No or bad ID: Bad request.
+        }
+        else{
+            if (mysqli_num_rows(mysqli_query($con, "SELECT id FROM project_version WHERE id = $id;")) == 0){
+                http_response_code(404); // Unexistent id: Not found.
+            }
+            else{
+                mysqli_query($con, "DELETE FROM project_version WHERE id = $id;");
+                http_response_code(200); //OK.
+            }
+        }
+    }
+?>

+ 421 - 0
www-admin/projects/edit.php

@@ -0,0 +1,421 @@
+<?php
+    session_start();
+    $http_host = $_SERVER["HTTP_HOST"];
+    $doc_root = $_SERVER["DOCUMENT_ROOT"] . "/";
+    include $doc_root . "functions.php";
+    $proto = get_protocol();
+    $lang = "en";
+    $con = start_db();
+    $server = $proto . $http_host;
+    $pserver = $proto . substr($http_host, 0, strpos($http_host, ':')); // public server
+    if (!check_session($con)){
+        http_response_code(401);
+        header("Location: /authentication.php");
+    }
+    else{
+        $id = intval(mysqli_real_escape_string($con, $_GET["p"]));
+        if (strlen($id) < 1){
+            http_response_code(400); // No or bad ID: Bad request.
+            header("Location: /projects/");
+        }
+        else{
+            $q_project = mysqli_query($con, "SELECT * FROM project WHERE id = $id;");
+            if (mysqli_num_rows($q_project) == 0){
+                http_response_code(404); // Unexistent id: Not found.
+                header("Location: /projects/");
+            }
+            else{
+                $r_project = mysqli_fetch_array($q_project);
+                $id = $r_project["id"];
+                $title = text($con, $r_project["title"], $lang);
+                $q_lang = mysqli_query($con, "SELECT * FROM lang;");
+?>
+<html>
+    <head>
+        <meta content='text/html; charset=windows-1252' http-equiv='content-type'/>
+        <meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1'>
+        <link rel='shortcut icon' href='<?=$pserver?>/img/logo/x2/favicon.ico'/>
+        <title><?=$title?>I&ntilde;igo Valentin - Administration Panel</title>
+        <style>
+<?php
+            include $doc_root . "css/ui.css";
+            include $doc_root . "css/projects.css";
+?>
+        </style>
+        <!-- CSS for mobile version -->
+        <style media='(max-width : 990px)'>
+<?php
+            include $doc_root . "css/m/ui.css";
+            include $doc_root . "css/m/projects.css";
+?>
+        </style>
+        <!-- Script files -->
+        <script type='text/javascript'>
+<?php
+            include $doc_root . "script/ui.js";
+            include $doc_root . "script/projects.js";
+?>
+        </script>
+         <script src='/script/ckeditor/ckeditor.js'></script>
+    </head>
+    <body>
+<?php
+        include $doc_root . "header.php";
+?>
+        <div id='content'>
+            <div class='section' id='project_edit'>
+                <h3 class='section_title'><?=$title?></h3>
+                <div class='entry'>
+                    <div id='lang_tabs'>
+                        <table>
+                            <tr>
+<?php
+                                mysqli_data_seek($q_lang, 0);
+                                $active = 'lang_tabs_active';
+                                while ($r_lang = mysqli_fetch_array($q_lang)){
+?>
+                                    <td class='pointer <?=$active?>' id='lang_tab_<?=$r_lang["code"]?>' onclick='showLanguage("<?=$r_lang["code"]?>");'>
+                                        <?=$r_lang["name"]?>
+                                    </td>
+<?php
+                                    $active = '';
+                                }
+?>
+                            </tr>
+                        </table>
+                    </div> <!-- #lang_tabs -->
+                    <span class='bold'>URL:</span>
+                    <?=$pserver?>/projects/
+                    <span class='editable'>
+                        <span class='field_result'><?=$r_project["permalink"]?></span>
+                        <img class='button_edit' src='<?=$server?>/img/icon/edit.png' onclick='enableEdit(this);'/>
+                        <input class='field_editable' type='text' value='<?=$r_project["permalink"]?>'/>
+                        <img class='button_save' src='<?=$server?>/img/icon/save.png' onclick='saveEdit(this, "project", "permalink", <?=$id?>);'/>
+                        <img class='button_cancel' src='<?=$server?>/img/icon/cancel.png' onclick='cancelEdit(this);'/>
+                    </span>
+                    <hr class='separator'/>
+                    <span class='bold'>Icon:</span>
+<?php
+                    if (strlen($r_project["icon"]) > 0){
+                        $src = "$pserver/img/projects/x3/$r_image[image]";
+                    }
+                    else{
+                        $src = "$server/img/icon/addimage.png";
+                    }
+?>
+                    <img class='pointer' onClick='selectIcon();' id='icon' src='<?=$src?>' />
+                    <input id='icon_file' type='file' accept='image/*' onchange='changeIcon(event, "<?=$title?>");'>
+                    <hr class='separator'/>
+<?php
+                    mysqli_data_seek($q_lang, 0);
+                    $active = "content_lang_active";
+                    while ($r_lang = mysqli_fetch_array($q_lang)){
+?>
+                        <div class='content_lang_<?=$r_lang["code"]?> content_lang <?=$active?>'>
+                            <div class='editable'>
+                                <span class='bold'>Title (<?=$r_lang["name"]?>):</span>
+                                <span class='field_result'><?=text($con, $r_project["title"], $r_lang["code"])?></span>
+                                <img class='button_edit' src='<?=$server?>/img/icon/edit.png' onclick='enableEdit(this);'/>
+                                <input class='field_editable' type='text' value='<?=text($con, $r_project["title"], $r_lang["code"])?>'/>
+                                <img class='button_save' src='<?=$server?>/img/icon/save.png' onclick='saveEdit(this, "text", "<?=$r_lang["code"]?>", "<?=$r_project["title"]?>");'/>
+                                <img class='button_cancel' src='<?=$server?>/img/icon/cancel.png' onclick='cancelEdit(this);'/>
+                            </div>
+                            <hr class='separator'/>
+                            <div class='editable'>
+                                <span class='bold'>Summary (<?=$r_lang["name"]?>):</span>
+                                <span class='field_result'><?=text($con, $r_project["summary"], $r_lang["code"])?></span>
+                                <img class='button_edit' src='<?=$server?>/img/icon/edit.png' onclick='enableEdit(this);'/>
+                                <input class='field_editable' type='text' value='<?=text($con, $r_project["summary"], $r_lang["code"])?>'/>
+                                <img class='button_save' src='<?=$server?>/img/icon/save.png' onclick='saveEdit(this, "text", "<?=$r_lang["code"]?>", "<?=$r_project["summary"]?>");'/>
+                                <img class='button_cancel' src='<?=$server?>/img/icon/cancel.png' onclick='cancelEdit(this);'/>
+                            </div>
+                            <hr class='separator'/>
+                            <div class='editable'>
+                                <span class='bold'>Text (<?=$r_lang["name"]?>):</span>
+                                <img class='button_edit' src='<?=$server?>/img/icon/edit.png' onclick='enableEdit(this);'/>
+                                <img class='button_save' src='<?=$server?>/img/icon/save.png' onclick='saveEdit(this, "text", "<?=$r_lang["code"]?>", "<?=$r_project["text"]?>");'/>
+                                <img class='button_cancel' src='<?=$server?>/img/icon/cancel.png' onclick='cancelEdit(this);'/>
+                                <br/>
+                                <p class='field_result'><?=text($con, $r_project["text"], $r_lang["code"])?></p>
+                                <textarea id='text_<?=$r_lang["code"]?>' class='field_editable'><?=text($con, $r_project["text"], $r_lang["code"])?></textarea>
+                                <script>CKEDITOR.replace('text_<?=$r_lang["code"]?>');</script>
+                            </div>
+                        </div> <!-- .project_add_language -->
+<?php
+                        $active = '';
+                    }
+?>
+                    <hr class='separator'/>
+                    <h4>Images</h4>
+                    <div id='image_reel' class='horizontal_scroll'>
+<?php
+                        $q_image = mysqli_query($con, "SELECT id, image FROM project_image WHERE project = $id;");
+                        while ($r_image = mysqli_fetch_array($q_image)){
+?>
+                            <span id='project_image_<?=$r_image["id"]?>'>
+                                <img alt='<?=$title?>' title='<?=$title?>' src='<?=$pserver?>/img/projects/x3/<?=$r_image["image"]?>' />
+                                <img class='pointer' onClick='deleteImage(<?=$r_image["id"]?>)' alt='Delete image' title='Delete image' src='<?=$server?>/img/icon/cancel.png' />
+                            </span>
+<?php
+                        }
+?>
+                        <span id='project_image_add'>
+                            <img onClick='addImage(<?=$id?>)' alt='Add image' title='Add image' src='<?=$server?>/img/icon/addimage.png' />
+                        </span> <!-- #image_reel -->
+                    </div>
+                    <hr class='separator'/>
+                    <h4>Tags</h4>
+                    TODO
+                    <hr class='separator'/>
+                    <h4>Versions</h4>
+                    <div id='versions'>
+                        <input id='new_version' type='button' value='New version' onClick='newVersion(<?=$id?>);' />
+<?php
+                        // TODO replace id -> project_version: id
+                        $q_version = mysqli_query($con, "SELECT project_version.id AS version, version_code, version_name, type, (SELECT text FROM text WHERE id = project_version_type.title AND lang = '$lang') AS type_name, color AS type_color, project_version.summary AS summary, changelog FROM project_version, project_version_type WHERE type = project_version_type.id AND project = $id ORDER BY version_code;");
+                        while ($r_version = mysqli_fetch_array($q_version)){
+?>
+                            <div class='entry list_enty'>
+                                <table id='versions'>
+                                    <tr>
+                                        <td class='version_name'>
+                                            <span class='editable'>
+                                                <span class='field_result'><?=$r_version["version_name"]?></span>
+                                                <img class='button_edit' src='<?=$server?>/img/icon/edit.png' onclick='enableEdit(this);'/>
+                                                <input class='field_editable' type='text' value='<?=$r_version["version_name"]?>'/>
+                                                <img class='button_save' src='<?=$server?>/img/icon/save.png' onclick='saveEdit(this, "project_version", "version", "<?=$r_version["id"]?>");'/>
+                                                <img class='button_cancel' src='<?=$server?>/img/icon/cancel.png' onclick='cancelEdit(this);'/>
+                                            </span>
+                                        </td>
+                                        <td class='version_details'>
+                                            <span class='bold'>Version code (fixed):</span> <?=$r_version["version_code"]?>
+                                            <br/>
+                                            <span class='editable'>
+                                                <span class='bold'>Version type:</span>
+                                                <span class='field_result'><?=$r_version["type_name"]?></span>
+                                                <img class='button_edit' src='<?=$server?>/img/icon/edit.png' onclick='enableEdit(this);'/>
+                                                <select class='field_editable' value='<?=$r_version["type"]?>'>
+<?php
+                                                    $q_version_type = mysqli_query($con, "SELECT id, title, color, (SELECT text FROM text WHERE id = project_version_type.title AND lang = '$lang') AS title FROM project_version_type;");
+                                                    while ($r_version_type = mysqli_fetch_array($q_version_type)){
+                                                        if ($r_version_type["id"] == $r_version["type"]){
+                                                            $selected = "selected";
+                                                        }
+                                                        else{
+                                                            $selected = "";
+                                                        }
+                                                        // TODO: Ignoring default 'selected'. It's because display: none?
+?>
+                                                        <option value='<?=$r_version_type["id"]?>' <?=$selected?>><?=$r_version_type["title"]?></option>
+<?php
+                                                    }
+?>
+                                                </select>
+                                                <img class='button_save' src='<?=$server?>/img/icon/save.png' onclick='saveEdit(this, "project_version", "type", "<?=$r_version["id"]?>");'/>
+                                                <img class='button_cancel' src='<?=$server?>/img/icon/cancel.png' onclick='cancelEdit(this);'/>
+                                            </span>
+<?php
+                                            mysqli_data_seek($q_lang, 0);
+                                            $active = "content_lang_active";
+                                            while ($r_lang = mysqli_fetch_array($q_lang)){
+?>
+                                                <div class='content_lang_<?=$r_lang["code"]?> content_lang <?=$active?>'>
+                                                    <div class='editable'>
+                                                        <span class='bold'>Summary (<?=$r_lang["name"]?>):</span>
+                                                        <span class='field_result'><?=text($con, $r_version["summary"], $r_lang["code"])?></span>
+                                                        <img class='button_edit' src='<?=$server?>/img/icon/edit.png' onclick='enableEdit(this);'/>
+                                                        <input class='field_editable' type='text' value='<?=text($con, $r_project["summary"], $r_lang["code"])?>'/>
+                                                        <img class='button_save' src='<?=$server?>/img/icon/save.png' onclick='saveEdit(this, "text", "<?=$r_lang["code"]?>", "<?=$r_version["summary"]?>");'/>
+                                                        <img class='button_cancel' src='<?=$server?>/img/icon/cancel.png' onclick='cancelEdit(this);'/>
+                                                    </div>
+                                                </div>
+                                                <div class='content_lang_<?=$r_lang["code"]?> content_lang <?=$active?>'>
+                                                    <div class='editable'>
+                                                        <span class='bold'>Changelog (<?=$r_lang["name"]?>):</span>
+                                                        <img class='button_edit' src='<?=$server?>/img/icon/edit.png' onclick='enableEdit(this);'/>
+                                                        <img class='button_save' src='<?=$server?>/img/icon/save.png' onclick='saveEdit(this, "text", "<?=$r_lang["code"]?>", "<?=$r_version["changelog"]?>");'/>
+                                                        <img class='button_cancel' src='<?=$server?>/img/icon/cancel.png' onclick='cancelEdit(this);'/>
+                                                        <p class='field_result'><?=text($con, $r_version["changelog"], $r_lang["code"])?></p>
+                                                        <textarea class='field_editable'><?=text($con, $r_project["summary"], $r_lang["code"])?></textarea>
+                                                    </div>
+                                                </div>
+<?php
+                                                $active = "";
+                                            }
+                                            if ($r_project["visible"] == 1){
+                                                $v = "Yes";
+                                                $selected1 = "selected";
+                                                $selected0 = "";
+                                            }
+                                            else{
+                                                $v = "No";
+                                                $selected1 = "";
+                                                $selected0 = "selected";
+                                            }
+?>
+                                            <div class='editable'>
+                                                <span class='bold'>Visible:</span>
+                                                <span class='field_result'><?=$v?></span>
+                                                <img class='button_edit' src='<?=$server?>/img/icon/edit.png' onclick='enableEdit(this);'/>
+                                                <select class='field_editable'>
+                                                    <option value='1' <?=$selected1?>>Yes</option>
+                                                    <option value='0' <?=$selected0?>>No</option>
+                                                </select>
+                                                <img class='button_save' src='<?=$server?>/img/icon/save.png' onclick='saveEdit(this, "project_version", "visible", "<?=$r_version["id"]?>");'/>
+                                                <img class='button_cancel' src='<?=$server?>/img/icon/cancel.png' onclick='cancelEdit(this);'/>
+                                            </div>
+
+                                        </td>
+                                        <td class='version_actions'>
+                                            <input type='button' onclick='deleteVersion(<?=$r_version["id"]?>, "<?=$title?>");' value='Delete'/>
+                                        </td>
+                                    </tr>
+                                </table>
+                            </div> <!-- .entry -->
+<?php
+                        }
+?>
+                    </div> <!-- #versions -->
+                    <hr class='separator'/>
+                    <h4>Settings</h4>
+<?php
+                    if ($r_project["visible"] == 1){
+                        $v = "Yes";
+                        $selected1 = "selected";
+                        $selected0 = "";
+                    }
+                    else{
+                        $v = "No";
+                        $selected1 = "";
+                        $selected0 = "selected";
+                    }
+?>
+                    <div class='editable'>
+                        <span class='bold'>Visible:</span>
+                        <span class='field_result'><?=$v?></span>
+                        <img class='button_edit' src='<?=$server?>/img/icon/edit.png' onclick='enableEdit(this);'/>
+                        <select class='field_editable'>
+                            <option value='1' <?=$selected1?>>Yes</option>
+                            <option value='0' <?=$selected0?>>No</option>
+                        </select>
+                        <img class='button_save' src='<?=$server?>/img/icon/save.png' onclick='saveEdit(this, "project", "visible", "<?=$id?>");'/>
+                        <img class='button_cancel' src='<?=$server?>/img/icon/cancel.png' onclick='cancelEdit(this);'/>
+                    </div>
+<?php
+                    if ($r_project["comments"] == 1){
+                        $v = "Yes";
+                        $selected1 = "selected";
+                        $selected0 = "";
+                    }
+                    else{
+                        $v = "No";
+                        $selected1 = "";
+                        $selected0 = "selected";
+                    }
+?>
+                    <div class='editable'>
+                        <span class='bold'>Comments enabled:</span>
+                        <span class='field_result'><?=$v?></span>
+                        <img class='button_edit' src='<?=$server?>/img/icon/edit.png' onclick='enableEdit(this);'/>
+                        <select class='field_editable'>
+                            <option value='1' <?=$selected1?>>Yes</option>
+                            <option value='0' <?=$selected0?>>No</option>
+                        </select>
+                        <img class='button_save' src='<?=$server?>/img/icon/save.png' onclick='saveEdit(this, "project", "comments", "<?=$id?>");'/>
+                        <img class='button_cancel' src='<?=$server?>/img/icon/cancel.png' onclick='cancelEdit(this);'/>
+                    </div>
+                    <input type='button' onclick='deleteProject(<?=$id?>, "<?=$title?>");' value='Delete project'/>
+                    <h4>Comments</h4>
+<?php
+                        $q_comment = mysqli_query($con, "SELECT project_comment.id AS id, (SELECT version_name FROM project_version WHERE id = project_comment.version) AS version, text, dtime, user, username, lang, (SELECT name FROM lang WHERE code = project_comment.lang) AS lang_name, approved, ip, browser, os, uagent FROM project_comment, stat_visit WHERE stat_visit.id = visit AND project = $id;");
+                        if (mysqli_num_rows($q_comment) == 0){
+?>
+                            <span>No comments yet</span>
+<?php
+                        }
+                        else{
+?>
+                            <table id='comments'>
+<?php
+                            while ($r_comment = mysqli_fetch_array($q_comment)){
+?>
+                                <tr>
+                                    <td class='text'>
+                                        <p><?=$r_comment["text"]?></p>
+                                    </td>
+                                    <td class='details'>
+                                        On <?=format_date($r_comment["dtime"], $lang, true)?>
+                                        <br/>
+                                        For version <?=$r_comment["version"]?>
+                                        <br/>
+<?php
+                                        if (strlen($r_comment["user"]) > 0){
+                                            $q_user = mysqli_query($con, "SELECT concat(fname, concat(' ', lname)) AS name, username FROM user WHERE id = $r_comment[user];");
+                                            $r_user = mysqli_fetch_array($q_user);
+?>
+                                            By <?=$r_user["name"]?> (<span class='bold'><?=$r_user["username"]?></span>)
+<?php
+                                        }
+                                        else{
+?>
+                                            By <?=$r_comment["username"]?>
+<?php
+                                        }
+                                        if (strlen($r_comment["lang_name"]) > 0){
+                                            $strlang = $r_comment["lang_name"];
+                                        }
+                                        else{
+                                            $strlang = $r_comment["lang"];
+                                        }
+                                        if (file_exists($doc_root . "img/lang/" . $r_comment["lang"] . ".gif")){
+                                            $imgpath = $doc_root . "img/lang/" . $r_comment["lang"] . ".gif";
+                                            $strlang = $strlang . " <img class='lang' title='$r_comment[lang]' alt='$r_comment[lang]' src='$server/$imgpath' />";
+                                        }
+?>
+                                        <br/>
+                                        Language: <?=$strlang?>
+<?php
+                                        if ($r_project["visible"] == 1){
+                                            $v = "Yes";
+                                            $selected1 = "selected";
+                                            $selected0 = "";
+                                        }
+                                        else{
+                                            $v = "No";
+                                            $selected1 = "";
+                                            $selected0 = "selected";
+                                        }
+?>
+                                        <div class='editable'>
+                                            Approved:
+                                            <span class='field_result'><?=$v?></span>
+                                            <img class='button_edit' src='<?=$server?>/img/icon/edit.png' onclick='enableEdit(this);'/>
+                                            <select class='field_editable'>
+                                                <option value='1' <?=$selected1?>>Yes</option>
+                                                <option value='0' <?=$selected0?>>No</option>
+                                            </select>
+                                            <img class='button_save' src='<?=$server?>/img/icon/save.png' onclick='saveEdit(this, "project_comment", "approved", "<?=$r_comment["id"]?>");'/>
+                                            <img class='button_cancel' src='<?=$server?>/img/icon/cancel.png' onclick='cancelEdit(this);'/>
+                                        </div>
+                                    </td>
+                                    <td class='actions'>
+                                        <input type='button' onclick='deleteProjectComment(<?=$id?>);' value='Delete'/>
+                                    </td>
+                                </tr>
+<?php
+                            }
+?>
+                            </table>
+<?php
+                        }
+?>
+                </div> <!-- .entry -->
+            </div> <!-- .section -->
+        </div> <!-- #content -->
+    </body>
+</html>
+<?php
+            }
+        }
+    }
+?>

+ 161 - 0
www-admin/projects/index.php

@@ -0,0 +1,161 @@
+<?php
+    session_start();
+    $http_host = $_SERVER["HTTP_HOST"];
+    $doc_root = $_SERVER["DOCUMENT_ROOT"] . "/";
+    include $doc_root . "functions.php";
+    $proto = get_protocol();
+    $con = start_db();
+    $server = $proto . $http_host;
+    $pserver = $proto . substr($http_host, 0, strpos($http_host, ':')); // public server
+    $lang = "en";
+    if (!check_session($con)){
+        header("Location: /authentication.php");
+        exit(-1);
+    }
+    else{
+?>
+<html>
+    <head>
+        <meta content='text/html; charset=windows-1252' http-equiv='content-type'/>
+        <meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1'>
+        <link rel='shortcut icon' href='<?=$pserver?>/img/logo/x2/favicon.ico'/>
+        <title>Projects - I&ntilde;igo Valentin - Administration Panel</title>
+        <style>
+<?php
+            include $doc_root . "css/ui.css";
+            include $doc_root . "css/projects.css";
+?>
+        </style>
+        <!-- CSS for mobile version -->
+        <style media='(max-width : 990px)'>
+<?php
+            include $doc_root . "css/m/ui.css";
+            include $doc_root . "css/m/projects.css";
+?>
+        </style>
+        <!-- Script files -->
+        <script type='text/javascript'>
+<?php
+            include $doc_root . "script/ui.js";
+            include $doc_root . "script/projects.js";
+?>
+        </script>
+    </head>
+    <body>
+<?php
+        include $doc_root . "header.php";
+?>
+        <div id='content'>
+            <div class='section'>
+                <h3 class='section_title'>Project list</h3>
+                <div class='entry'>
+                    <table id='project_list'>
+                        <tr>
+                            <th>Projects</th>
+                            <th>Details</th>
+                            <th>Actions</th>
+                        </tr>
+<?php
+                        $q_project = mysqli_query($con, "SELECT project.id, permalink, project_type.title AS type, project.title AS title, project.logo AS logo, header, text, license.id AS license, concat(fname, concat(' ', lname)) AS user, visible, comments, (SELECT count(id) FROM project_comment WHERE project = project.id) AS comment_count, (SELECT version_name FROM project_version v, project_version_type WHERE type = v.id AND v.project = project.id ORDER BY version_code DESC LIMIT 1) AS version, (SELECT dtime FROM project_version v WHERE  v.project = project.id ORDER BY version_code DESC LIMIT 1) AS dtime, (SELECT title FROM project_version v, project_version_type WHERE type = v.id AND v.project = project.id ORDER BY version_code DESC LIMIT 1) AS version_type, (SELECT color FROM project_version v, project_version_type WHERE type = v.id AND v.project = project.id ORDER BY version_code DESC LIMIT 1) AS version_color, (SELECT image FROM project_image WHERE project = project.id) AS image, (SELECT count(image) FROM project_image WHERE project = project.id ORDER BY idx LIMIT 1) AS image_count FROM project, project_type, license, user WHERE project.type = project_type.id AND project.license = license.id AND project.user = user.id;");
+                        while ($r_project = mysqli_fetch_array($q_project)){
+                            $title = text($con, $r_project["title"], $lang);
+?>
+                            <tr>
+                                <td class='td_project'>
+                                    <h4>
+                                        <a title='View <?=$title?> on a new tab' target='_blank' href='<?=$pserver?>/project/<?=$r_project["permalink"]?>'>
+<?php
+                                            if (strlen($r_project["logo"]) > 0){
+?>
+                                                <img alt='<?=$title?>' title='<?=$title?>' src='<?=$pserver?>/img/project/x2/<?=$r_project["logo"]?>' />
+<?php
+                                            }
+?>
+                                            <?=$title?>
+                                        </a>
+                                    </h4>
+                                    <p><?=cut_text(text($con, $r_project["header"],$lang), 300)?></p>
+<?php
+                                    if ($r_project["image_count"] > 0){
+?>
+                                        <img class='project_image' alt='<?=$title?>' title='<?=$title?>' src='<?=$pserver?>/img/project/x2/<?=$r_project["image"]?>' />
+<?php
+                                        if ($r_project["image_count"] == 2){
+?>
+                                            + other image.
+<?php
+                                        }
+                                        elseif ($r_project["image_count"] > 2){
+                                            $pl = $r_project["image_count"] - 1;
+?>
+                                            + <?=$pl?> other images.
+<?php
+                                        }
+                                    }
+?>
+                                </td>
+                                <td class='td_details'>
+                                    By <?=$r_project["user"]?>.<br/>
+                                    Last version 
+                                    <span style='color:<?=$r_project["version_color"]?>;'>
+                                        <?=$r_project["version"]?> (<?=strtolower(text($con, $r_project["version_type"], $lang))?>)
+                                    </span>
+                                    modified on <?=format_date($r_project["dtime"], $lang, false)?>.<br/>
+                                    Licensed under <?=$r_project["license"]?>.<br/>
+                                    Visible:
+<?php
+                                    if ($r_project["visible"] == 1){
+?>
+                                        Yes
+<?php
+                                    }
+                                    else{
+?>
+                                        No
+<?php
+                                    }
+?>
+                                    <br/>Comments: <?=$r_project["comment_count"]?><br/>
+                                    Comments allowed:
+<?php
+                                    if ($r_project["comments"] == 1){
+?>
+                                        Yes
+<?php
+                                    }
+                                    else{
+?>
+                                        No
+<?php
+                                    }
+?>
+                                </td>
+                                <td class='td_actions'>
+                                    <input type='button' onclick='manageProject(<?=$r_project["id"]?>);' value='Manage / Translate'/>
+                                    <input type='button' onclick='deleteProject(<?=$r_project["id"]?>, "<?=$title?>");' value='Delete'/>
+                                    <?php
+                                    if ($r_project["visible"] == 1){
+?>
+                                        <input type='button' onclick='visibleProject(<?=$r_project["id"]?>, "<?=$title?>", 0);' value='Unpublish'/>
+<?php
+                                    }
+                                    else{
+?>
+                                        <input type='button' onclick='visibleProject(<?=$r_project["id"]?>, "<?=$title?>", 1);' value='Publish'/>
+<?php
+                                    }
+?>
+                                </td>
+                            </tr>
+<?php
+                        }
+?>
+                    </table>
+                </div> <!-- .entry -->
+            </div> <!-- .section -->
+        </div> <!-- #content -->
+    </body>
+</html>
+<?php
+    }
+?>

+ 20 - 0
www-admin/projects/new.php

@@ -0,0 +1,20 @@
+<?php
+    session_start();
+    $http_host = $_SERVER["HTTP_HOST"];
+    $doc_root = $_SERVER["DOCUMENT_ROOT"] . "/";
+    include $doc_root . "functions.php";
+    $proto = get_protocol();
+    $con = start_db();
+    $server = $proto . $http_host;
+    $pserver = substr($server, 0, strpos($http_host, ':')); // public server
+    if (!check_session($con)){
+        http_response_code(401);
+        header("Location: /authentication.php");
+    }
+    else{
+        $user = mysqli_real_escape_string($_SESSION['id']);
+        mysqli_query($con, "INSERT INTO project (id, permalink, type, title, user, visible) VALUES ((SELECT max(id) +1 FROM project p), concat((SELECT max(id) +1 FROM project p), ''), 'M', 'NULL', $user, 0);");
+        $id = mysqli_fetch_array(mysqli_query($con, "SELECT max(id) AS id FROM project;"))["id"];
+        header("Location: /projects/edit.php?p=" + $id);
+    }
+?>

+ 28 - 0
www-admin/projects/uploadimage.php

@@ -0,0 +1,28 @@
+<?php
+    session_start();
+    $http_host = $_SERVER["HTTP_HOST"];
+    $doc_root = $_SERVER["DOCUMENT_ROOT"] . "/";
+    include $doc_root . "functions.php";
+    $proto = get_protocol();
+    $lang = "en";
+    $con = start_db();
+    $server = $proto . $http_host;
+    $pserver = $proto . substr($http_host, 0, strpos($http_host, ':')); // public server
+    if (!check_session($con)){
+        http_response_code(401);
+        header("Location: /authentication.php");
+    }
+    else{
+        foreach ($_POST as $key => $value){
+            error_log("{$key} = {$value}");
+        }
+        $name = mysqli_real_escape_string($con, $_GET["name"]);
+        $base64string = $_POST["image"];
+        error_log("FILENAME: $name");
+        error_log("PATH: $doc_root../www/img/projects/DEBUG_$name");
+        error_log("B64: $base64string");
+        file_put_contents("$doc_root../www/img/projects/DEBUG_$name", base64_decode($base64string));
+
+        echo("OK");
+    }
+?>

+ 29 - 0
www-admin/projects/visible.php

@@ -0,0 +1,29 @@
+<?php
+    session_start();
+    $http_host = $_SERVER["HTTP_HOST"];
+    $doc_root = $_SERVER["DOCUMENT_ROOT"] . "/";
+    include $doc_root . "functions.php";
+    $proto = get_protocol();
+    $con = start_db();
+    $server = $proto . $http_host;
+    $pserver = substr($server, 0, strpos($http_host, ':')); // public server
+    if (!check_session($con)){
+        http_response_code(401); // Not logged in: Forbidden.
+    }
+    else{
+        $id = intval(mysqli_real_escape_string($con, $_GET["p"]));
+        $visible = intval(mysqli_real_escape_string($con, $_GET["v"]));
+        if (strlen($id) < 1 || ($visible != 1 && $visible != 0)){
+            http_response_code(400); // No or bad ID, visible not 1 or 0: Bad request.
+        }
+        else{
+            if (mysqli_num_rows(mysqli_query($con, "SELECT id FROM project WHERE id = $id;")) == 0){
+                http_response_code(404); // Unexistent id: Not found.
+            }
+            else{
+                mysqli_query($con, "UPDATE project SET visible = $visible WHERE id = $id;");
+                http_response_code(200); //OK.
+            }
+        }
+    }
+?>

+ 69 - 0
www-admin/script/blog.js

@@ -0,0 +1,69 @@
+function managePost(id){
+    window.location.href = "/blog/edit.php?p=" + id;
+}
+
+function deletePost(id, title){
+    dialog("confirm", "Delete post", "Do you really want to delete the post '" + title + "'?<br/><br/>There is no way to undo this action. You can also unpublish the post instead of deleting it.", function (confirmed) {
+        if (confirmed) {
+            var xmlhttp;
+            if (window.XMLHttpRequest){
+                xmlhttp = new XMLHttpRequest();
+            }
+            xmlhttp.onreadystatechange=function(){
+                if (xmlhttp.readyState == 4){
+                    if (xmlhttp.status == 200){
+                        location.reload();
+                    }
+                    else{
+                        dialog("alert", "ERROR", "Error deleting the post '" + title + "'.", null);
+                    }
+                }
+            }
+            xmlhttp.open("GET","/blog/delete.php?p=" + id, true);
+            xmlhttp.send();
+        }
+    });
+}
+
+
+function deleteComment(id){
+    dialog("confirm", "Delete version", "Do you really want to delete this comment?<br/><br/>There is no way to undo this action. You can also disapprove the comment instead of deleting it.", function (confirmed) {
+        if (confirmed) {
+            var xmlhttp;
+            if (window.XMLHttpRequest){
+                xmlhttp = new XMLHttpRequest();
+            }
+            xmlhttp.onreadystatechange=function(){
+                if (xmlhttp.readyState == 4){
+                    if (xmlhttp.status == 200){
+                        location.reload();
+                    }
+                    else{
+                        dialog("alert", "ERROR", "Error deleting the comment.", null);
+                    }
+                }
+            }
+            xmlhttp.open("GET","/blog/deletecomment.php?i=" + id, true);
+            xmlhttp.send();
+        }
+    });
+}
+
+function visiblePost(id, title, visible){
+    var xmlhttp;
+    if (window.XMLHttpRequest){
+        xmlhttp = new XMLHttpRequest();
+    }
+    xmlhttp.onreadystatechange=function(){
+        if (xmlhttp.readyState == 4){
+            if (xmlhttp.status == 200){
+                location.reload();
+            }
+            else{
+                dialog("alert", "ERROR", "Error changing visibility of the post '" + title + "'.", null);
+            }
+        }
+    }
+    xmlhttp.open("GET","/post/visible.php?p=" + id + "&v=" + visible, true);
+    xmlhttp.send();
+}

+ 136 - 0
www-admin/script/projects.js

@@ -0,0 +1,136 @@
+function manageProject(id){
+    window.location.href = "/projects/edit.php?p=" + id;
+}
+
+function deleteProject(id, title){
+    dialog("confirm", "Delete project", "Do you really want to delete the project '" + title + "'?<br/><br/>There is no way to undo this action. You can also unpublish the project instead of deleting it.", function (confirmed) {
+        if (confirmed) {
+            var xmlhttp;
+            if (window.XMLHttpRequest){
+                xmlhttp = new XMLHttpRequest();
+            }
+            xmlhttp.onreadystatechange=function(){
+                if (xmlhttp.readyState == 4){
+                    if (xmlhttp.status == 200){
+                        location.reload();
+                    }
+                    else{
+                        dialog("alert", "ERROR", "Error deleting the project '" + title + "'.", null);
+                    }
+                }
+            }
+            xmlhttp.open("GET","/projects/delete.php?p=" + id, true);
+            xmlhttp.send();
+        }
+    });
+}
+
+function deleteVersion(id, title){
+    dialog("confirm", "Delete version", "Do you really want to delete the version '" + title + "'?<br/><br/>There is no way to undo this action. You can also unpublish the version instead of deleting it.", function (confirmed) {
+        if (confirmed) {
+            var xmlhttp;
+            if (window.XMLHttpRequest){
+                xmlhttp = new XMLHttpRequest();
+            }
+            xmlhttp.onreadystatechange=function(){
+                if (xmlhttp.readyState == 4){
+                    if (xmlhttp.status == 200){
+                        location.reload();
+                    }
+                    else{
+                        dialog("alert", "ERROR", "Error deleting the version '" + title + "'.", null);
+                    }
+                }
+            }
+            xmlhttp.open("GET","/projects/deleteversion.php?v=" + id, true);
+            xmlhttp.send();
+        }
+    });
+}
+
+function deleteComment(id){
+    dialog("confirm", "Delete version", "Do you really want to delete this comment?<br/><br/>There is no way to undo this action. You can also disapprove the comment instead of deleting it.", function (confirmed) {
+        if (confirmed) {
+            var xmlhttp;
+            if (window.XMLHttpRequest){
+                xmlhttp = new XMLHttpRequest();
+            }
+            xmlhttp.onreadystatechange=function(){
+                if (xmlhttp.readyState == 4){
+                    if (xmlhttp.status == 200){
+                        location.reload();
+                    }
+                    else{
+                        dialog("alert", "ERROR", "Error deleting the comment.", null);
+                    }
+                }
+            }
+            xmlhttp.open("GET","/projects/deletecomment.php?i=" + id, true);
+            xmlhttp.send();
+        }
+    });
+}
+
+function visibleProject(id, title, visible){
+    var xmlhttp;
+    if (window.XMLHttpRequest){
+        xmlhttp = new XMLHttpRequest();
+    }
+    xmlhttp.onreadystatechange=function(){
+        if (xmlhttp.readyState == 4){
+            if (xmlhttp.status == 200){
+                location.reload();
+            }
+            else{
+                dialog("alert", "ERROR", "Error changing visibility of the project '" + title + "'.", null);
+            }
+        }
+    }
+    xmlhttp.open("GET","/projects/visible.php?p=" + id + "&v=" + visible, true);
+    xmlhttp.send();
+}
+
+function selectIcon(){
+    document.getElementById("icon_file").click();
+}
+
+function changeIcon(event, title){
+    console.log("changeIcon 1");
+    var frame = document.getElementById("icon");
+    var file = event.target.files[0];
+    var reader = new FileReader();
+    var httpPost = new XMLHttpRequest();
+    console.log("changeIcon 2");
+    reader.onloadend = function() {
+        console.log("changeIcon LOADEND 1");
+        console.log('RESULT', reader.result);
+        var data = "image=IVV" + reader.result;
+        var path = "/projects/uploadimage.php?name=" + title + "-icon.png";
+        console.log("changeIcon LOADEND 2");
+        httpPost.open("POST", path, true);
+        console.log("changeIcon LOADEND 3");
+        httpPost.setRequestHeader('Content-Type', 'x-www-form-urlencoded');
+        httpPost.setRequestHeader("Content-length", data.length);
+        console.log("changeIcon LOADEND 4");
+        httpPost.send(data);
+        console.log("changeIcon LOADEND 5 - END");
+    }
+    console.log("changeIcon 3");
+    var b64 = reader.readAsDataURL(file);
+    //var b64 = reader.result;
+    console.log("changeIcon 4");
+    httpPost.onreadystatechange = function(err) {
+        console.log("changeIcon REQCHANGE");
+        if (httpPost.readyState == 4 && httpPost.status == 200){
+            console.log("changeIcon REQOK");
+            frame.src = window.URL.createObjectURL(event.target.files[0]);
+            console.log(httpPost.responseText);
+        }
+        else {
+            console.log("changeIcon REQ-ERROR");
+            console.log(err);
+        }
+    };
+    console.log("changeIcon 5 - END");
+    
+}

+ 227 - 0
www-admin/script/stats.js

@@ -0,0 +1,227 @@
+function BarGraph(context) {
+
+    var graph = this;
+    this.values = [];
+    this.labels = [];
+
+    this.setLabels = function(labels){
+        this.labels = labels;
+    }
+
+    this.setValues = function(values){
+        this.values = values;
+    }
+
+    this.draw = function () {
+
+        var values = graph.values;
+        var barW;
+        var barH;
+        var barB = 1; // Border
+        var ratio;
+        var maxH;
+        var max;
+        var i;
+
+        // Canvas dimensions
+        context.canvas.width = graph.width;
+        context.canvas.height = graph.height;
+
+        // Width of each bar
+        barW = graph.width / values.length - graph.margin * 2;
+        maxH = graph.height - 30;
+
+        // Reference height
+        var max = 0;
+        for (i = 0; i < values.length; i ++) {
+            if (values[i] > max) {
+                max = values[i];	
+            }
+        }
+
+        // Loop bars
+        for (i = 0; i < values.length; i ++) {
+
+            // Compare with the max value
+            barH = maxH * values[i] / max;
+            graph.height -= 15;
+
+            // Value
+            context.fillStyle = "#003300";
+            context.font = "bold 12px sans-serif";
+            context.textAlign = "center";
+            context.fillText(parseInt(values[i], 10), i * graph.width / values.length + (graph.width / values.length) / 2, graph.height - barH - 2);
+
+            // Bar background (border)
+            context.fillStyle = "#003300";
+            context.fillRect(graph.margin + i * graph.width / values.length, graph.height - barH, barW, barH);
+
+            // Bar fill
+            context.fillStyle = "#ccffcc";
+            context.fillRect(graph.margin + i * graph.width / values.length + barB, graph.height - barH + barB, barW - barB * 2, barH - barB * 2);
+
+            // Label
+            graph.height += 15;
+            context.fillStyle = "#003300";
+            context.font = "10px sans-serif";
+            context.textAlign = "center";
+            context.fillText(graph.labels[i], i * graph.width / values.length + (graph.width / values.length) / 2, graph.height - 5);
+        }
+    };
+
+  this.width = 450;
+  this.height = 150;
+  this.margin = 1;
+  
+}
+
+
+function HBarGraph(context) {
+
+    var graph = this;
+    this.values = [];
+    this.labels = [];
+
+    this.setLabels = function(labels){
+        this.labels = labels;
+    }
+
+    this.setValues = function(values){
+        this.values = values;
+    }
+
+    this.draw = function () {
+
+        var values = graph.values;
+        var barW;
+        var barH;
+        var barB = 1; // Border
+        var ratio;
+        var maxH;
+        var max;
+        var i;
+
+        // Canvas dimensions
+        context.canvas.width = graph.width;
+        context.canvas.height = graph.height;
+
+        // Height of each bar
+        barH = graph.height / values.length - graph.margin * 2;
+        maxW = graph.width - 200;
+
+        // Reference width
+        var max = 0;
+        for (i = 0; i < values.length; i ++) {
+            if (values[i] > max) {
+                max = values[i];	
+            }
+        }
+
+        // Loop bars
+        for (i = 0; i < values.length; i ++) {
+
+            // Compare with the max value
+            barW = maxW * values[i] / max;
+
+            // Label
+            context.fillStyle = "#003300";
+            context.font = "10px sans-serif";
+            context.textAlign = "right";
+            context.fillText(graph.labels[i], 150, barH / 2 + (graph.margin + i * graph.height / values.length));
+
+            // Bar background (border)
+            context.fillStyle = "#003300";
+            context.fillRect(160, graph.margin + i * graph.height / values.length, barW, barH);
+
+            // Bar fill
+            context.fillStyle = "#ccffcc";
+            context.fillRect(160 + barB, barB + (graph.margin + i * graph.height / values.length), barW - (2 * barB), barH - (2 * barB));
+
+            // Value
+            context.fillStyle = "#003300";
+            context.font = "bold 12px sans-serif";
+            context.textAlign = "left";
+            context.fillText(parseInt(values[i], 10), 170 + barW, barH / 2 + (graph.margin + i * graph.height / values.length));
+
+        }
+    };
+
+  this.width = 450;
+  this.height = 250;
+  this.margin = 1;
+  
+}
+
+
+function PieGraph(context) {
+
+    var graph = this;
+    this.values = [];
+    this.labels = [];
+
+    this.setLabels = function(labels){
+        this.labels = labels;
+    }
+
+    this.setValues = function(values){
+        this.values = values;
+    }
+
+    this.draw = function () {
+
+        var values = graph.values;
+        var barW;
+        var barH;
+        var barB = 1; // Border
+        var ratio;
+        var maxH;
+        var max;
+        var i;
+
+        // Canvas dimensions
+        context.canvas.width = graph.width;
+        context.canvas.height = graph.height;
+
+        // Sum of values
+        var sum = 0;
+        for (i = 0; i < values.length; i ++) {
+            sum = sum + values[i]
+        }
+
+        // Loop arcs
+        var iAngle = (-0.5) * Math.PI;
+        var hLegend = 30;
+        for (i = 0; i < values.length; i ++) {
+
+            // Calculate angle
+            var v = (values[i] * 2  / sum) * Math.PI;
+
+            // Draw arc
+            context.fillStyle = "#00" + (3 * (i + 3)).toString().substr(-1) + (3 * (i + 3)).toString().substr(-1) + "00";
+            context.strokeStyle = "#00" + (3 * (i + 3)).toString().substr(-1) + (3 * (i + 3)).toString().substr(-1) + "00";
+            context.beginPath();
+            context.arc(100, 100, 50, iAngle, iAngle + v);
+            context.lineWidth = 80;
+            context.stroke();
+
+            // Legend
+            var label = graph.labels[i] + " (" + (values[i] * 100 / sum).toFixed(2) + "%)";
+            context.beginPath();
+            context.arc(215, hLegend, 10, 0, 2 * Math.PI);
+            context.fill();
+            context.font = "10px sans-serif";
+            context.textAlign = "left";
+            context.fillText(label, 230, hLegend + 5);
+
+            // Prepare for next
+            iAngle = iAngle + v;
+            hLegend = hLegend + 30;
+
+        }
+    };
+
+  this.width = 380;
+  this.height = 200;
+  this.margin = 1;
+  
+}

+ 204 - 0
www-admin/script/ui.js

@@ -0,0 +1,204 @@
+/**
+ * Shows one of the headers.
+ * 
+ * @param header Header ID.
+ * @param from Element clicked to open the header. It will be highlighted.
+ */
+function showHeader(header, from){
+    if(document.getElementById('header_' + header).style.display == 'block'){
+        document.getElementById('header_' + header).style.display = 'none';
+        from.style.backgroundColor = '#000000';
+    }
+    else{
+        var headers = document.getElementsByClassName('secondary_header');
+        for (var i = 0; i < headers.length; i ++)
+                headers.item(i).style.display = 'none';
+        var sections = document.getElementsByClassName('header_section');
+        for (var j = 0; j < sections.length; j ++)
+                sections.item(j).style.backgroundColor = '#000000';
+        
+        document.getElementById('header_' + header).style.display = 'block';
+        from.style.backgroundColor = '#333333';
+    }
+}
+
+
+/**
+ * Shows a language tab.
+ * 
+ * @param lang Two letter language code. 'es', 'en' or 'eu'.
+ */
+function showLanguage(lang){
+    var en = document.getElementsByClassName('content_lang_en');
+    var es = document.getElementsByClassName('content_lang_es');
+    var eu = document.getElementsByClassName('content_lang_eu');
+    switch (lang){
+        case 'es':
+            for (var i = 0; i < en.length; ++i) {
+                en[i].style.display = 'none';
+                es[i].style.display = 'block';
+                eu[i].style.display = 'none';
+            }
+            document.getElementById('lang_tab_es').classList.add("lang_tabs_active");
+            document.getElementById('lang_tab_eu').classList.remove("lang_tabs_active");
+            document.getElementById('lang_tab_en').classList.remove("lang_tabs_active");
+            break;
+        case 'eu':
+            for (var i = 0; i < en.length; ++i) {
+                en[i].style.display = 'none';
+                es[i].style.display = 'none';
+                eu[i].style.display = 'block';
+            }
+            document.getElementById('lang_tab_es').classList.remove("lang_tabs_active");
+            document.getElementById('lang_tab_eu').classList.add("lang_tabs_active");
+            document.getElementById('lang_tab_en').classList.remove("lang_tabs_active");
+            break;
+        case 'en':
+            for (var i = 0; i < en.length; ++i) {
+                en[i].style.display = 'block';
+                es[i].style.display = 'none';
+                eu[i].style.display = 'none';
+            }
+            document.getElementById('lang_tab_es').classList.remove("lang_tabs_active");
+            document.getElementById('lang_tab_eu').classList.remove("lang_tabs_active");
+            document.getElementById('lang_tab_en').classList.add("lang_tabs_active");
+    }
+}
+
+function dialog(type, title, message, callback) {
+    var dialogWindow = document.getElementById('dialog');
+    var dialogText = document.getElementById('dialog_text');
+    var dialogTitle = document.getElementById('dialog_title');
+    var background = document.getElementById('dialog_background');
+    var ynContainer = document.getElementById('dialog_button_yn_container');
+    var aContainer = document.getElementById('dialog_button_a_container');
+    var yButton = document.getElementById('dialog_yes');
+    var nButton = document.getElementById('dialog_no');
+    var aButton = document.getElementById('dialog_accept');
+
+    function show(){
+        dialogTitle.innerText = title;
+        dialogText.innerHTML = message;
+        dialogWindow.style.display = 'block';
+        dialogWindow.style.opacity = '1';
+    };
+    function hide(){
+        dialogWindow.style.display = 'none';
+        dialogWindow.style.opacity = '0';
+    };
+
+    // Set up the dialog type
+    if (type == "confirm"){
+        ynContainer.style.display = "block";
+        aContainer.style.display = "none";
+        yButton.onclick = function() {
+            hide();
+            callback(true);
+        }
+        nButton.onclick = function() { 
+            hide();
+            callback(false);
+        }
+    }
+    else if (type == "alert"){
+        ynContainer.style.display = "none";
+        aContainer.style.display = "block";
+        aButton.onclick = function() {
+            hide();
+            callback(true);
+        };
+    }
+    show();
+}
+
+
+function enableEdit(button){
+    var container = button.parentElement;
+    var fieldResult = container.getElementsByClassName('field_result')[0];
+    var fieldEditable = container.getElementsByClassName('field_editable')[0];
+    var cke = container.getElementsByClassName('cke')[0];
+    var buttonEdit = container.getElementsByClassName('button_edit')[0];
+    var buttonSave = container.getElementsByClassName('button_save')[0];
+    var buttonCancel = container.getElementsByClassName('button_cancel')[0];
+    fieldEditable.value = fieldResult.innerHTML;
+    fieldResult.style.display = 'none';
+    if (typeof(cke) != 'undefined' && cke != null){
+        cke.style.display = 'block';
+    }
+    else{
+        fieldEditable.style.display = 'initial';
+    }
+    buttonEdit.style.display = 'none';
+    buttonSave.style.display = 'initial';
+    buttonCancel.style.display = 'initial';
+}
+
+function saveEdit(button, table, column, id){
+    var container = button.parentElement;
+    var fieldResult = container.getElementsByClassName('field_result')[0];
+    var fieldEditable = container.getElementsByClassName('field_editable')[0];
+    var cke = container.getElementsByClassName('cke')[0];
+    var buttonEdit = container.getElementsByClassName('button_edit')[0];
+    var buttonSave = container.getElementsByClassName('button_save')[0];
+    var buttonCancel = container.getElementsByClassName('button_cancel')[0];
+    var value;
+    if (typeof(cke) != 'undefined' && cke != null){
+        fieldEditable.innerHTML = CKEDITOR.instances['text_en'].getData()
+        fieldResult.innerHTML = fieldEditable.innerHTML;
+        value = escape(fieldEditable.innerHTML);
+    }
+    else{
+        value = escape(fieldEditable.value);
+    }
+    // TODO: Do some validation.
+    
+    var xmlhttp;
+            if (window.XMLHttpRequest){
+                xmlhttp = new XMLHttpRequest();
+            }
+            xmlhttp.onreadystatechange=function(){
+                if (xmlhttp.readyState == 4){
+                    if (xmlhttp.status == 200){
+                        if (typeof(cke) != 'undefined' && cke != null){
+                            cke.style.display = 'none';
+                            fieldEditable.innerHTML = CKEDITOR.instances['text_en'].getData()
+                            fieldResult.innerHTML = fieldEditable.innerHTML;
+                        }
+                        else{
+                            fieldEditable.style.display = 'none';
+                            fieldResult.innerHTML = fieldEditable.value;
+                        }
+                        fieldResult.style.display = 'initial';
+                        buttonEdit.style.display = 'initial';
+                        buttonSave.style.display = 'none';
+                        buttonCancel.style.display = 'none';
+                    }
+                    else{
+                        dialog("alert", "ERROR", "Error saving changes.", null);
+                    }
+                }
+            }
+            xmlhttp.open("GET","/functions/save.php?t=" + table + "&c=" + column + "&v=" + value + "&i=" + id, true);
+            xmlhttp.send();
+}
+
+function cancelEdit(button){
+    var container = button.parentElement;
+    var fieldResult = container.getElementsByClassName('field_result')[0];
+    var fieldEditable = container.getElementsByClassName('field_editable')[0];
+    var cke = container.getElementsByClassName('cke')[0];
+    var buttonEdit = container.getElementsByClassName('button_edit')[0];
+    var buttonSave = container.getElementsByClassName('button_save')[0];
+    var buttonCancel = container.getElementsByClassName('button_cancel')[0];
+    fieldEditable.value = fieldResult.innerHTML;
+    fieldResult.style.display = 'initial';
+        if (typeof(cke) != 'undefined' && cke != null){
+        cke.style.display = 'none';
+    }
+    else{
+        fieldEditable.style.display = 'none';
+    }
+    buttonEdit.style.display = 'initial';
+    buttonSave.style.display = 'none';
+    buttonCancel.style.display = 'none';
+}

+ 0 - 0
www-admin/settings/index.php


+ 0 - 0
www-admin/settings/profile.php


+ 266 - 0
www-admin/stats/index.php

@@ -0,0 +1,266 @@
+<?php
+    session_start();
+    $http_host = $_SERVER["HTTP_HOST"];
+    $doc_root = $_SERVER["DOCUMENT_ROOT"] . "/";
+    include $doc_root . "functions.php";
+    $proto = get_protocol();
+    $lang = "en";
+    $con = start_db();
+    $server = $proto . $http_host;
+    $pserver = $proto . substr($http_host, 0, strpos($http_host, ':')); // public server
+    if (!check_session($con)){
+        http_response_code(401);
+        header("Location: /authentication.php");
+    }
+    else{
+?>
+<html>
+    <head>
+        <meta content='text/html; charset=windows-1252' http-equiv='content-type'/>
+        <meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1'>
+        <link rel='shortcut icon' href='<?=$pserver?>/img/logo/x2/favicon.ico'/>
+        <title><?=$title?>Stats - I&ntilde;igo Valentin - Administration Panel</title>
+        <style>
+<?php
+            include $doc_root . "css/ui.css";
+            include $doc_root . "css/stats.css";
+?>
+        </style>
+        <!-- CSS for mobile version -->
+        <style media='(max-width : 990px)'>
+<?php
+            include $doc_root . "css/m/ui.css";
+            include $doc_root . "css/m/stats.css";
+?>
+        </style>
+        <!-- Script files -->
+        <script type='text/javascript'>
+<?php
+            include $doc_root . "script/ui.js";
+            include $doc_root . "script/stats.js";
+?>
+        </script>
+    </head>
+    <body>
+        <div id='content'>
+            <div class='section' id='post_edit'>
+                <h3 class='section_title'>Visits</h3>
+                <div class='entry'>
+                    <div id='visitCanvas'></div>
+                    <script>
+                        (function () {
+                            function createVisitCanvas() {
+                                var parent = document.getElementById("visitCanvas");
+                                var canvas = document.createElement("canvas");
+                                parent.appendChild(canvas);
+                                return canvas.getContext("2d");
+                            }
+                            var context = createVisitCanvas();
+                            var graph = new BarGraph(context);
+<?php
+                            $q_month = mysqli_query($con, "SELECT month(dtime) AS month, count(id) AS total FROM stat_visit GROUP BY year(dtime), month(dtime) ORDER BY dtime DESC LIMIT 12;");
+                            $name_string = "";
+                            $valu_string = "";
+                            $month = date('m');
+
+                            // Show at least the last twelve months, even if there is no data that old.
+                            for ($i = 0; $i < 12 - mysqli_num_rows($q_month); $i ++){
+                                $month_number = intval($month) - (12 - $i) + 1;
+                                if ($month_number <= 0){
+                                    $month_number = $month_number + 12;
+                                }
+                                $name_string = $name_string . "\"" . ucfirst(substr(text($con, "MONTH_" . str_pad($month_number, 2, "0", STR_PAD_LEFT), $lang), 0, 3)) . "\", ";     //"\"" . text($con, "MONTH_" . $r_month["month"], $lang) . "\", ";
+                                $valu_string = $valu_string . "0, ";
+                            }
+
+                            // Loop data
+                            while ($r_month = mysqli_fetch_array($q_month)){
+                                $name_string = $name_string . "\"" . ucfirst(substr(text($con, "MONTH_" . $r_month["month"], $lang), 0, 3)) . "\", ";
+                                $valu_string = $valu_string . $r_month["total"] . ", ";
+                            }
+                            $name_string = substr(trim($name_string), 0, -1);
+                            $valu_string = substr(trim($valu_string), 0, -1);
+?>
+                            graph.width = parseInt(window.getComputedStyle(document.getElementById("visitCanvas")).width);
+                            graph.height = 180;
+                            graph.setLabels([<?=$name_string?>]);
+                            graph.setValues([<?=$valu_string?>]);
+                            graph.draw();
+                        }());
+                    </script>
+                </div> <!-- .entry -->
+            </div> <!-- .section -->
+            <div class='section' id='post_edit'>
+                <h3 class='section_title'>Most popular pages</h3>
+                <div class='entry'>
+                    <div id='pagesCanvas'></div>
+                    <script>
+                        (function () {
+                            function createPagesCanvas() {
+                                var parent = document.getElementById("pagesCanvas");
+                                var canvas = document.createElement("canvas");
+                                parent.appendChild(canvas);
+                                return canvas.getContext("2d");
+                            }
+                            var context = createPagesCanvas();
+                            var graph = new HBarGraph(context);
+                            
+<?php
+                            $q_visit = mysqli_query($con, "SELECT count(id) AS total, section, entry FROM stat_view GROUP BY section, entry LIMIT 10;");
+                            $name_string = "";
+                            $valu_string = "";
+                            while ($r_visit = mysqli_fetch_array($q_visit)){
+                                $s = $r_visit["section"];
+                                $e = $r_visit["entry"];
+                                switch ($s){
+                                    case "index":
+                                        $name_string = $name_string . "\"Homepage\", ";
+                                        break;
+                                    case "profile":
+                                        $name_string = $name_string . "\"Profile page\", ";
+                                        break;
+                                    case "help":
+                                        $name_string = $name_string . "\"Help page\", ";
+                                        break;
+                                    case "project":
+                                        if (strlen(e) == 0){
+                                            $name_string = $name_string . "\"Projects page\", ";
+                                        }
+                                        else{
+                                            $t = text($con, mysqli_fetch_array(mysqli_query($con, "SELECT title, permalink FROM project WHERE id = $e;"))["title"], $lang);
+                                            $name_string = $name_string . "\"Project: " . $t . "\", ";
+                                        }
+                                        break;
+                                    case "blog":
+                                        if (strlen(e) == 0){
+                                            $name_string = $name_string . "\"Blog\", ";
+                                        }
+                                        else{
+                                            $t = text($con, mysqli_fetch_array(mysqli_query($con, "SELECT title, permalink FROM project WHERE id = $e;"))["title"], $lang);
+                                            $name_string = $name_string . "\"Post: " . $t . "\", ";
+                                        }
+                                        break;
+                                    default:
+                                        $name_string = $name_string . "\"" . $s . " - " . $e . "\", ";
+                                }
+                                $valu_string = $valu_string . $r_visit["total"] . ", ";
+                            }
+                            $name_string = substr(trim($name_string), 0, -1);
+                            $valu_string = substr(trim($valu_string), 0, -1);
+?>
+                            setTimeout(function(){
+                                graph.width = parseInt(window.getComputedStyle(document.getElementById("pagesCanvas")).width);
+                                graph.height = <?=(40 + (30 * mysqli_num_rows($q_visit)))?>;
+                                graph.setLabels([<?=$name_string?>]);
+                                graph.setValues([<?=$valu_string?>]);
+                                graph.draw();
+                            }, 0);
+                        }());
+                    </script>
+                </div> <!-- .entry -->
+            </div> <!-- .section -->
+            <div class='section'>
+                <h3 class='section_title'>Visitors profile</h3>
+                <div class='entry'>
+                    <div class='pie_chart' id='osCanvas'><h4>Top OS</h4></div>
+                    <div class='pie_chart' id='browserCanvas'><h4>Top browsers</h4></div>
+                    <div class='pie_chart' id='osBrowserCanvas'><h4>Top OS/browser</h4></div>
+                    <script>
+                        (function () {
+                            function createOsCanvas() {
+                                var parent = document.getElementById("osCanvas");
+                                var canvas = document.createElement("canvas");
+                                parent.appendChild(canvas);
+                                return canvas.getContext("2d");
+                            }
+                            var context = createOsCanvas();
+                            var graph = new PieGraph(context);
+<?php
+                            $q_os = mysqli_query($con, "SELECT count(id) AS total, os FROM stat_visit GROUP BY os ORDER BY total DESC LIMIT 9;");
+                            $name_string = "";
+                            $valu_string = "";
+                            while ($r_os = mysqli_fetch_array($q_os)){
+                                $name_string = $name_string . "\"" . $r_os["os"] . "\", ";
+                                $valu_string = $valu_string . $r_os["total"] . ", ";
+                            }
+                            $name_string = substr(trim($name_string), 0, -1);
+                            $valu_string = substr(trim($valu_string), 0, -1);
+?>
+                            var cHeight = 30 * (<?=mysqli_num_rows($q_os)?> + 3);
+                            if (cHeight < 200){
+                                cHeight = 200;
+                            }
+                            graph.width = 400;
+                            graph.height = cHeight;
+                            graph.setLabels([<?=$name_string?>]);
+                            graph.setValues([<?=$valu_string?>]);
+                            graph.draw();
+                        }());
+                        (function () {
+                            function createBrowserCanvas() {
+                                var parent = document.getElementById("browserCanvas");
+                                var canvas = document.createElement("canvas");
+                                parent.appendChild(canvas);
+                                return canvas.getContext("2d");
+                            }
+                            var context = createBrowserCanvas();
+                            var graph = new PieGraph(context);
+<?php
+                            $q_browser = mysqli_query($con, "SELECT count(id) AS total, browser FROM stat_visit GROUP BY browser ORDER BY total DESC LIMIT 9;");
+                            $name_string = "";
+                            $valu_string = "";
+                            while ($r_browser = mysqli_fetch_array($q_browser)){
+                                $name_string = $name_string . "\"" . $r_browser["browser"] . "\", ";
+                                $valu_string = $valu_string . $r_browser["total"] . ", ";
+                            }
+                            $name_string = substr(trim($name_string), 0, -1);
+                            $valu_string = substr(trim($valu_string), 0, -1);
+?>
+                            var cHeight = 30 * (<?=mysqli_num_rows($q_browser)?> + 3);
+                            if (cHeight < 200){
+                                cHeight = 200;
+                            }
+                            graph.width = 400;
+                            graph.height = cHeight;
+                            graph.setLabels([<?=$name_string?>]);
+                            graph.setValues([<?=$valu_string?>]);
+                            graph.draw();
+                        }());
+                        (function () {
+                            function createOsBrowserCanvas() {
+                                var parent = document.getElementById("osBrowserCanvas");
+                                var canvas = document.createElement("canvas");
+                                parent.appendChild(canvas);
+                                return canvas.getContext("2d");
+                            }
+                            var context = createOsBrowserCanvas();
+                            var graph = new PieGraph(context);
+<?php
+                            $q_osbrowser = mysqli_query($con, "SELECT count(id) AS total, os, browser FROM stat_visit GROUP BY os, browser ORDER BY total DESC LIMIT 9;");
+                            $name_string = "";
+                            $valu_string = "";
+                            while ($r_osbrowser = mysqli_fetch_array($q_osbrowser)){
+                                $name_string = $name_string . "\"" . $r_osbrowser["os"] . "/" . $r_osbrowser["browser"] . "\", ";
+                                $valu_string = $valu_string . $r_osbrowser["total"] . ", ";
+                            }
+                            $name_string = substr(trim($name_string), 0, -1);
+                            $valu_string = substr(trim($valu_string), 0, -1);
+?>
+                            var cHeight = 30 * (<?=mysqli_num_rows($q_osbrowser)?> + 3);
+                            if (cHeight < 200){
+                                cHeight = 200;
+                            }
+                            graph.width = 400;
+                            graph.height = cHeight;
+                            graph.setLabels([<?=$name_string?>]);
+                            graph.setValues([<?=$valu_string?>]);
+                            graph.draw();
+                        }());
+                    </script>
+                </div> <!-- .entry -->
+            </div> <!-- .section -->
+        </div>
+    </div>
+<?php
+    }
+?>

+ 1 - 1
www/functions.php

@@ -297,7 +297,7 @@
             return $text;
         }
         $cut = substr($text, 0, strpos($text, " ", $length));
-        $cut = closeTags($cut);
+        $cut = close_tags($cut);
         if (strlen($cut) == 0){
             $cut = $text;
         }