├── Commentia
├── updaterating
├── app
│ ├── Commentia
│ │ ├── .htaccess
│ │ ├── Lexicon
│ │ │ ├── Lexicon.php
│ │ │ └── locale
│ │ │ │ ├── en-US.php
│ │ │ │ ├── ru-RU.php
│ │ │ │ ├── de-DE.php
│ │ │ │ └── fr-FR.php
│ │ ├── DBHandler
│ │ │ └── DBHandler.php
│ │ ├── Roles
│ │ │ └── Roles.php
│ │ ├── Controllers
│ │ │ └── CommentiaController.php
│ │ └── Models
│ │ │ ├── Comments.php
│ │ │ └── Members.php
│ └── data
│ │ ├── db
│ │ ├── .htaccess
│ │ └── commentia.db
│ │ ├── avatars
│ │ ├── avatar_57d6d013a7ade9.74641826.jpg
│ │ ├── avatar_57d6d0698edcf7.15530964.jpg
│ │ ├── avatar_57d6d0be198521.79128790.png
│ │ └── placeholder
│ │ │ └── avatar_placeholder.jpg
│ │ └── config.php
├── composer.json
├── api.php
└── assets
│ ├── commentia.min.js
│ ├── commentia-default-theme.css
│ ├── style.example.css
│ ├── commentia-new.js
│ └── commentia.js
├── .gitignore
├── LICENSE
├── test.php
└── README.md
/Commentia/updaterating:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Commentia/app/Commentia/.htaccess:
--------------------------------------------------------------------------------
1 | deny from all
2 |
--------------------------------------------------------------------------------
/Commentia/app/data/db/.htaccess:
--------------------------------------------------------------------------------
1 | deny from all
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | vendor/
3 | composer.lock
4 |
--------------------------------------------------------------------------------
/Commentia/app/data/db/commentia.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/algb12/Commentia/HEAD/Commentia/app/data/db/commentia.db
--------------------------------------------------------------------------------
/Commentia/app/data/avatars/avatar_57d6d013a7ade9.74641826.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/algb12/Commentia/HEAD/Commentia/app/data/avatars/avatar_57d6d013a7ade9.74641826.jpg
--------------------------------------------------------------------------------
/Commentia/app/data/avatars/avatar_57d6d0698edcf7.15530964.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/algb12/Commentia/HEAD/Commentia/app/data/avatars/avatar_57d6d0698edcf7.15530964.jpg
--------------------------------------------------------------------------------
/Commentia/app/data/avatars/avatar_57d6d0be198521.79128790.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/algb12/Commentia/HEAD/Commentia/app/data/avatars/avatar_57d6d0be198521.79128790.png
--------------------------------------------------------------------------------
/Commentia/app/data/avatars/placeholder/avatar_placeholder.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/algb12/Commentia/HEAD/Commentia/app/data/avatars/placeholder/avatar_placeholder.jpg
--------------------------------------------------------------------------------
/Commentia/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "require": {
3 | "pixel418/markdownify": "2.1.*",
4 | "erusev/parsedown": "^1.6",
5 | "ircmaxell/password-compat": "^1.0",
6 | "google/apiclient": "^2.0"
7 | },
8 | "autoload": {
9 | "psr-4": {
10 | "Commentia\\": "app/Commentia"
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | Copyright (c) 2016 algb12.19@gmail.com
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5 |
6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7 |
8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
9 |
--------------------------------------------------------------------------------
/Commentia/app/Commentia/Lexicon/Lexicon.php:
--------------------------------------------------------------------------------
1 | load('en_US');
24 | }
25 | }
26 |
27 | /**
28 | * Returns a specified localised phrase using phrase identifier.
29 | *
30 | * @param string $phrase The phrase identifier, which is the same for every language
31 | *
32 | * @return string The localised phrase
33 | */
34 | public static function getPhrase($phrase)
35 | {
36 | return constant($phrase) ? constant($phrase) : 'Error: Undefined phrase in lexicon '."'$phrase'";
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Commentia/app/data/config.php:
--------------------------------------------------------------------------------
1 | open($dbfile);
20 |
21 | $this->exec('CREATE TABLE IF NOT EXISTS comments(
22 | ucid INT PRIMARY KEY,
23 | content TEXT,
24 | timestamp DATETIME,
25 | creator_username TEXT,
26 | is_deleted BOOLEAN,
27 | children TEXT,
28 | rating INT,
29 | pageid INT)'
30 | );
31 |
32 | $this->exec('CREATE TABLE IF NOT EXISTS members(
33 | username TEXT PRIMARY KEY,
34 | password_hash TEXT,
35 | email TEXT,
36 | avatar_file TEXT,
37 | is_banned BOOLEAN,
38 | role TEXT,
39 | upvoted_comments TEXT,
40 | downvoted_comments TEXT,
41 | member_since DATETIME)'
42 | );
43 |
44 | $this->exec('CREATE TABLE IF NOT EXISTS votes(
45 | voter TEXT PRIMARY KEY,
46 | comment INT,
47 | direction INT)'
48 | );
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Commentia/app/Commentia/Roles/Roles.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
24 |
25 |
26 |
27 |
28 | Commentia - A lightweight, no DB comment system
29 |
30 |
31 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | Comments:
44 |
45 |
48 | =$commentia->displayAuthForm();?>
49 | =$commentia->displaySignUpForm();?>
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/Commentia/app/Commentia/Lexicon/locale/en-US.php:
--------------------------------------------------------------------------------
1 | comments = new Comments($real_pageid);
47 | } else {
48 | exit('Error: Page ID not set');
49 | }
50 |
51 | $this->members = new Members();
52 |
53 | $this->params = array();
54 | foreach ($_GET as $key => $value) {
55 | $this->params[$key] = $value;
56 | }
57 |
58 | $_SESSION['__COMMENTIA__']['pageid'] = $pageid;
59 | }
60 |
61 | public function displayComments($is_ajax_request)
62 | {
63 | return $this->comments->displayComments($is_ajax_request);
64 | }
65 |
66 | public function createNewComment($content, $childof)
67 | {
68 | return $this->comments->createNewComment($content, $childof);
69 | }
70 |
71 | public function editComment($ucid, $content)
72 | {
73 | return $this->comments->editComment($ucid, $content);
74 | }
75 |
76 | public function updateRating($ucid, $direction)
77 | {
78 | return $this->comments->updateRating($ucid, $direction);
79 | }
80 |
81 | public function deleteComment($ucid)
82 | {
83 | return $this->comments->deleteComment($ucid);
84 | }
85 |
86 | public function getCommentMarkdown($ucid)
87 | {
88 | return $this->comments->getCommentMarkdown($ucid);
89 | }
90 |
91 | public function getCommentData($ucid, $entry)
92 | {
93 | return $this->comments->getCommentData($ucid, $entry);
94 | }
95 |
96 | public function getMemberData($username, $entry)
97 | {
98 | return $this->members->getMemberData($username, $entry);
99 | }
100 |
101 | public function loginMember($username, $password)
102 | {
103 | return $this->members->loginMember($username, $password);
104 | }
105 |
106 | public function logoutMember()
107 | {
108 | return $this->members->logoutMember();
109 | }
110 |
111 | public function signUpMember($username, $password, $retyped_password, $email, $avatar_file)
112 | {
113 | return $this->members->signUpMember($username, $password, $retyped_password, $email, $avatar_file);
114 | }
115 |
116 | public function displayAuthForm()
117 | {
118 | return $this->members->displayAuthForm();
119 | }
120 |
121 | public function displaySignUpForm()
122 | {
123 | return $this->members->displaySignUpForm();
124 | }
125 |
126 | public function getPhrase($phrase)
127 | {
128 | return Lexicon::getPhrase($phrase);
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/Commentia/api.php:
--------------------------------------------------------------------------------
1 | loginMember($username, $password);
40 | }
41 |
42 | if (($action === 'logoutMember')) {
43 | $commentia->logoutMember();
44 | }
45 |
46 | if ($action === 'signUpMember') {
47 | $username = $_POST['username'];
48 | $password = $_POST['password'];
49 | $retyped_password = $_POST['retyped_password'];
50 | $email = $_POST['email'];
51 | $avatar_file = $_POST['avatar_file'];
52 | $commentia->signUpMember($username, $password, $retyped_password, $email, $avatar_file);
53 | }
54 | }
55 |
56 | if (isset($_SESSION['__COMMENTIA__']['pageid'])
57 | && isset($_GET['action'])) {
58 | $pageid = $_SESSION['__COMMENTIA__']['pageid'];
59 | $action = $_GET['action'];
60 |
61 | if ($action === 'display') {
62 | echo $commentia->displayComments();
63 | }
64 |
65 | if ($action === 'getPhrase'
66 | && $_SESSION['__COMMENTIA__']['member_is_logged_in']) {
67 | if (isset($_GET['phrase'])) {
68 | echo $commentia->getPhrase($_GET['phrase']);
69 | }
70 | }
71 |
72 | if (($action === 'getCommentMarkdown')
73 | && isset($_GET['ucid'])
74 | && $_SESSION['__COMMENTIA__']['member_is_logged_in']) {
75 | $ucid = $_GET['ucid'];
76 | echo $commentia->getCommentMarkdown($ucid);
77 | }
78 | }
79 |
80 | if (isset($_SESSION['__COMMENTIA__']['pageid'])
81 | && isset($_POST['action'])
82 | && $_SESSION['__COMMENTIA__']['member_is_logged_in']) {
83 | $pageid = $_SESSION['__COMMENTIA__']['pageid'];
84 | $action = $_POST['action'];
85 |
86 | if ((($action === 'reply')
87 | || ($action === 'postNewComment'))
88 | && isset($_POST['content'])) {
89 | $content = $_POST['content'];
90 | $childof = $_POST['childof'];
91 |
92 | $commentia->createNewComment($content, $childof);
93 | }
94 |
95 | if (($roles->memberHasUsername($commentia->getCommentData($_POST['ucid'], 'creator_username'))
96 | || $roles->memberIsAdmin())
97 | && ($action === 'edit')
98 | && isset($_POST['content'])
99 | && isset($_POST['ucid'])) {
100 | $content = $_POST['content'];
101 | $ucid = $_POST['ucid'];
102 | $commentia->editComment($ucid, $content);
103 | }
104 |
105 | if (($roles->memberIsLoggedIn())
106 | && ($action === 'updateRating')
107 | && isset($_POST['direction'])
108 | && isset($_POST['ucid'])) {
109 | $direction = $_POST['direction'];
110 | $ucid = $_POST['ucid'];
111 | $commentia->updateRating($ucid, $direction);
112 | }
113 |
114 | if (($roles->memberHasUsername($commentia->getCommentData($_POST['ucid'], 'creator_username'))
115 | || $roles->memberIsAdmin())
116 | && ($action === 'delete')
117 | && isset($_POST['ucid'])) {
118 | $ucid = $_POST['ucid'];
119 | $commentia->deleteComment($ucid);
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/Commentia/assets/commentia.min.js:
--------------------------------------------------------------------------------
1 | var api="api.php";var pageid=document.getElementsByTagName("html")[0].getAttribute("data-pageid");function httpRequest(){try{http_request=new XMLHttpRequest();return http_request}catch(a){try{http_request=new ActiveXObject("Msxml2.XMLHTTP");return http_request}catch(a){try{http_request=new ActiveXObject("Microsoft.XMLHTTP");return http_request}catch(a){alert("Your browser broke!");return false}}}}function refreshComments(){var b=document.getElementById("comments_pageid-"+pageid);var a=api+"?pageid="+pageid+"&action=display";var c=httpRequest();console.log("GET request to: "+a);c.onreadystatechange=function(){if(c.readyState==4){b.innerHTML=c.responseText;c=null}};c.open("GET",a,true);c.send()}function showReplyArea(e){var h=findCommentRoot(e);var b=h.getAttribute("data-ucid");var f="reply-area-"+b;var a="reply-box-"+b;var g="reply-button-"+b;var d="edit-cancel-button-"+b;if(!document.getElementById(f)){var k=document.createElement("div");k.setAttribute("id",f);var c=document.createElement("textarea");c.setAttribute("id",a);c.setAttribute("oninput","autoGrow(this);");k.appendChild(c);var i=document.createElement("button");i.innerHTML="reply";i.setAttribute("id",g);i.setAttribute("onclick","postReply(this);");k.appendChild(i);var j=document.createElement("button");j.innerHTML="cancel";j.setAttribute("id",d);j.setAttribute("onclick","hideReplyArea(this);");k.appendChild(j);h.getElementsByClassName("commentia-comment__reply-area")[0].appendChild(k)}document.getElementById(f).style.display="block"}function hideReplyArea(b){var d=findCommentRoot(b);var a=d.getAttribute("data-ucid");var c="reply-area-"+a;document.getElementById(c).style.display="none"}function findCommentRoot(a){while((a=a.parentNode)&&!a.hasAttribute("data-ucid")){}return a}function postReply(e){var g=findCommentRoot(e);var c=g.getAttribute("data-ucid");var i=g.getAttribute("data-reply-path");var b="reply-box-"+c;var d=document.getElementById("comments_pageid-"+pageid);var h=encodeURI(document.getElementById(b).value);var f="pageid="+pageid+"&action=reply&content="+h+"&reply_path="+i+"&username=user0";console.log("POST request to: "+api+" with params: "+f);var a=httpRequest();a.onreadystatechange=function(){if(a.readyState==4){refreshComments();a=null}};a.open("POST",api,true);a.setRequestHeader("Content-type","application/x-www-form-urlencoded");a.send(f)}function postNewComment(a){var f="comment-box";var c=document.getElementById("comments_pageid-"+pageid);var b=encodeURI(document.getElementById(f).value);var d="pageid="+pageid+"&action=postNewComment&content="+b+"&username=user0";var e=httpRequest();console.log("POST request to: "+api+" with params: "+d);e.onreadystatechange=function(){if(e.readyState==4){refreshComments();e=null}};e.open("POST",api,true);e.setRequestHeader("Content-type","application/x-www-form-urlencoded");e.send(d)}function deleteComment(b){if(confirm("Are you sure that you want to delete this comment?")){var c=document.getElementById("comments_pageid-"+pageid);var g=findCommentRoot(b);var a=g.getAttribute("data-ucid");var e=g.getAttribute("data-reply-path");var d="pageid="+pageid+"&action=delete&ucid="+a+"&reply_path="+e;console.log("POST request to: "+api+" with params: "+d);var f=httpRequest();f.onreadystatechange=function(){if(f.readyState==4){refreshComments();f=null}};f.open("POST",api,true);f.setRequestHeader("Content-type","application/x-www-form-urlencoded");f.send(d)}}function showEditArea(g){var i=findCommentRoot(g);var c=i.getAttribute("data-ucid");var l=i.getAttribute("data-reply-path");var k="edit-area-"+c;var j="edit-box-"+c;var f="edit-button-"+c;var e="edit-cancel-button-"+c;if(!document.getElementById(k)){var h=document.createElement("div");h.setAttribute("id",k);var n=document.createElement("textarea");n.setAttribute("id",j);n.setAttribute("oninput","autoGrow(this);");var b=api+"?pageid="+pageid+"&action=getCommentMarkdown&ucid="+c+"&reply_path="+l;var a=httpRequest();console.log("GET request to: "+b);a.onreadystatechange=function(){if(a.readyState==4){i.getElementsByClassName("commentia-comment__content")[0].style.display="none";i.getElementsByClassName("commentia-comment__edit-area")[0].appendChild(h);i.getElementsByClassName("commentia-comment__edit-area")[0].style.display="block";n.innerHTML=a.responseText;document.getElementById(j).style.height=document.getElementById(j).scrollHeight+"px";a=null}};a.open("GET",b,true);a.send();h.appendChild(n);var d=document.createElement("button");d.innerHTML="edit";d.setAttribute("id",f);d.setAttribute("onclick","editComment(this);");h.appendChild(d);var m=document.createElement("button");m.innerHTML="cancel";m.setAttribute("id",e);m.setAttribute("onclick","hideEditArea(this);");h.appendChild(m)}else{i.getElementsByClassName("commentia-comment__content")[0].style.display="none";i.getElementsByClassName("commentia-comment__edit-area")[0].style.display="block"}}function hideEditArea(a){var b=findCommentRoot(a);b.getElementsByClassName("commentia-comment__edit-area")[0].style.display="none";b.getElementsByClassName("commentia-comment__content")[0].style.display="block"}function editComment(b){var g=findCommentRoot(b);var a=g.getAttribute("data-ucid");var c=document.getElementById("edit-box-"+a);var e=g.getAttribute("data-reply-path");var d="pageid="+pageid+"&action=edit&content="+encodeURI(c.value)+"&ucid="+a+"&reply_path="+e;var f=httpRequest();console.log("POST request to: "+api+" with params: "+d);f.onreadystatechange=function(){if(f.readyState==4){refreshComments();f=null}};f.open("POST",api,true);f.setRequestHeader("Content-type","application/x-www-form-urlencoded");f.send(d)}function autoGrow(a){a.style.height="auto";a.style.height=(a.scrollHeight+5)+"px"};
2 |
--------------------------------------------------------------------------------
/Commentia/assets/commentia-default-theme.css:
--------------------------------------------------------------------------------
1 | /* The Commentia CSS file used for theming the comment section */
2 |
3 | /* =GENERAL STYLES FOR CONTROLS AND TABLES= */
4 | .commentia__comments-container button,
5 | .commentia__login-form input[type=submit],
6 | .commentia__logout-form input[type=submit],
7 | .commentia__signup-form input[type=submit] {
8 | -webkit-appearance: none;
9 | margin: 0;
10 | padding: 6px;
11 | font-size: 1em;
12 | border: 1px solid #000;
13 | border-radius: 4px;
14 | color: #000;
15 | }
16 | .commentia__login-form table, .commentia__login-form form,
17 | .commentia__logout-form table, .commentia__logout-form form,
18 | .commentia__signup-form table, .commentia__signup-form form {
19 | width: 100%;
20 | max-width: 350px;
21 | white-space: nowrap;
22 | }
23 | .commentia__login-form input[type=text],
24 | .commentia__login-form input[type=password],
25 | .commentia__login-form input[type=email],
26 | .commentia__logout-form input[type=text],
27 | .commentia__logout-form input[type=password],
28 | .commentia__logout-form input[type=email],
29 | .commentia__signup-form input[type=text],
30 | .commentia__signup-form input[type=password],
31 | .commentia__signup-form input[type=email] {
32 | width: 100%;
33 | box-sizing: border-box;
34 | }
35 |
36 | /* =GENERAL STYLES FOR WHOLE COMMENTS CONTAINER= */
37 | .commentia__comments-container * {
38 | margin: 0;
39 | font-size: 1em;
40 | font-family: inherit;
41 | }
42 | .commentia__comments-container button {
43 | background-color: #adf;
44 | width: 100%;
45 | }
46 | .commentia__comments-container button:hover {
47 | background-color: #9ce;
48 | }
49 | .commentia__comments-container button:active {
50 | background-color: #8bd;
51 | }
52 | .commentia__comments-container {
53 | background: #ededed;
54 | padding: 4px;
55 | overflow: auto;
56 | }
57 | .commentia__comments-container textarea {
58 | min-height: 50px;
59 | max-height: 800px;
60 | width: 100%;
61 | box-sizing: border-box;
62 | }
63 |
64 | /* ==COMMENT STYLES== */
65 | .commentia-comment {
66 | overflow: auto;
67 | clear: both;
68 | border-style: solid;
69 | border-color: #ccc;
70 | border-width: 2px 0 2px 8px;
71 | border-radius: 12px 0 0 12px;
72 | margin: 4px 2px 2px 2px;
73 | padding: 4px 4px 0 4px;
74 | }
75 |
76 | /* ===COMMENT INFO UI ELEMENTS STYLES=== */
77 | .commentia-comment-info {
78 | height: 3.1em;
79 | padding: 0 5px 5px;
80 | }
81 | .commentia-comment-info__by, .commentia-comment-info__timestamp {
82 | font-size: 0.8em;
83 | float: left;
84 | line-height: 1;
85 | padding: 1.44em 1.44em 1.44em 0;
86 | }
87 | .commentia-comment-info__member-avatar {
88 | height: 100%;
89 | float: left;
90 | margin-right: 12px;
91 | }
92 |
93 | /* ===COMMENT EDIT AREA STYLES=== */
94 | .commentia-comment__content, .commentia-comment__edit-area {
95 | clear: both;
96 | padding: 2px;
97 | border-width: 1px 0 1px;
98 | border-color: #ccc;
99 | border-style: solid;
100 | }
101 | .commentia-comment__edit-area {
102 | display: none;
103 | }
104 | .commentia-comment__edit-area button, .commentia-comment__reply-area button {
105 | width: auto !important;
106 | margin: 4px;
107 | }
108 | .commentia-comment__edit-area textarea {
109 | width: 100%;
110 | margin: 2px;
111 | }
112 |
113 | /* ===COMMENT CONTENT STYLES=== */
114 | .commentia-comment__content p {
115 | margin-top: 10px;
116 | }
117 | .commentia-comment__content h1, .commentia-comment__content h2,
118 | .commentia-comment__content h3, .commentia-comment__content h4,
119 | .commentia-comment__content h5, .commentia-comment__content h6 {
120 | margin: 15px 2px 5px;
121 | }
122 | .commentia-comment__content blockquote {
123 | margin-top: 10px;
124 | margin-bottom: 10px;
125 | margin-left: 10px;
126 | padding-left: 8px;
127 | border-left: 4px solid #888;
128 | }
129 |
130 | /* ===COMMENT CONTROLS STYLES=== */
131 | .commentia-comment-controls {
132 | margin: 10px;
133 | float: left;
134 | }
135 | .commentia-comment-controls a {
136 | padding: 3px;
137 | font-size: 1em;
138 | text-decoration: none;
139 | width: 100%;
140 | background-color: #adf;
141 | border: 1px solid #000;
142 | border-radius: 4px;
143 | color: #000000;
144 | margin: 0 4px 0 0;
145 | }
146 | .commentia-comment-controls a:hover {
147 | background-color: #9ce;
148 | }
149 | .commentia-comment-controls a:active {
150 | background-color: #8bd;
151 | }
152 | .commentia-rating-controls {
153 | width: 16px;
154 | height: 30px;
155 | margin: 5px;
156 | position: relative;
157 | float: left;
158 | }
159 | .commentia-rating-controls__arrow {
160 | position: absolute;
161 | width: 0;
162 | height: 0;
163 | border-style: solid;
164 | }
165 | .commentia-rating-controls__arrow--up {
166 | border-width: 0 8px 10px 8px;
167 | border-color: transparent transparent #000 transparent;
168 | top: 0;
169 | }
170 | .commentia-rating-controls__arrow--down {
171 | border-width: 10px 8px 0 8px;
172 | border-color: #000 transparent transparent transparent;
173 | bottom: 0;
174 | }
175 | .commentia-rating-indicator {
176 | margin: 10px 0 0 0;
177 | float: left;
178 | }
179 |
180 | /* ==NEW COMMENT AREA STYLES== */
181 | .commentia__new-comment-area {
182 | margin: 2px;
183 | }
184 | .commentia__new-comment-area h4 {
185 | margin: 15px 2px 5px;
186 | }
187 | #comment-box {
188 | min-height: 50px;
189 | max-height: 800px;
190 | width: 100%;
191 | box-sizing: border-box;
192 | }
193 |
194 | /* =LOGIN FORM STYLES= */
195 | .commentia__login-form input[type=submit] {
196 | background-color: #bfa;
197 | }
198 | .commentia__login-form input[type=submit]:hover {
199 | background-color: #ae9;
200 | }
201 | .commentia__login-form input[type=submit]:active {
202 | background-color: #9d8;
203 | }
204 |
205 | /* =LOGOUT FORM STYLES= */
206 | .commentia__logout-form input[type=submit] {
207 | background-color: #fca;
208 | }
209 | .commentia__logout-form input[type=submit]:hover {
210 | background-color: #eb9;
211 | }
212 | .commentia__logout-form input[type=submit]:active {
213 | background-color: #da8;
214 | }
215 |
216 | /* =SIGNUP FORM STYLES */
217 | .commentia__signup-form input[type=submit] {
218 | background-color: #fea;
219 | }
220 | .commentia__signup-form input[type=submit]:hover {
221 | background-color: #ed9;
222 | }
223 | .commentia__signup-form input[type=submit]:active {
224 | background-color: #dc8;
225 | }
226 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Commentia – Documentation
2 |
3 | Commentia is a plugin which adds a comment functionality to any webpage, and does not require any database server, but uses a flat file, SQLite3 DB to store data, and only requires PHP, GD, the SQLite3 module installed/enabled on the server, and Composer for PHP dependency bootstrapping and autoloader generation (both, GD and SQLite3 module usually come preinstalled by default, Composer is easy to install).
4 |
5 | Even though CMSs with commenting functionality already exist, some people prefer to have a static website without any CMS, and still want commenting functionality. This plugin caters for these kinds of people. Also, this plugin is fully self-contained, that is, comments and users don't get stored in the website database, if it runs on one.
6 |
7 | The following is a short documentation for Commentia, serving as a quick getting started guide, and providing some background information on its technical workings:
8 |
9 | ## Dependencies
10 |
11 | ### Composer
12 |
13 | The only real dependency (besides php-gd and php-sqlite3, but they're probably installed already anyways) is the Composer dependency manager – it will manage all needed dependencies all by itself.
14 |
15 | The user issues one command (`composer install`), and composer will automatically download all dependencies and dump the PSR-4 autoloader.
16 |
17 | If need be, please read more on how to [install composer](https://getcomposer.org/doc/00-intro.md#installation-linux-unix-osx 'Installing composer').
18 |
19 | ## Installation
20 |
21 | ### Unpacking onto website
22 |
23 | Unpack commentia into a separate folder (such as `/var/www/example.org/public_html/commentia`) or into the web-root, although a separate directory is recommended for security purposes.
24 |
25 | ### Running Composer's install
26 |
27 | In a command line, switch to the Commentia directory with the `composer.json` file. Then, run either `composer self-update` followed by `composer install`, or replace the command `composer` with `composer.phar`, depending on whether the `.phar` version of Composer is used or not.
28 |
29 | This should install all the needed dependencies. A `/vendor` directory and `composer.lock` file will be created. Do not delete them, they are very important.
30 |
31 | ### Modification of website code
32 |
33 | #### Initialization
34 |
35 | Once Composer has run, modify the website template or website to have the following code at the top of the page:
36 |
37 | ```php
38 |
53 | ```
54 |
55 | Where `x` is the right side of the assignment for the `$pageid`, which can be any function/shortcode returning an ID for the page, such as:
56 |
57 | - Wordpress's `get_the_ID()`
58 | - Drupal's `$node->nid`
59 | - MODX's `[[*id]]` template tag.
60 |
61 | And `$commentia_dir` is the path to the directory Commentia is in.
62 |
63 | Throughout other parts of this README, the term `commentia_dir` will be referred to exactly as that. It should be a root-relative and not absolute path, as the same path will be used for loading of resources such as comment area CSS and avatar images.
64 |
65 | The above just describes some of the popular CMSs' ways of getting the page ID. For static blogs, `x` can also be any manually entered page ID. Make sure to replace `x` with the relevant way of getting the page ID.
66 |
67 | It is irrelevant whether `$pageid` is a number or an alphanumeric string, as Commentia is just checking under the given page ID for comments to display.
68 |
69 | The only thing that _does_ matter is that the page ID should be unique.
70 |
71 | #### Modification of relevant tags
72 |
73 | Now, just include 2 files in the head of the website, using the syntax which will locate the files in the `$commentia_dir` automatically. If, as supposed to, a root relative path is used, this code will automatically load all needed resources for Commentia. For this, modify the `` tag as follows:
74 |
75 | ```html
76 |
80 |
81 |
82 |
83 | ```
84 |
85 | Now, there should be a working instance of Commentia. Mind that for a folder-based structure, pages nested in folders may need `$commentia_dir` to be adjusted accordingly.
86 |
87 | #### Displaying the comments section and login form
88 |
89 | To display the comments section and login/signup form, use the following code:
90 |
91 | ```html
92 | =$commentia->displayComments();?>
93 | =$commentia->displayAuthForm();?>
94 | ```
95 |
96 | ## Technical details
97 |
98 | Internally, each comment has a UCID, a **U**nique **C**omment **ID**. These are referred to as `$ucid` in the code. Each new comment would get a UCID of the last UCID + 1.
99 |
100 | Manual changes can be made to the SQLite3 database by using an SQLite editor. A recommended editor is the [DB Browser for SQLite](http://sqlitebrowser.org).
101 |
102 | ## Translatability
103 |
104 | **Note: If anyone has made good translations, it would be appreciated if they were shared with me. Just send me the file to my professional email: algb12.19@gmail.com. Thank you!**
105 |
106 | Both, translating the commenting front-end with an existing language and creating a new language is very easy. This is a list for language files currently included in the project:
107 |
108 | * American English/American English): `en-US.php`
109 | * German (Germany)/Deutsch (Deutschland): `de-DE.php`
110 | * French (France)/Français (France): `fr-FR.php`
111 |
112 | ###Switching the front-end language
113 |
114 | In order to switch the front-end language, all that has to be done is a modification of one line in the `/commentia_dir/app/data/config.php` file. The lexicon locale, `COMMENTIA_LEX_LOCALE`, is always the name of the language file _without_ the `.php` extension.
115 |
116 | The name of the language file always adheres to the language tag according to the [RFC 5646](https://tools.ietf.org/html/rfc5646 'RFC 5646 standard') standard, with the extension `.php` appended to it.
117 |
118 | E.g., for American English, the line would read
119 |
120 | ```php
121 | define('COMMENTIA_LEX_LOCALE', 'en-US');
122 | ```
123 |
124 | And for German it would be
125 |
126 | ```php
127 | define('COMMENTIA_LEX_LOCALE', 'de-DE');
128 | ```
129 |
130 | A full list of languages and their locales/culture codes can be found [here](http://download1.parallels.com/SiteBuilder/Windows/docs/3.2/en_US/sitebulder-3.2-win-sdk-localization-pack-creation-guide/30801.htm 'List of Culture Codes').
131 |
132 | ### Creating a new language
133 |
134 | In order to create a new language, the `en-US.php` file has to be copied and renamed to the correct language for the target language, plus the `.php` extension in the same locale directory.
135 |
136 | For example, if the target language is Chinese (China), the language code would be `zh-CN`, so the new file would be called `zh-CN.php` accordingly.
137 |
138 | Open up the new language file and the `en-US.php` file side-by-side, and start translating. The format of each phrase is as follows: `define('PHRASE_IDENTIFIER', 'Localized phrase')`, where `PHRASE_IDENTIFIER` is an uppercase, underscore-separated string, clearly describing the meaning of a phrase, such as `COMMENT_INFO_COMMENT_BY`. Generally, the phrase identifier follows the pattern `CATEGORY_OBJECT`, e.g. `COMMENT_INFO` is the category and `COMMENT_BY` is the object. It is also why the phrases are grouped the way they are in the lexicon files. The localized phrase is just the translation.
139 |
140 | To use the newly created language file, read the previous section on [switching the front-end language](#switch-language).
141 |
142 | ## Readiness for production use
143 |
144 | Even though the system was tested thoroughly to not contain any bugs (please do open up issues if any are found), it did not undergo formal tests (e.g. PHPunit).
145 |
146 | Unit tests are planned for future releases of Commentia.
147 |
--------------------------------------------------------------------------------
/Commentia/assets/style.example.css:
--------------------------------------------------------------------------------
1 | /*! normalize.css v4.1.1 | MIT License | github.com/necolas/normalize.css */
2 |
3 |
4 | /**
5 | * 1. Change the default font family in all browsers (opinionated).
6 | * 2. Prevent adjustments of font size after orientation changes in IE and iOS.
7 | */
8 |
9 | html {
10 | font-family: sans-serif;
11 | /* 1 */
12 | -ms-text-size-adjust: 100%;
13 | /* 2 */
14 | -webkit-text-size-adjust: 100%;
15 | /* 2 */
16 | }
17 |
18 | /**
19 | * Remove the margin in all browsers (opinionated).
20 | */
21 |
22 | body {
23 | margin: 0;
24 | }
25 |
26 | /* HTML5 display definitions
27 | ========================================================================== */
28 |
29 |
30 | /**
31 | * Add the correct display in IE 9-.
32 | * 1. Add the correct display in Edge, IE, and Firefox.
33 | * 2. Add the correct display in IE.
34 | */
35 |
36 | article, aside, details,
37 | /* 1 */
38 |
39 | figcaption, figure, footer, header, main,
40 | /* 2 */
41 |
42 | menu, nav, section, summary {
43 | /* 1 */
44 | display: block;
45 | }
46 |
47 | /**
48 | * Add the correct display in IE 9-.
49 | */
50 |
51 | audio, canvas, progress, video {
52 | display: inline-block;
53 | }
54 |
55 | /**
56 | * Add the correct display in iOS 4-7.
57 | */
58 |
59 | audio:not([controls]) {
60 | display: none;
61 | height: 0;
62 | }
63 |
64 | /**
65 | * Add the correct vertical alignment in Chrome, Firefox, and Opera.
66 | */
67 |
68 | progress {
69 | vertical-align: baseline;
70 | }
71 |
72 | /**
73 | * Add the correct display in IE 10-.
74 | * 1. Add the correct display in IE.
75 | */
76 |
77 | template,
78 | /* 1 */
79 |
80 | [hidden] {
81 | display: none;
82 | }
83 |
84 | /* Links
85 | ========================================================================== */
86 |
87 |
88 | /**
89 | * 1. Remove the gray background on active links in IE 10.
90 | * 2. Remove gaps in links underline in iOS 8+ and Safari 8+.
91 | */
92 |
93 | a {
94 | background-color: transparent;
95 | /* 1 */
96 | -webkit-text-decoration-skip: objects;
97 | /* 2 */
98 | }
99 |
100 | /**
101 | * Remove the outline on focused links when they are also active or hovered
102 | * in all browsers (opinionated).
103 | */
104 |
105 | a:active, a:hover {
106 | outline-width: 0;
107 | }
108 |
109 | /* Text-level semantics
110 | ========================================================================== */
111 |
112 |
113 | /**
114 | * 1. Remove the bottom border in Firefox 39-.
115 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
116 | */
117 |
118 | abbr[title] {
119 | border-bottom: none;
120 | /* 1 */
121 | text-decoration: underline;
122 | /* 2 */
123 | text-decoration: underline dotted;
124 | /* 2 */
125 | }
126 |
127 | /**
128 | * Prevent the duplicate application of `bolder` by the next rule in Safari 6.
129 | */
130 |
131 | b, strong {
132 | font-weight: inherit;
133 | }
134 |
135 | /**
136 | * Add the correct font weight in Chrome, Edge, and Safari.
137 | */
138 |
139 | b, strong {
140 | font-weight: bolder;
141 | }
142 |
143 | /**
144 | * Add the correct font style in Android 4.3-.
145 | */
146 |
147 | dfn {
148 | font-style: italic;
149 | }
150 |
151 | /**
152 | * Correct the font size and margin on `h1` elements within `section` and
153 | * `article` contexts in Chrome, Firefox, and Safari.
154 | */
155 |
156 | h1 {
157 | font-size: 2em;
158 | margin: 0.67em 0;
159 | }
160 |
161 | /**
162 | * Add the correct background and color in IE 9-.
163 | */
164 |
165 | mark {
166 | background-color: #ff0;
167 | color: #000;
168 | }
169 |
170 | /**
171 | * Add the correct font size in all browsers.
172 | */
173 |
174 | small {
175 | font-size: 80%;
176 | }
177 |
178 | /**
179 | * Prevent `sub` and `sup` elements from affecting the line height in
180 | * all browsers.
181 | */
182 |
183 | sub, sup {
184 | font-size: 75%;
185 | line-height: 0;
186 | position: relative;
187 | vertical-align: baseline;
188 | }
189 | sub {
190 | bottom: -0.25em;
191 | }
192 | sup {
193 | top: -0.5em;
194 | }
195 |
196 | /* Embedded content
197 | ========================================================================== */
198 |
199 |
200 | /**
201 | * Remove the border on images inside links in IE 10-.
202 | */
203 |
204 | img {
205 | border-style: none;
206 | }
207 |
208 | /**
209 | * Hide the overflow in IE.
210 | */
211 |
212 | svg:not(:root) {
213 | overflow: hidden;
214 | }
215 |
216 | /* Grouping content
217 | ========================================================================== */
218 |
219 |
220 | /**
221 | * 1. Correct the inheritance and scaling of font size in all browsers.
222 | * 2. Correct the odd `em` font sizing in all browsers.
223 | */
224 |
225 | code, kbd, pre, samp {
226 | font-family: monospace, monospace;
227 | /* 1 */
228 | font-size: 1em;
229 | /* 2 */
230 | }
231 |
232 | /**
233 | * Add the correct margin in IE 8.
234 | */
235 |
236 | figure {
237 | margin: 1em 40px;
238 | }
239 |
240 | /**
241 | * 1. Add the correct box sizing in Firefox.
242 | * 2. Show the overflow in Edge and IE.
243 | */
244 |
245 | hr {
246 | box-sizing: content-box;
247 | /* 1 */
248 | height: 0;
249 | /* 1 */
250 | overflow: visible;
251 | /* 2 */
252 | }
253 |
254 | /* Forms
255 | ========================================================================== */
256 |
257 |
258 | /**
259 | * 1. Change font properties to `inherit` in all browsers (opinionated).
260 | * 2. Remove the margin in Firefox and Safari.
261 | */
262 |
263 | button, input, select, textarea {
264 | font: inherit;
265 | /* 1 */
266 | margin: 0;
267 | /* 2 */
268 | }
269 |
270 | /**
271 | * Restore the font weight unset by the previous rule.
272 | */
273 |
274 | optgroup {
275 | font-weight: bold;
276 | }
277 |
278 | /**
279 | * Show the overflow in IE.
280 | * 1. Show the overflow in Edge.
281 | */
282 |
283 | button, input {
284 | /* 1 */
285 | overflow: visible;
286 | }
287 |
288 | /**
289 | * Remove the inheritance of text transform in Edge, Firefox, and IE.
290 | * 1. Remove the inheritance of text transform in Firefox.
291 | */
292 |
293 | button, select {
294 | /* 1 */
295 | text-transform: none;
296 | }
297 |
298 | /**
299 | * 1. Prevent a WebKit bug where (2) destroys native `audio` and `video`
300 | * controls in Android 4.
301 | * 2. Correct the inability to style clickable types in iOS and Safari.
302 | */
303 |
304 | button, html [type="button"],
305 | /* 1 */
306 |
307 | [type="reset"], [type="submit"] {
308 | -webkit-appearance: button;
309 | /* 2 */
310 | }
311 |
312 | /**
313 | * Remove the inner border and padding in Firefox.
314 | */
315 |
316 | button::-moz-focus-inner, [type="button"]::-moz-focus-inner, [type="reset"]::-moz-focus-inner, [type="submit"]::-moz-focus-inner {
317 | border-style: none;
318 | padding: 0;
319 | }
320 |
321 | /**
322 | * Restore the focus styles unset by the previous rule.
323 | */
324 |
325 | button:-moz-focusring, [type="button"]:-moz-focusring, [type="reset"]:-moz-focusring, [type="submit"]:-moz-focusring {
326 | outline: 1px dotted ButtonText;
327 | }
328 |
329 | /**
330 | * Change the border, margin, and padding in all browsers (opinionated).
331 | */
332 |
333 | fieldset {
334 | border: 1px solid #c0c0c0;
335 | margin: 0 2px;
336 | padding: 0.35em 0.625em 0.75em;
337 | }
338 |
339 | /**
340 | * 1. Correct the text wrapping in Edge and IE.
341 | * 2. Correct the color inheritance from `fieldset` elements in IE.
342 | * 3. Remove the padding so developers are not caught out when they zero out
343 | * `fieldset` elements in all browsers.
344 | */
345 |
346 | legend {
347 | box-sizing: border-box;
348 | /* 1 */
349 | color: inherit;
350 | /* 2 */
351 | display: table;
352 | /* 1 */
353 | max-width: 100%;
354 | /* 1 */
355 | padding: 0;
356 | /* 3 */
357 | white-space: normal;
358 | /* 1 */
359 | }
360 |
361 | /**
362 | * Remove the default vertical scrollbar in IE.
363 | */
364 |
365 | textarea {
366 | overflow: auto;
367 | }
368 |
369 | /**
370 | * 1. Add the correct box sizing in IE 10-.
371 | * 2. Remove the padding in IE 10-.
372 | */
373 |
374 | [type="checkbox"], [type="radio"] {
375 | box-sizing: border-box;
376 | /* 1 */
377 | padding: 0;
378 | /* 2 */
379 | }
380 |
381 | /**
382 | * Correct the cursor style of increment and decrement buttons in Chrome.
383 | */
384 |
385 | [type="number"]::-webkit-inner-spin-button, [type="number"]::-webkit-outer-spin-button {
386 | height: auto;
387 | }
388 |
389 | /**
390 | * 1. Correct the odd appearance in Chrome and Safari.
391 | * 2. Correct the outline style in Safari.
392 | */
393 |
394 | [type="search"] {
395 | -webkit-appearance: textfield;
396 | /* 1 */
397 | outline-offset: -2px;
398 | /* 2 */
399 | }
400 |
401 | /**
402 | * Remove the inner padding and cancel buttons in Chrome and Safari on OS X.
403 | */
404 |
405 | [type="search"]::-webkit-search-cancel-button, [type="search"]::-webkit-search-decoration {
406 | -webkit-appearance: none;
407 | }
408 |
409 | /**
410 | * Correct the text style of placeholders in Chrome, Edge, and Safari.
411 | */
412 |
413 | ::-webkit-input-placeholder {
414 | color: inherit;
415 | opacity: 0.54;
416 | }
417 |
418 | /**
419 | * 1. Correct the inability to style clickable types in iOS and Safari.
420 | * 2. Change font properties to `inherit` in Safari.
421 | */
422 |
423 | ::-webkit-file-upload-button {
424 | -webkit-appearance: button;
425 | /* 1 */
426 | font: inherit;
427 | /* 2 */
428 | }
429 |
430 | /* Test page specific styles */
431 |
432 | body {
433 | font-family: Avenir, "Open Sans", Helvetica, sans-ferif;
434 | margin: 4px;
435 | }
436 |
--------------------------------------------------------------------------------
/Commentia/assets/commentia-new.js:
--------------------------------------------------------------------------------
1 | /* Allow namespace commentia to be defined elsewhere (mainly for passing config vars) */
2 | window.commentia = window.commentia || {};
3 | /* If no APIURL provided, default to root api.php */
4 | window.commentia.APIURL = window.commentia.APIURL || "/api.php";
5 |
6 | function ajax(url, callback, data, x) {
7 | try {
8 | x = new(this.XMLHttpRequest || ActiveXObject)('MSXML2.XMLHTTP.3.0');
9 | x.open(data ? 'POST' : 'GET', url, 1);
10 | x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
11 | x.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
12 | x.onreadystatechange = function () {
13 | x.readyState > 3 && callback && callback(x.responseText, x);
14 | };
15 | x.send(data)
16 | } catch (e) {
17 | window.console && console.log(e);
18 | }
19 | };
20 |
21 | function httpRequest() {
22 | var http_request;
23 | try {
24 | http_request = new XMLHttpRequest();
25 | return http_request;
26 | } catch (ignore) {
27 | try {
28 | http_request = new ActiveXObject("Msxml2.XMLHTTP");
29 | return http_request;
30 | } catch (ignore) {
31 | try {
32 | http_request = new ActiveXObject("Microsoft.XMLHTTP");
33 | return http_request;
34 | } catch (ignore) {
35 | alert("Your browser broke!");
36 | return false;
37 | }
38 | }
39 | }
40 | }
41 |
42 | function refreshComments() {
43 | var comments_section = document.getElementById("comments-container");
44 | var url = window.commentia.APIURL + "?action=display";
45 |
46 | console.log("GET request to: " + url);
47 |
48 | ajax(window.commentia.APIURL + "?action=display", function (response) {
49 | comments_section.innerHTML = response;
50 | });
51 | }
52 |
53 | function showReplyArea(caller) {
54 | var comment = findCommentRoot(caller);
55 | var ucid = comment.getAttribute('data-ucid');
56 |
57 | var reply_area_id = 'reply-area-' + ucid;
58 | var reply_box_id = 'reply-box-' + ucid;
59 | var reply_button_id = 'reply-button-' + ucid;
60 | var cancel_button_id = 'edit-cancel-button-' + ucid;
61 |
62 | if (!document.getElementById(reply_area_id)) {
63 | var reply_area = document.createElement('div');
64 | reply_area.setAttribute('id', reply_area_id);
65 |
66 | var reply_box = document.createElement('textarea');
67 | reply_box.setAttribute('id', reply_box_id);
68 | reply_box.setAttribute('oninput', "autoGrow(this);");
69 | reply_area.appendChild(reply_box);
70 |
71 | var reply_button = document.createElement('button');
72 | reply_button.innerHTML = 'reply';
73 | reply_button.setAttribute('id', reply_button_id);
74 | reply_button.setAttribute('onclick', 'postReply(this);');
75 | reply_area.appendChild(reply_button);
76 |
77 | var cancel_button = document.createElement('button');
78 | cancel_button.innerHTML = 'cancel';
79 | cancel_button.setAttribute('id', cancel_button_id);
80 | cancel_button.setAttribute('onclick', 'hideReplyArea(this);');
81 | reply_area.appendChild(cancel_button);
82 | comment.getElementsByClassName('commentia-comment__reply-area')[0].appendChild(reply_area);
83 | }
84 | document.getElementById(reply_area_id).style.display = "block";
85 | }
86 |
87 | function hideReplyArea(caller) {
88 | var comment = findCommentRoot(caller);
89 | var ucid = comment.getAttribute('data-ucid');
90 | var reply_area_id = 'reply-area-' + ucid;
91 |
92 | document.getElementById(reply_area_id).style.display = "none";
93 | }
94 |
95 | function findCommentRoot(el) {
96 | while ((el = el.parentNode) && !el.hasAttribute('data-ucid'));
97 | return el;
98 | }
99 |
100 | function postReply(caller) {
101 | var comment = findCommentRoot(caller);
102 | var ucid = comment.getAttribute('data-ucid');
103 | var reply_path = comment.getAttribute('data-reply-path');
104 | var reply_box_id = 'reply-box-' + ucid;
105 |
106 | var comments_section = document.getElementById("comments-container");
107 | var content = encodeURI(document.getElementById(reply_box_id).value);
108 | var params = "action=reply&content=" + content + "&reply_path=" + reply_path;
109 |
110 | console.log("POST request to: " + window.commentia.APIURL + " with params: " + params);
111 |
112 | ajax(window.commentia.APIURL, function () {
113 | refreshComments();
114 | }, params);
115 | }
116 |
117 | function postNewComment(caller) {
118 | var comment_box_id = 'comment-box';
119 |
120 | var comments_section = document.getElementById("comments-container");
121 | var content = encodeURI(document.getElementById(comment_box_id).value);
122 | var params = "action=postNewComment&content=" + content + "&username=user0";
123 |
124 | console.log("POST request to: " + window.commentia.APIURL + " with params: " + params);
125 |
126 | ajax(window.commentia.APIURL, function () {
127 | refreshComments();
128 | }, params);
129 |
130 | http_request.open("POST", window.commentia.APIURL, true);
131 | http_request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
132 | http_request.send(params);
133 | }
134 |
135 | function deleteComment(caller) {
136 | var url = window.commentia.APIURL + "?action=getPhrase&phrase=DIALOGS_DELETE_COMMENT";
137 |
138 | var http_request = httpRequest();
139 |
140 | var dialog_msg;
141 |
142 | console.log("GET request to: " + url);
143 |
144 | http_request.onreadystatechange = function() {
145 |
146 | if (http_request.readyState === 4) {
147 | dialog_msg = http_request.responseText;
148 | http_request = null;
149 | }
150 | }
151 |
152 | http_request.open("GET", url, false);
153 | http_request.send();
154 |
155 | if (confirm(dialog_msg)) {
156 | var comments_section = document.getElementById("comments-container");
157 | var comment = findCommentRoot(caller);
158 | var ucid = comment.getAttribute('data-ucid');
159 | var reply_path = comment.getAttribute('data-reply-path');
160 |
161 | var params = "action=delete&ucid=" + ucid + "&reply_path=" + reply_path;
162 |
163 | console.log("POST request to: " + window.commentia.APIURL + " with params: " + params);
164 |
165 | var http_request = httpRequest();
166 |
167 | http_request.onreadystatechange = function() {
168 |
169 | if (http_request.readyState === 4) {
170 | refreshComments();
171 | http_request = null;
172 | }
173 | }
174 |
175 | http_request.open("POST", window.commentia.APIURL, true);
176 | http_request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
177 | http_request.send(params);
178 | }
179 | }
180 |
181 | function showEditArea(caller) {
182 | var comment = findCommentRoot(caller);
183 | var ucid = comment.getAttribute('data-ucid');
184 | var reply_path = comment.getAttribute('data-reply-path');
185 |
186 | var edit_area_id = 'edit-area-' + ucid;
187 | var edit_box_id = 'edit-box-' + ucid;
188 | var edit_button_id = 'edit-button-' + ucid;
189 | var cancel_button_id = 'edit-cancel-button-' + ucid;
190 |
191 | if (!document.getElementById(edit_area_id)) {
192 | var edit_area = document.createElement('div');
193 | edit_area.setAttribute('id', edit_area_id);
194 |
195 | var edit_box = document.createElement('textarea');
196 | edit_box.setAttribute('id', edit_box_id);
197 | edit_box.setAttribute('oninput', "autoGrow(this);");
198 |
199 | var url = window.commentia.APIURL + "?action=getCommentMarkdown&ucid=" + ucid + "&reply_path=" + reply_path;
200 | var http_request = httpRequest();
201 |
202 | console.log("GET request to: " + url);
203 |
204 | http_request.onreadystatechange = function() {
205 |
206 | if (http_request.readyState === 4) {
207 | comment.getElementsByClassName('commentia-comment__content')[0].style.display = "none";
208 | comment.getElementsByClassName('commentia-comment__edit-area')[0].appendChild(edit_area);
209 | comment.getElementsByClassName('commentia-comment__edit-area')[0].style.display = "block";
210 | edit_box.innerHTML = http_request.responseText;
211 | document.getElementById(edit_box_id).style.height = document.getElementById(edit_box_id).scrollHeight + 'px';
212 | http_request = null;
213 | }
214 | }
215 |
216 | http_request.open("GET", url, true);
217 | http_request.send();
218 |
219 | edit_area.appendChild(edit_box);
220 |
221 | var edit_button = document.createElement('button');
222 | edit_button.innerHTML = 'edit';
223 | edit_button.setAttribute('id', edit_button_id);
224 | edit_button.setAttribute('onclick', 'editComment(this);');
225 | edit_area.appendChild(edit_button);
226 |
227 | var cancel_button = document.createElement('button');
228 | cancel_button.innerHTML = 'cancel';
229 | cancel_button.setAttribute('id', cancel_button_id);
230 | cancel_button.setAttribute('onclick', 'hideEditArea(this);');
231 | edit_area.appendChild(cancel_button);
232 | } else {
233 | comment.getElementsByClassName('commentia-comment__content')[0].style.display = "none";
234 | comment.getElementsByClassName('commentia-comment__edit-area')[0].style.display = "block";
235 | }
236 | }
237 |
238 | function hideEditArea(caller) {
239 | var comment = findCommentRoot(caller);
240 |
241 | comment.getElementsByClassName('commentia-comment__edit-area')[0].style.display = "none";
242 | comment.getElementsByClassName('commentia-comment__content')[0].style.display = "block";
243 | }
244 |
245 | function editComment(caller) {
246 | var comment = findCommentRoot(caller);
247 | var ucid = comment.getAttribute('data-ucid');
248 | var edit_box = document.getElementById('edit-box-' + ucid);
249 | var reply_path = comment.getAttribute('data-reply-path');
250 |
251 | var params = "action=edit&content=" + encodeURI(edit_box.value) + "&ucid=" + ucid + "&reply_path=" + reply_path;
252 |
253 | var http_request = httpRequest();
254 |
255 | console.log("POST request to: " + window.commentia.APIURL + " with params: " + params);
256 |
257 | http_request.onreadystatechange = function() {
258 |
259 | if (http_request.readyState === 4) {
260 | refreshComments();
261 | http_request = null;
262 | }
263 | }
264 |
265 | http_request.open("POST", window.commentia.APIURL, true);
266 | http_request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
267 | http_request.send(params);
268 | }
269 |
270 | function autoGrow(caller) {
271 | caller.style.height = "auto";
272 | caller.style.height = (caller.scrollHeight + 5) + "px";
273 | }
274 |
--------------------------------------------------------------------------------
/Commentia/assets/commentia.js:
--------------------------------------------------------------------------------
1 | /* Allow namespace commentia to be defined elsewhere (mainly for passing config vars) */
2 | window.commentia = window.commentia || {};
3 | /* If no APIURL provided, default to root api.php */
4 | window.commentia.APIURL = window.commentia.APIURL || "/api.php";
5 |
6 | function httpRequest() {
7 | try {
8 | http_request = new XMLHttpRequest();
9 | return http_request;
10 | } catch (e) {
11 | try {
12 | http_request = new ActiveXObject("Msxml2.XMLHTTP");
13 | return http_request;
14 | } catch (e) {
15 | try {
16 | http_request = new ActiveXObject("Microsoft.XMLHTTP");
17 | return http_request;
18 | } catch (e) {
19 | alert("Your browser broke!");
20 | return false;
21 | }
22 | }
23 | }
24 | }
25 |
26 | function refreshComments() {
27 | var comments_section = document.getElementById("commentia__comments-container");
28 | var url = window.commentia.APIURL + "?action=display";
29 |
30 | var http_request = httpRequest();
31 |
32 | console.log("GET request to: " + url);
33 |
34 | http_request.onreadystatechange = function() {
35 |
36 | if (http_request.readyState == 4) {
37 | comments_section.innerHTML = http_request.responseText;
38 | http_request = null;
39 | }
40 | }
41 |
42 | http_request.open("GET", url, true);
43 | http_request.send();
44 | }
45 |
46 | function showReplyArea(caller) {
47 | var comment = findCommentRoot(caller);
48 | var ucid = comment.getAttribute('data-ucid');
49 |
50 | var reply_area_id = 'reply-area-' + ucid;
51 | var reply_box_id = 'reply-box-' + ucid;
52 | var reply_button_id = 'reply-button-' + ucid;
53 | var cancel_button_id = 'edit-cancel-button-' + ucid;
54 |
55 | if (!document.getElementById(reply_area_id)) {
56 | var reply_area = document.createElement('div');
57 | reply_area.setAttribute('id', reply_area_id);
58 |
59 | var reply_box = document.createElement('textarea');
60 | reply_box.setAttribute('id', reply_box_id);
61 | reply_box.setAttribute('oninput', "autoGrow(this);");
62 | reply_area.appendChild(reply_box);
63 |
64 | var reply_button = document.createElement('button');
65 | reply_button.innerHTML = 'reply';
66 | reply_button.setAttribute('id', reply_button_id);
67 | reply_button.setAttribute('onclick', 'postReply(this);');
68 | reply_area.appendChild(reply_button);
69 |
70 | var cancel_button = document.createElement('button');
71 | cancel_button.innerHTML = 'cancel';
72 | cancel_button.setAttribute('id', cancel_button_id);
73 | cancel_button.setAttribute('onclick', 'hideReplyArea(this);');
74 | reply_area.appendChild(cancel_button);
75 | comment.getElementsByClassName('commentia-comment__reply-area')[0].appendChild(reply_area);
76 | }
77 | document.getElementById(reply_area_id).style.display = "block";
78 | }
79 |
80 | function hideReplyArea(caller) {
81 | var comment = findCommentRoot(caller);
82 | var ucid = comment.getAttribute('data-ucid');
83 | var reply_area_id = 'reply-area-' + ucid;
84 |
85 | document.getElementById(reply_area_id).style.display = "none";
86 | }
87 |
88 | function findCommentRoot(el) {
89 | while ((el = el.parentNode) && !el.hasAttribute('data-ucid'));
90 | return el;
91 | }
92 |
93 | function postReply(caller) {
94 | var comment = findCommentRoot(caller);
95 |
96 | var ucid = comment.getAttribute('data-ucid');
97 | var reply_box_id = 'reply-box-' + ucid;
98 |
99 | var comments_section = document.getElementById("commentia__comments-container");
100 | var content = encodeURI(document.getElementById(reply_box_id).value);
101 | var params = "action=reply&content=" + content + "&childof=" + ucid + "&username=user0";
102 |
103 | console.log("POST request to: " + window.commentia.APIURL + " with params: " + params);
104 |
105 | var http_request = httpRequest();
106 |
107 | http_request.onreadystatechange = function() {
108 |
109 | if (http_request.readyState == 4) {
110 | refreshComments();
111 | http_request = null;
112 | }
113 | }
114 |
115 | http_request.open("POST", window.commentia.APIURL, true);
116 | http_request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
117 | http_request.send(params);
118 | }
119 |
120 | function postNewComment(caller) {
121 | var comment_box_id = 'comment-box';
122 |
123 | var comments_section = document.getElementById("commentia__comments-container");
124 | var content = encodeURI(document.getElementById(comment_box_id).value);
125 | var params = "action=postNewComment&content=" + content;
126 |
127 | var http_request = httpRequest();
128 |
129 | console.log("POST request to: " + window.commentia.APIURL + " with params: " + params);
130 |
131 | http_request.onreadystatechange = function() {
132 |
133 | if (http_request.readyState == 4) {
134 | refreshComments();
135 | http_request = null;
136 | }
137 | }
138 |
139 | http_request.open("POST", window.commentia.APIURL, true);
140 | http_request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
141 | http_request.send(params);
142 | }
143 |
144 | function deleteComment(caller) {
145 | var url = window.commentia.APIURL + "?action=getPhrase&phrase=DIALOGS_DELETE_COMMENT";
146 |
147 | var http_request = httpRequest();
148 |
149 | console.log("GET request to: " + url);
150 |
151 | http_request.onreadystatechange = function() {
152 |
153 | if (http_request.readyState == 4) {
154 | dialog_msg = http_request.responseText;
155 | http_request = null;
156 | }
157 | }
158 |
159 | http_request.open("GET", url, false);
160 | http_request.send();
161 |
162 | if (confirm(dialog_msg)) {
163 | var comments_section = document.getElementById("commentia__comments-container");
164 | var comment = findCommentRoot(caller);
165 | var ucid = comment.getAttribute('data-ucid');
166 | var reply_path = comment.getAttribute('data-reply-path');
167 |
168 | var params = "action=delete&ucid=" + ucid + "&reply_path=" + reply_path;
169 |
170 | console.log("POST request to: " + window.commentia.APIURL + " with params: " + params);
171 |
172 | var http_request = httpRequest();
173 |
174 | http_request.onreadystatechange = function() {
175 |
176 | if (http_request.readyState == 4) {
177 | refreshComments();
178 | http_request = null;
179 | }
180 | }
181 |
182 | http_request.open("POST", window.commentia.APIURL, true);
183 | http_request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
184 | http_request.send(params);
185 | }
186 | }
187 |
188 | function updateRating(caller, direction) {
189 | var comments_section = document.getElementById("commentia__comments-container");
190 | var comment = findCommentRoot(caller);
191 |
192 | var ucid = comment.getAttribute('data-ucid');
193 |
194 | var params = "action=updateRating&ucid=" + ucid + "&direction=" + direction;
195 |
196 | console.log("POST request to: " + window.commentia.APIURL + " with params: " + params);
197 |
198 | var http_request = httpRequest();
199 |
200 | http_request.onreadystatechange = function() {
201 |
202 | if (http_request.readyState == 4) {
203 | refreshComments();
204 | http_request = null;
205 | }
206 | }
207 |
208 | http_request.open("POST", window.commentia.APIURL, true);
209 | http_request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
210 | http_request.send(params);
211 | }
212 |
213 | function showEditArea(caller) {
214 | var comment = findCommentRoot(caller);
215 | var ucid = comment.getAttribute('data-ucid');
216 | var reply_path = comment.getAttribute('data-reply-path');
217 |
218 | var edit_area_id = 'edit-area-' + ucid;
219 | var edit_box_id = 'edit-box-' + ucid;
220 | var edit_button_id = 'edit-button-' + ucid;
221 | var cancel_button_id = 'edit-cancel-button-' + ucid;
222 |
223 | if (!document.getElementById(edit_area_id)) {
224 | var edit_area = document.createElement('div');
225 | edit_area.setAttribute('id', edit_area_id);
226 |
227 | var edit_box = document.createElement('textarea');
228 | edit_box.setAttribute('id', edit_box_id);
229 | edit_box.setAttribute('oninput', "autoGrow(this);");
230 |
231 | var url = window.commentia.APIURL + "?action=getCommentMarkdown&ucid=" + ucid + "&reply_path=" + reply_path;
232 | var http_request = httpRequest();
233 |
234 | console.log("GET request to: " + url);
235 |
236 | http_request.onreadystatechange = function() {
237 |
238 | if (http_request.readyState == 4) {
239 | comment.getElementsByClassName('commentia-comment__content')[0].style.display = "none";
240 | comment.getElementsByClassName('commentia-comment__edit-area')[0].appendChild(edit_area);
241 | comment.getElementsByClassName('commentia-comment__edit-area')[0].style.display = "block";
242 | edit_box.innerHTML = http_request.responseText;
243 | document.getElementById(edit_box_id).style.height = document.getElementById(edit_box_id).scrollHeight + 'px';
244 | http_request = null;
245 | }
246 | }
247 |
248 | http_request.open("GET", url, true);
249 | http_request.send();
250 |
251 | edit_area.appendChild(edit_box);
252 |
253 | var edit_button = document.createElement('button');
254 | edit_button.innerHTML = 'edit';
255 | edit_button.setAttribute('id', edit_button_id);
256 | edit_button.setAttribute('onclick', 'editComment(this);');
257 | edit_area.appendChild(edit_button);
258 |
259 | var cancel_button = document.createElement('button');
260 | cancel_button.innerHTML = 'cancel';
261 | cancel_button.setAttribute('id', cancel_button_id);
262 | cancel_button.setAttribute('onclick', 'hideEditArea(this);');
263 | edit_area.appendChild(cancel_button);
264 | } else {
265 | comment.getElementsByClassName('commentia-comment__content')[0].style.display = "none";
266 | comment.getElementsByClassName('commentia-comment__edit-area')[0].style.display = "block";
267 | }
268 | }
269 |
270 | function hideEditArea(caller) {
271 | var comment = findCommentRoot(caller);
272 |
273 | comment.getElementsByClassName('commentia-comment__edit-area')[0].style.display = "none";
274 | comment.getElementsByClassName('commentia-comment__content')[0].style.display = "block";
275 | }
276 |
277 | function editComment(caller) {
278 | var comment = findCommentRoot(caller);
279 | var ucid = comment.getAttribute('data-ucid');
280 | var edit_box = document.getElementById('edit-box-' + ucid);
281 | var reply_path = comment.getAttribute('data-reply-path');
282 |
283 | var params = "action=edit&content=" + encodeURI(edit_box.value) + "&ucid=" + ucid + "&reply_path=" + reply_path;
284 |
285 | var http_request = httpRequest();
286 |
287 | console.log("POST request to: " + window.commentia.APIURL + " with params: " + params);
288 |
289 | http_request.onreadystatechange = function() {
290 |
291 | if (http_request.readyState == 4) {
292 | refreshComments();
293 | http_request = null;
294 | }
295 | }
296 |
297 | http_request.open("POST", window.commentia.APIURL, true);
298 | http_request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
299 | http_request.send(params);
300 | }
301 |
302 | function autoGrow(caller) {
303 | caller.style.height = "auto";
304 | caller.style.height = (caller.scrollHeight + 5) + "px";
305 | }
306 |
--------------------------------------------------------------------------------
/Commentia/app/Commentia/Models/Comments.php:
--------------------------------------------------------------------------------
1 | HTML converters, checks for DB file (if none, will create one), arranges and assigns content of DB file to array for PHP use.
33 | *
34 | * @param string $pageid ID of page on which comments should be displayed
35 | */
36 | public function __construct($pageid)
37 | {
38 | $this->pageid = $pageid;
39 | $this->db = new DBHandler(COMMENTIA_DB);
40 | $this->md_to_html = new Parsedown();
41 | $this->html_to_md = new Converter();
42 |
43 | $stmt = $this->db->prepare('SELECT * FROM comments WHERE pageid = :pageid');
44 | $stmt->bindValue(':pageid', $pageid);
45 | $res = $stmt->execute();
46 |
47 | while ($r = $res->fetchArray(SQLITE3_ASSOC)) {
48 | $this->comments['ucid-'.$r['ucid']] = $r;
49 | $this->comments['ucid-'.$r['ucid']]['children'] = json_decode($this->comments['ucid-'.$r['ucid']]['children']);
50 | }
51 | }
52 |
53 | /**
54 | * Returns the comments as HTML markup.
55 | *
56 | * @param bool $is_ajax_request If set to true, only inner comments without container will be outputted for use with JS's innerHTML
57 | *
58 | * @return string Comments HTML markup
59 | */
60 | public function displayComments()
61 | {
62 | global $lexicon;
63 | $lexicon = new Lexicon(COMMENTIA_LEX_LOCALE);
64 |
65 | global $roles;
66 | $roles = new Roles();
67 |
68 | /**
69 | * Iterates through each comment recursively, and appends it to the var holding the HTML markup.
70 | *
71 | * @param array $comment An array containing all the comment data
72 | */
73 |
74 | foreach ($this->comments as $comment) {
75 | if (isset($this->comments['ucid-'.$comment['ucid']])) {
76 | $this->renderCommentView($comment['ucid']);
77 | unset($this->comments['ucid-'.$comment['ucid']]);
78 | }
79 | }
80 |
81 | if ($_SESSION['__COMMENTIA__']['member_is_logged_in']) {
82 | $this->html_output .= (''."\n");
83 | }
84 |
85 | return $this->html_output;
86 | }
87 |
88 | /**
89 | * Outputs the comment markup (including children).
90 | *
91 | * @param string $ucid UCID of comment (unique comment ID)
92 | */
93 | public function renderCommentView($ucid) {
94 | global $lexicon;
95 | global $roles;
96 |
97 | $members = new Members();
98 |
99 | $comment_data = $this->comments['ucid-'.$ucid];
100 | $this->html_output .= (''."\n");
140 |
141 | }
142 |
143 | /**
144 | * Creates a new comment/reply.
145 | *
146 | * @param string $content Text/content of the comment to be created
147 | * @param int $childof The UCID of the parent comment if reply
148 | */
149 | public function createNewComment($content, $childof)
150 | {
151 | $res = $this->db->query('SELECT MAX(ucid) FROM comments');
152 | $last_ucid = $res->fetchArray(SQLITE3_ASSOC)['MAX(ucid)'];
153 |
154 | if (is_numeric($last_ucid)) {
155 | $ucid = $last_ucid + 1;
156 | } else {
157 | $ucid = 0;
158 | }
159 |
160 | $content = $this->md_to_html->text(htmlspecialchars(urldecode($content)));
161 | $timestamp = date(DateTime::ISO8601);
162 | $creator_username = $_SESSION['__COMMENTIA__']['member_username'];
163 | $is_deleted = 0;
164 | $children = '';
165 | $pageid = $this->pageid;
166 |
167 | $stmt = $this->db->prepare('INSERT INTO comments (ucid, content, timestamp, creator_username, is_deleted, children, pageid) VALUES (
168 | :ucid, :content, :timestamp, :creator_username, :is_deleted, :children, :pageid);');
169 |
170 | $stmt->bindValue(':ucid', $ucid);
171 | $stmt->bindValue(':content', $content);
172 | $stmt->bindValue(':timestamp', $timestamp);
173 | $stmt->bindValue(':creator_username', $creator_username);
174 | $stmt->bindValue(':is_deleted', $is_deleted);
175 | $stmt->bindValue(':children', $children);
176 | $stmt->bindValue(':pageid', $pageid);
177 |
178 | $stmt->execute();
179 |
180 | if (isset($childof)) {
181 | $stmt = $this->db->prepare('SELECT children FROM comments WHERE ucid = :childof');
182 | $stmt->bindValue(':childof', $childof);
183 | $res = $stmt->execute();
184 |
185 | $children = json_decode($res->fetchArray(SQLITE3_ASSOC)['children']);
186 | $children[] = $ucid;
187 | $children_new = json_encode($children);
188 |
189 | $stmt = $this->db->prepare('UPDATE comments SET children = :children_new WHERE ucid = :childof');
190 | $stmt->bindValue(':children_new', $children_new);
191 | $stmt->bindValue(':childof', $childof);
192 | $stmt->execute();
193 | }
194 | }
195 |
196 | /**
197 | * Updates comment content with supplied value.
198 | *
199 | * @param int $ucid Unique comment ID
200 | * @param string $content New content to be put into comment text
201 | */
202 | public function editComment($ucid, $content)
203 | {
204 | $content = $this->md_to_html->text(htmlspecialchars(urldecode($content)));
205 | $timestamp = date(DateTime::ISO8601);
206 | $stmt = $this->db->prepare('UPDATE comments SET content = :content, timestamp = :timestamp WHERE ucid = :ucid');
207 | $stmt->bindValue(':content', $content);
208 | $stmt->bindValue(':timestamp', $timestamp);
209 | $stmt->bindValue(':ucid', $ucid);
210 | $stmt->execute();
211 | }
212 |
213 | /**
214 | * Sets is_deleted flag of comment to true, overwrites content with the string '[[deleted]]'.
215 | *
216 | * @param int $ucid Unique comment ID
217 | */
218 | public function deleteComment($ucid)
219 | {
220 | $content = $this->md_to_html->text(htmlspecialchars(urldecode('_[[deleted]]_')));
221 | $timestamp = date(DateTime::ISO8601);
222 | $stmt = $this->db->prepare('UPDATE comments SET content = :content, timestamp = :timestamp, is_deleted = 1 WHERE ucid = :ucid');
223 | $stmt->bindValue(':content', $content);
224 | $stmt->bindValue(':timestamp', $timestamp);
225 | $stmt->bindValue(':ucid', $ucid);
226 | $stmt->execute();
227 | }
228 |
229 | public function updateRating($ucid, $direction) {
230 | $rating = (int) $this->comments['ucid-'.$ucid]['rating'];
231 | if ($direction === 'up') {
232 | file_put_contents('updaterating', ' ');
233 | $rating += 1;
234 | }
235 | if ($direction === 'down') {
236 | $rating -= 1;
237 | }
238 |
239 | $stmt = $this->db->prepare('UPDATE comments SET rating = :rating WHERE ucid = :ucid');
240 | $stmt->bindValue(':ucid', $ucid);
241 | $stmt->bindValue(':rating', $rating);
242 | $stmt->execute();
243 | }
244 |
245 | /**
246 | * Used by the frontend to get the comment's Markdown for editing.
247 | *
248 | * @param int $ucid Unique comment ID
249 | */
250 | public function getCommentMarkdown($ucid)
251 | {
252 | $comment_post_path = &$this->comments["ucid-$ucid"];
253 | $comment_md = $this->html_to_md->parseString($comment_post_path['content']);
254 | return $comment_md;
255 | }
256 |
257 | /**
258 | * Returns a specified entry for a comment.
259 | *
260 | * @param int $ucid Unique comment ID
261 | * @param string $entry The entry that should be retrieved (e.g. creator_username, content, is_deleted etc.)
262 | *
263 | * @return mixed Returns the wanted entry
264 | */
265 | public function getCommentData($ucid, $entry)
266 | {
267 | $stmt = $this->db->prepare('SELECT * FROM comments WHERE ucid = :ucid');
268 | $stmt->bindValue(':ucid', $ucid);
269 | $res = $stmt->execute();
270 |
271 | $result = $res->fetchArray(SQLITE3_ASSOC)[$entry];
272 | return $result;
273 | }
274 | }
275 |
--------------------------------------------------------------------------------
/Commentia/app/Commentia/Models/Members.php:
--------------------------------------------------------------------------------
1 | db = new DBHandler(COMMENTIA_DB);
27 |
28 | if (!isset($_SESSION['__COMMENTIA__']['member_username'])) {
29 | $_SESSION['__COMMENTIA__']['member_is_logged_in'] = false;
30 | }
31 |
32 | if (!isset($_SESSION['__COMMENTIA__']['login_error_msg'])) {
33 | $_SESSION['__COMMENTIA__']['login_error_msg'] = '';
34 | }
35 | }
36 |
37 | /**
38 | * Generates a thumbnail for the member avatar (used on signup) and returns its path.
39 | *
40 | * @param string $src The path to the full image
41 | * @param int $dest_w Desired width of the thumbnail
42 | * @param int $dest_h Desired height of the thumbnail
43 | *
44 | * @return string The path to the newly generated thumbnail
45 | */
46 | private function generateAvatarThumbnail($src, $dest_w, $dest_h)
47 | {
48 | if (!file_exists($src)) {
49 | throw new InvalidArgumentException('File "'.$src.'" not found.');
50 | }
51 |
52 | switch (strtolower(pathinfo($src, PATHINFO_EXTENSION))) {
53 | case 'jpeg':
54 | case 'jpg':
55 | $unoriented_source_image = imagecreatefromjpeg($src);
56 | $source_image = imagerotate($unoriented_source_image, array_values([0, 0, 0, 180, 0, 0, -90, 0, 90])[@exif_read_data($src)['Orientation'] ?: 0], 0);
57 | break;
58 |
59 | case 'png':
60 | $source_image = imagecreatefrompng($src);
61 | break;
62 |
63 | case 'gif':
64 | $source_image = imagecreatefromgif($src);
65 | break;
66 |
67 | default:
68 | throw new InvalidArgumentException('File "'.$src.'" is not valid jpg, png or gif image.');
69 | break;
70 | }
71 |
72 | $orig_w = imagesx($source_image);
73 | $orig_h = imagesy($source_image);
74 |
75 | $w_ratio = ($dest_w / $orig_w);
76 | $h_ratio = ($dest_h / $orig_h);
77 |
78 | if ($orig_w > $orig_h) {
79 | $crop_w = round($orig_w * $h_ratio);
80 | $crop_h = $dest_h;
81 | } elseif ($orig_w < $orig_h) {
82 | $crop_h = round($orig_h * $w_ratio);
83 | $crop_w = $dest_w;
84 | } else {
85 | $crop_w = $dest_w;
86 | $crop_h = $dest_h;
87 | }
88 |
89 | $dest_image = imagecreatetruecolor($dest_w, $dest_h);
90 |
91 | $image_file_no_ext = (COMMENTIA_AVATAR_DIR.uniqid('avatar_', true));
92 |
93 | switch (strtolower(pathinfo($src, PATHINFO_EXTENSION))) {
94 | case 'jpeg':
95 | case 'jpg':
96 | $image_file = $image_file_no_ext.'.jpg';
97 | imagecopyresampled($dest_image, $source_image, 0, 0, 0, 0, $crop_w, $crop_h, $orig_w, $orig_h);
98 | imagejpeg($dest_image, $image_file);
99 | break;
100 |
101 | case 'png':
102 | $image_file = $image_file_no_ext.'.png';
103 | imagealphablending($dest_image, false);
104 | imagesavealpha($dest_image, true);
105 | imagecopyresampled($dest_image, $source_image, 0, 0, 0, 0, $crop_w, $crop_h, $orig_w, $orig_h);
106 | imagepng($dest_image, $image_file);
107 | break;
108 |
109 | case 'gif':
110 | $image_file = $image_file_no_ext.'.gif';
111 | imagealphablending($dest_image, false);
112 | $trans_index = imagecolortransparent($source_image);
113 | if ($trans_index >= 0) {
114 | $trans_index = imagecolortransparent($source_image);
115 | $trans_col = imagecolorsforindex($source_image, $trans_index);
116 | $trans_index = imagecolorallocatealpha(
117 | $dest_image,
118 | $trans_col['red'],
119 | $trans_col['green'],
120 | $trans_col['blue'],
121 | 127
122 | );
123 | imagefill($dest_image, 0, 0, $trans_index);
124 | }
125 |
126 | imagecopyresampled($dest_image, $source_image, 0, 0, 0, 0, $crop_w, $crop_h, $orig_w, $orig_h);
127 |
128 | if ($trans_index >= 0) {
129 | imagecolortransparent($dest_image, $trans_index);
130 | for ($y = 0; $y < $crop_h; ++$y) {
131 | for ($x = 0; $x < $crop_w; ++$x) {
132 | if (((imagecolorat($dest_image, $x, $y) >> 24) & 0x7F) >= 100) {
133 | imagesetpixel(
134 | $dest_image,
135 | $x,
136 | $y,
137 | $trans_index
138 | );
139 | }
140 | }
141 | }
142 | }
143 |
144 | imagetruecolortopalette($dest_image, true, 255);
145 | imagegif($dest_image, $image_file);
146 | break;
147 | default:
148 | throw new InvalidArgumentException('File "'.$src.'" is not valid jpg, png or gif image.');
149 | break;
150 | }
151 |
152 | if ($src !== 'app/data/avatars/placeholder/avatar_placeholder.jpg') {
153 | unlink($src);
154 | }
155 |
156 | return basename($image_file);
157 | }
158 |
159 | /**
160 | * Writes new account data to DB.
161 | *
162 | * @param string $username The username used to log in
163 | * @param string $password The password to be hashed and used for log in
164 | * @param string $email The email of the Members
165 | * @param string $role The role of the new member (e.g. member, admin, guest etc.)
166 | * @param string $avatar_file The path to the member's avatar image thumbnail
167 | */
168 | private function createNewMember($username, $password, $email, $role, $avatar_file)
169 | {
170 | $password_hash = password_hash($password, PASSWORD_DEFAULT);
171 | $avatar_file = $this->generateAvatarThumbnail($avatar_file, 150, 150);
172 | $is_banned = false;
173 | $member_since = date(DateTime::ISO8601);
174 |
175 | $stmt = $this->db->prepare('INSERT INTO members (username, password_hash, email, avatar_file, is_banned, role, member_since) VALUES (
176 | :username, :password_hash, :email, :avatar_file, :is_banned, :role, :member_since);');
177 |
178 | $stmt->bindValue(':username', $username);
179 | $stmt->bindValue(':password_hash', $password_hash);
180 | $stmt->bindValue(':email', $email);
181 | $stmt->bindValue(':avatar_file', $avatar_file);
182 | $stmt->bindValue(':is_banned', $is_banned);
183 | $stmt->bindValue(':role', $role);
184 | $stmt->bindValue(':member_since', $member_since);
185 |
186 | $stmt->execute();
187 |
188 | return true;
189 | }
190 |
191 | /**
192 | * Performs checking and validation prior to creating member account.
193 | *
194 | * @param string $username The username used to log in
195 | * @param string $password The password used to log in
196 | * @param string $retyped_password Compared to the password to prevent mistyping
197 | * @param string $email The email of the new Members
198 | * @param array $avatar_file An array containing information on the image uploaded for avatar generation
199 | */
200 | public function signUpMember($username, $password, $retyped_password, $email, $avatar_file)
201 | {
202 | $error_encountered = false;
203 |
204 | if (empty($username) || $username === '') {
205 | $_SESSION['__COMMENTIA__']['sign_up_error_msg'] .= ERROR_SIGN_UP_MISSING_USERNAME."
\n";
206 | $error_encountered = true;
207 | } elseif ($this->getMemberData($username, 'username')) {
208 | $_SESSION['__COMMENTIA__']['sign_up_error_msg'] .= ERROR_SIGN_UP_USERNAME_TAKEN."
\n";
209 | $error_encountered = true;
210 | }
211 |
212 | if (empty($password) || $password === '') {
213 | $_SESSION['__COMMENTIA__']['sign_up_error_msg'] .= ERROR_SIGN_UP_MISSING_PASSWORD."
\n";
214 | $error_encountered = true;
215 | } elseif ($password !== $retyped_password) {
216 | $_SESSION['__COMMENTIA__']['sign_up_error_msg'] .= ERROR_SIGN_UP_PASSWORD_MISMATCH."
\n";
217 | $error_encountered = true;
218 | }
219 |
220 | if (strlen($password) < COMMENTIA_MIN_PASSWORD_LEN) {
221 | $_SESSION['__COMMENTIA__']['sign_up_error_msg'] .= ERROR_SIGN_UP_PASSWORD_INSECURE."
\n";
222 | $error_encountered = true;
223 | }
224 |
225 | if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
226 | $_SESSION['__COMMENTIA__']['sign_up_error_msg'] .= ERROR_SIGN_UP_INVALID_EMAIL."
\n";
227 | $error_encountered = true;
228 | }
229 |
230 | if (!empty($_FILES['avatar_img']['tmp_name'])) {
231 | $uploads_dir = COMMENTIA_AVATAR_DIR;
232 | $upload_img_tmp = $_FILES['avatar_img']['tmp_name'];
233 | $is_image = getimagesize($_FILES['avatar_img']['tmp_name']) ? true : false;
234 | $filesize = $_FILES['avatar_img']['size'];
235 |
236 | if (($_FILES['avatar_img']['error'] == UPLOAD_ERR_OK) && $is_image && ($filesize <= 2097152)) {
237 | $name = $_FILES['avatar_img']['name'];
238 | $tmp_img_store_name = "$uploads_dir".uniqid()."_$name";
239 | move_uploaded_file($upload_img_tmp, $tmp_img_store_name);
240 | $avatar_file = $tmp_img_store_name;
241 | } else {
242 | $_SESSION['__COMMENTIA__']['sign_up_error_msg'] .= ERROR_SIGN_UP_AVATAR_UPLOAD."
\n";
243 | $error_encountered = true;
244 | }
245 | } else {
246 | $avatar_file = 'app/data/avatars/placeholder/avatar_placeholder.jpg';
247 | }
248 |
249 | if (!$error_encountered) {
250 | $role = 'member';
251 |
252 | $member_created = $this->createNewMember($username, $password, $email, $role, $avatar_file);
253 |
254 | if ($member_created) {
255 | $_SESSION['__COMMENTIA__']['sign_up_error_msg'] .= NOTICE_SIGN_UP_SUCCESS."
\n";
256 | }
257 | }
258 | header('Location:'.$_SESSION['__COMMENTIA__']['log_in_page']);
259 | }
260 |
261 | /**
262 | * Deletes existing members.
263 | *
264 | * @param string $username The username used to log in
265 | */
266 | public function deleteMember($username)
267 | {
268 | $stmt = $this->db->prepare('DELETE FROM members WHERE username = :username');
269 | $stmt->bindValue(':username', $username);
270 | $stmt->execute();
271 | }
272 |
273 | /**
274 | * Retrieves an entry for an existing member and returns it.
275 | *
276 | * @param string $username The username used for log in
277 | * @param string $entry The entry to be returned (e.g. avatar_file, is_banned etc.)
278 | *
279 | * @return [type] [description]
280 | */
281 | public function getMemberData($username, $entry)
282 | {
283 | $stmt = $this->db->prepare('SELECT * FROM members WHERE username = :username');
284 | $stmt->bindValue(':username', $username);
285 | $res = $stmt->execute();
286 |
287 | $entry_data = $res->fetchArray(SQLITE3_ASSOC)[$entry];
288 |
289 | if ($entry === 'avatar_file') {
290 | $result = ABS_PATH_PREFIX.COMMENTIA_AVATAR_DIR.$entry_data;
291 | } else {
292 | $result = $entry_data;
293 | }
294 |
295 | return $result;
296 | }
297 |
298 | /**
299 | * Sets a certain entry in the member data of an existing member to a specified value.
300 | *
301 | * @param string $username The username used for log in
302 | * @param string $entry The entry to be set (e.g. member_since, role, is_banned)
303 | * @param [type] $data [description]
304 | */
305 | public function setMemberData($username, $entry, $data)
306 | {
307 | $stmt = $this->db->prepare('UPDATE members SET :entry = :data WHERE username = :username');
308 | $stmt->bindValue(':entry', $entry);
309 | $stmt->bindValue(':data', $data);
310 | $stmt->bindValue(':username', $username);
311 | $stmt->execute();
312 | }
313 |
314 | /**
315 | * Logs member into account.
316 | *
317 | * @param string $username The username used for login
318 | * @param string $password The password used for login
319 | */
320 | public function loginMember($username, $password)
321 | {
322 | if (isset($username) && isset($password)) {
323 | if (password_verify($password, $this->getMemberData($username, 'password_hash'))) {
324 | $_SESSION['__COMMENTIA__']['member_is_logged_in'] = true;
325 | $_SESSION['__COMMENTIA__']['member_username'] = $username;
326 | $_SESSION['__COMMENTIA__']['member_role'] = $this->getMemberData($username, 'role');
327 | // TODO: Login success message localization
328 | $_SESSION['__COMMENTIA__']['login_error_msg'] = 'LOGIN_AUTH_SUCCESS';
329 | session_regenerate_id();
330 | header('Location:'.$_SESSION['__COMMENTIA__']['log_in_page']);
331 | } else {
332 | $_SESSION['__COMMENTIA__']['member_is_logged_in'] = false;
333 | // TODO: Login fail message localization
334 | $_SESSION['__COMMENTIA__']['login_error_msg'] = 'LOGIN_AUTH_FAIL';
335 | session_regenerate_id();
336 | header('Location:'.$_SESSION['__COMMENTIA__']['log_in_page']);
337 | }
338 | }
339 | }
340 |
341 | /**
342 | * Logs currently logged in member out of their account.
343 | */
344 | public function logoutMember()
345 | {
346 | $_SESSION['__COMMENTIA__']['member_is_logged_in'] = false;
347 | unset($_SESSION['__COMMENTIA__']['member_username']);
348 | unset($_SESSION['__COMMENTIA__']['member_role']);
349 | $_SESSION['__COMMENTIA__']['login_error_msg'] = 'LOGOUT';
350 | session_regenerate_id();
351 | header('Location:'.$_SESSION['__COMMENTIA__']['log_in_page']);
352 | }
353 |
354 | /**
355 | * Generates and returns the HTML to display the auth form.
356 | *
357 | * @return string HTML markup of the auth form
358 | */
359 | public function displayAuthForm()
360 | {
361 | $html = ''.TITLES_AUTH_FORM.'
';
362 | if ($_SESSION['__COMMENTIA__']['member_is_logged_in']) {
363 | $html .= '
364 |
368 | Logged in as '.$_SESSION['__COMMENTIA__']['member_username'].' with role '.$_SESSION['__COMMENTIA__']['member_role'].'
';
369 | } else {
370 | $html .= '
371 | ';
387 | }
388 | $html .= ''.$_SESSION['__COMMENTIA__']['login_error_msg'].'
';
389 | $_SESSION['__COMMENTIA__']['login_error_msg'] = '';
390 |
391 | $isSecure = false;
392 |
393 | $this->setLoginPage();
394 |
395 | return $html;
396 | }
397 |
398 | /**
399 | * Generates and returns the HTML to display the sign up form.
400 | *
401 | * @return string HTML markup of the sign up form
402 | */
403 | public function displaySignUpForm()
404 | {
405 | $sign_up_error_msg = isset($_SESSION['__COMMENTIA__']['sign_up_error_msg']) ? $_SESSION['__COMMENTIA__']['sign_up_error_msg'] : '';
406 | $html = ''.TITLES_SIGN_UP_FORM.'
';
407 | $html .= '
408 |
436 | '.$sign_up_error_msg.'
';
437 |
438 | $_SESSION['__COMMENTIA__']['sign_up_error_msg'] = '';
439 |
440 | $this->setLoginPage();
441 |
442 | return $html;
443 | }
444 |
445 | /**
446 | * When called, sets login page to a session variable (because $_SERVER['REQUEST_URI'] is very unreliable).
447 | */
448 | public function setLoginPage()
449 | {
450 | $isSecure = false;
451 |
452 | if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') {
453 | $isSecure = true;
454 | } elseif (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https' || !empty($_SERVER['HTTP_X_FORWARDED_SSL']) && $_SERVER['HTTP_X_FORWARDED_SSL'] == 'on') {
455 | $isSecure = true;
456 | }
457 |
458 | $REQUEST_PROTOCOL = $isSecure ? 'https' : 'http';
459 |
460 | $_SESSION['__COMMENTIA__']['log_in_page'] = $REQUEST_PROTOCOL.'://'.$_SERVER['HTTP_HOST'].$_SERVER['PHP_SELF'];
461 | }
462 | }
463 |
--------------------------------------------------------------------------------