├── comment-gravatar.js ├── comment-textarea.js ├── comment.css ├── comment.php └── extension.ini /comment-gravatar.js: -------------------------------------------------------------------------------- 1 | // Comments extension, https://github.com/GiovanniSalmeri/yellow-comments 2 | 3 | document.addEventListener("DOMContentLoaded", function() { 4 | document.getElementById('from').addEventListener("change", fillOutFrom); 5 | }); 6 | var md5 = function(){for(var m=[],l=0;64>l;)m[l]=0|4294967296*Math.abs(Math.sin(++l));return function(c){var e,g,f,a,h=[];c=unescape(encodeURI(c));for(var b=c.length,k=[e=1732584193,g=-271733879,~e,~g],d=0;d<=b;)h[d>>2]|=(c.charCodeAt(d)||128)<<8*(d++%4);h[c=16*(b+8>>6)+14]=8*b;for(d=0;da;)b=[f=b[3],(e=b[1]|0)+((f=b[0]+[e&(g=b[2])|~e&f,f&e|~f&g,e^g^f,g^(e|~f)][b=a>>4]+(m[a]+(h[[a,5*a+1,3*a+5,7*a][b]%16+d]|0)))<<(b=[7,12,17,22,5,9,14,20,4,11,16,23,6,10,15,21][4*b+a++%4])|f>>>32-b),e,g];for(a=4;a;)k[--a]=k[a]+b[a]}for(c="";32>a;)c+=(k[a>>3]>>4*(1^a++&7)&15).toString(16);return c}}(); 7 | function fillOutFrom() { 8 | var PATT = /^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]+$/ 9 | var BASE = "https://en.gravatar.com/"; 10 | var email = this.value.trim().toLowerCase(); 11 | var hash = md5(PATT.test(email) ? email : ""); 12 | var size = document.getElementById("gravatar").width; 13 | var def = document.getElementById("gravatar").getAttribute("data-default"); 14 | document.getElementById("gravatar").src = BASE+"avatar/"+hash+"?s="+size+"&d="+def; 15 | var jsonFile = new XMLHttpRequest(); 16 | jsonFile.onreadystatechange = function() { 17 | if (this.readyState == 4 && this.status == 200) { 18 | document.getElementById("name").value = JSON.parse(this.responseText).entry[0].displayName; 19 | document.getElementById("comment").focus(); 20 | } else { 21 | document.getElementById("name").value =""; 22 | } 23 | } 24 | jsonFile.open("get", BASE+hash+".json", true); 25 | jsonFile.send(); 26 | } 27 | -------------------------------------------------------------------------------- /comment-textarea.js: -------------------------------------------------------------------------------- 1 | // Comment extension, https://github.com/GiovanniSalmeri/yellow-comment 2 | 3 | "use strict"; 4 | // https://stackoverflow.com/questions/454202/creating-a-textarea-with-auto-resize 5 | window.addEventListener("load", function() { 6 | var tx = document.getElementsByTagName('textarea'); 7 | for (var i = 0; i < tx.length; i++) { 8 | tx[i].setAttribute('style', 'height:' + tx[i].scrollHeight + 'px;overflow-y:hidden;resize:none'); 9 | tx[i].addEventListener("input", OnInput); 10 | } 11 | function OnInput() { 12 | this.style.height = 'auto'; 13 | this.style.height = this.scrollHeight + 'px'; 14 | if (this.nextSibling.className == 'comment-charcount') { 15 | this.nextSibling.firstChild.data = this.value.length + ' / ' + this.maxLength; 16 | } 17 | } 18 | }); 19 | -------------------------------------------------------------------------------- /comment.css: -------------------------------------------------------------------------------- 1 | /* Comment extension, https://github.com/GiovanniSalmeri/yellow-comment */ 2 | 3 | .comment { 4 | clear:both; 5 | margin-bottom: 1em; 6 | padding: 0.5em; 7 | background-color: #f7f7f7; 8 | border-radius: 3px; 9 | } 10 | .comment-icon { 11 | float: left; 12 | margin-right: 1em; 13 | } 14 | .comment-message { 15 | display: none; 16 | } 17 | input:focus, textarea:focus { 18 | background-color: #FFFFCC; 19 | } 20 | .comment-name, .comment-date { 21 | display: inline; 22 | } 23 | .comment-name { 24 | font-weight: bold; 25 | } 26 | .comment-info { 27 | font-size: 0.85em; 28 | } 29 | form div { 30 | margin-bottom: 0.5em; 31 | } 32 | .separate { 33 | margin-bottom: 2em; 34 | } 35 | .comment-main { 36 | display: table-cell; 37 | } 38 | .comment-icon { 39 | display: table-cell; 40 | } 41 | .comment-form .form-control { 42 | width: 100%; 43 | -webkit-box-sizing: border-box; 44 | -moz-box-sizing: border-box; 45 | box-sizing: border-box; 46 | } 47 | .incomplete, .invalid, .error { 48 | color: red; 49 | font-weight: bold; 50 | } 51 | .none, .done, .closed { 52 | font-weight: bold; 53 | } 54 | .comment-charcount { 55 | display: block; 56 | text-align: right; 57 | } 58 | -------------------------------------------------------------------------------- /comment.php: -------------------------------------------------------------------------------- 1 | yellow = $yellow; 15 | $this->yellow->system->setDefault("commentModerator", ""); 16 | $this->yellow->system->setDefault("commentDirectory", "comment/"); 17 | $this->yellow->system->setDefault("commentAutoPublish", "0"); 18 | $this->yellow->system->setDefault("commentMaxSize", "5000"); 19 | $this->yellow->system->setDefault("commentTimeout", "0"); 20 | $this->yellow->system->setDefault("commentOpening", "30"); 21 | $this->yellow->system->setDefault("commentAuthorNotification", "1"); 22 | $this->yellow->system->setDefault("commentSpamFilter", "href=|url="); 23 | $this->yellow->system->setDefault("commentIconSize", "80"); 24 | $this->yellow->system->setDefault("commentIconGravatar", "0"); 25 | $this->yellow->system->setDefault("commentIconGravatarDefault", "mp"); 26 | $this->yellow->system->setDefault("commentReverseOrder", "0"); 27 | $this->yellow->system->setDefault("commentConsent", "0"); 28 | $this->yellow->language->setDefaults(array( 29 | "Language: en", 30 | "CommentCommentList: Comments:", 31 | "CommentStatusNone: Interested to discuss? Leave a comment.", 32 | "CommentStatusClosed: Comments are closed.", 33 | "CommentStatusDone: Thanks for your feedback.", 34 | "CommentPrivacy: Your email will not be published nor shared with anyone.", 35 | "CommentGravatar: Please use the service Gravatar if you would like a photo beside your name.", 36 | "CommentMarkdown: In your text you can use *italic*, **bold**, [links](http://example.org).", 37 | "CommentManual: These comments are moderated and published manually as soon as possible.", 38 | "CommentPublished: Your comment has been published. Thank you very much!", 39 | "CommentHoneypot: Please leave this field blank:", 40 | "CommentName: Name:", 41 | "CommentEmail: Email:", 42 | "CommentMessage: Message:", 43 | "CommentConsent: I consent that this website stores my message.", 44 | "CommentButton: Send message", 45 | "CommentStatusIncomplete: Please fill out all fields.", 46 | "CommentStatusInvalid: Please enter a valid email.", 47 | "CommentStatusError: Your comment could not be sent, please try again later.", 48 | "CommentStatusToolong: Your comment is too long, please be more concise...", 49 | "Language: de", 50 | "CommentCommentList: Kommentare:", 51 | "CommentStatusNone: Interesse an einer Diskussion? Schreibe einen Kommentar.", 52 | "CommentStatusClosed: Kommentare sind geschlossen.", 53 | "CommentStatusDone: Danke für die Rückmeldung.", 54 | "CommentPrivacy: Die E-Mail-Adresse wird nicht veröffentlicht noch an Dritte weitergegeben.", 55 | "CommentGravatar: Benutze Gravatar, wenn ein Bild neben deinem Namen erscheinen soll.", 56 | "CommentMarkdown: In deinem Text kannst du *Kursivschrift*, **Fettschrift** und [Links](http://example.org) verwenden.", 57 | "CommentManual: Der Kommentar wird moderiert und so bald wie möglich freigeschaltet.", 58 | "CommentPublished: Dein Kommentar wurde veröffentlicht. Danke schön!", 59 | "CommentHoneypot: Dieses Feld bitte leer lassen:", 60 | "CommentName: Name:", 61 | "CommentEmail: E-Mail:", 62 | "CommentMessage: Nachricht:", 63 | "CommentConsent: Ich stimme zu, dass diese Webseite meine Nachricht speichert.", 64 | "CommentButton: Nachricht absenden", 65 | "CommentStatusIncomplete: Bitte alle Felder ausfüllen.", 66 | "CommentStatusInvalid: Bitte eine gültige E-Mail angeben.", 67 | "CommentStatusError: Nachricht konnte nicht versandt werden, versuche es später erneut.", 68 | "CommentStatusToolong: Dein Kommentar ist zu lang, bitte fasse dich besser zusammen...", 69 | "Language: fr", 70 | "CommentCommentList: Commentaires:", 71 | "CommentStatusNone: Envie de discuter? Laissez un commentaire.", 72 | "CommentStatusClosed: Les commentaires sont fermés.", 73 | "CommentStatusDone: Merci de votre participation.", 74 | "CommentPrivacy: L'adresse email ne sera pas publiée ni partagée avec autrui.", 75 | "CommentGravatar: Si vous voulez une photo à côté de votre nom, utilisez le service Gravatar.", 76 | "CommentMarkdown: Dans votre texte vous pouvez utiliser l'*italique*, le **gras**, les [liens](http://example.org).", 77 | "CommentManual: Les commentaires sont modérés et publiés manuellement dès que possible.", 78 | "CommentPublished: Votre commentaire a été publié. Merci de votre participation!", 79 | "CommentHoneypot: Laissez ce champ vide, s'il vous plaît:", 80 | "CommentName: Nom:", 81 | "CommentEmail: Email:", 82 | "CommentMessage: Message:", 83 | "CommentConsent: Je consens à ce que ce site stocke mon message.", 84 | "CommentButton: Envoyer le message", 85 | "CommentStatusIncomplete: S'il vous plaît, veuillez remplir tous les champs.", 86 | "CommentStatusInvalid: S'il vous plaît, veuillez entrer une adresse email valide.", 87 | "CommentStatusError: Votre message n'a pas pu être envoyé, réessayez plus tard s'il vous plaît.", 88 | "CommentStatusToolong: Votre commentaire est trop long, veuillez être plus concis...", 89 | "Language: it", 90 | "CommentCommentList: Commenti:", 91 | "CommentStatusNone: Vuoi contribuire alla discussione? Lascia un commento!", 92 | "CommentStatusClosed: I commenti sono chiusi.", 93 | "CommentStatusDone: Grazie per il contributo!", 94 | "CommentPrivacy: Il tuo indirizzo di posta elettronica non sarà né pubblicato né ceduto a terzi.", 95 | "CommentGravatar: Usa il servizio Gravatar se vuoi che sia mostrata una foto accanto al tuo nome.", 96 | "CommentMarkdown: Nel testo puoi usare il *corsivo*, il **neretto**, i [collegamenti](http://example.org).", 97 | "CommentManual: I commenti sono moderati e pubblicati manualmente appena possibile.", 98 | "CommentPublished: Il tuo commento è stato pubblicato. Grazie per il contributo!", 99 | "CommentHoneypot: Lascia questo campo vuoto:", 100 | "CommentName: Nome:", 101 | "CommentEmail: Email:", 102 | "CommentMessage: Messaggio:", 103 | "CommentConsent: Acconsento alla registrazione del mio messaggio in questo sito.", 104 | "CommentButton: Invia il messaggio", 105 | "CommentStatusIncomplete: Compila per favore tutti i campi.", 106 | "CommentStatusInvalid: Inserisci per favore un indirizzo email valido.", 107 | "CommentStatusError: C'è stato un problema nell'invio del messaggio. Riprova per favore più tardi.", 108 | "CommentStatusToolong: Il commento è troppo lungo, prova ad essere più conciso...", 109 | )); 110 | } 111 | 112 | // Handle page content element 113 | public function onParseContentElement($page, $name, $text, $attributes, $type) { 114 | $output = null; 115 | if ($name=="comment" && ($type=="block" || $type=="inline") && !preg_match("/exclude/i", $page->get("comment"))) { 116 | list($opening) = $this->yellow->toolbox->getTextArguments($text); 117 | if ($opening == "") $opening = $this->yellow->system->get("commentOpening"); 118 | $this->areOpen = time()-$opening*86400 < strtotime($this->yellow->page->get("published")) || !$opening; 119 | 120 | $this->lockComments($this->yellow->page, false); 121 | $this->loadComments(); 122 | $this->processSend(); 123 | if ($this->yellow->page->get("status") == "done") { // post/redirect/get 124 | setcookie("status", "done"); 125 | $this->yellow->page->status(303, $this->yellow->page->getLocation(true)); 126 | } 127 | $this->unlockComments(); 128 | $iconSize = $this->yellow->system->get("commentIconSize"); 129 | $maxSize = $this->yellow->system->get("commentMaxSize"); 130 | $reverseOrder = $this->yellow->system->get("commentReverseOrder"); 131 | 132 | if ($reverseOrder) $output .= $this->buildForm(); 133 | $output .= "
\n"; 134 | $output .= "

" . $this->yellow->language->getText("commentCommentList") . " " . $this->getCommentCount() . "

\n"; 135 | foreach ($reverseOrder ? array_reverse($this->comments) : $this->comments as $comment) { 136 | if ($comment["meta"]["published"] !== "No") { 137 | $output .= "
\n"; 138 | $output .= "
getUserIcon($comment["meta"]["from"]) . "\" width=\"" . $iconSize . "\" height=\"" . $iconSize . "\" alt=\"Image\" />
\n"; 139 | $output .= "
\n"; 140 | $output .= "
" . htmlspecialchars($comment["meta"]["name"]) . "
\n"; 141 | $output .= "
".$this->yellow->language->getDateFormatted(strtotime($comment["meta"]["created"]), $this->yellow->language->getText("coreDateFormatLong")) . "
\n"; 142 | $output .= "
" . $this->toHtml($comment["text"]) . "
\n"; 143 | $output .= "
\n"; 144 | $output .= "
\n"; 145 | } 146 | } 147 | $output .= "
\n"; 148 | if (!$reverseOrder) $output .= $this->buildForm(); 149 | } 150 | return $output; 151 | } 152 | 153 | // Handle page extra data 154 | public function onParsePageExtra($page, $name) { 155 | $output = null; 156 | if ($name=="header") { 157 | $assetLocation = $this->yellow->system->get("coreServerBase").$this->yellow->system->get("coreAssetLocation"); 158 | $output .= "\n"; 159 | $output .= "\n"; 160 | if ($this->yellow->system->get("commentIconGravatar")) $output .= "\n"; 161 | } 162 | if ($name=="comment") { 163 | $output = $this->onParseContentElement($page, "comment", "", "", "block"); 164 | } 165 | return $output; 166 | } 167 | 168 | // Return Email 169 | function getEmail() { 170 | return $this->yellow->system->isExisting("commentModerator") ? $this->yellow->system->get("commentModerator") : $this->yellow->system->get("email"); 171 | } 172 | 173 | // Return file name from page object 174 | function getCommentFileName($page) { 175 | return dirname($page->fileName) . "/" . $this->yellow->system->get("commentDirectory") . basename($page->fileName); 176 | } 177 | 178 | // Lock comments file 179 | function lockComments($page, $forceOpen) { 180 | if ($this->fileHandle) return; 181 | $fileName = $this->getCommentFileName($page); 182 | if ($forceOpen) @mkdir(dirname($fileName)); 183 | if (file_exists($fileName) || $forceOpen) $this->fileHandle = @fopen($fileName, "c+"); 184 | if ($this->fileHandle) flock($this->fileHandle, LOCK_EX); 185 | } 186 | 187 | // Unlock comments file 188 | function unlockComments() { 189 | if (!$this->fileHandle) return; 190 | flock($this->fileHandle, LOCK_UN); 191 | fclose($this->fileHandle); 192 | unset($this->fileHandle); 193 | } 194 | 195 | // Load comments 196 | function loadComments() { 197 | $this->comments = []; 198 | if (!$this->fileHandle) return; 199 | $length = fstat($this->fileHandle)['size']; 200 | $contents = array_slice(explode("\n\n---\n", str_replace("\r\n", "\n", $length>0 ? fread($this->fileHandle, $length) : "")), 1); 201 | foreach ($contents as $content) { 202 | if (preg_match("/^(.+?)\n+---\n+(.*)/s", $content, $parts)) { 203 | $comment = []; 204 | foreach (explode("\n", $parts[1]) as $line) { 205 | if (preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches) && $matches[1] && $matches[2]) { 206 | $comment["meta"][lcfirst($matches[1])] = $matches[2]; 207 | } 208 | } 209 | if (isset($comment["meta"]["created"])) { 210 | $this->yellow->page->setLastModified(strtotime($comment["meta"]["created"])); 211 | } 212 | $comment["text"] = trim($parts[2]); 213 | $comment["text"] = preg_replace("/^-(-{3,})$/m", "$1", $comment["text"]); // revert safety substitution 214 | $this->comments[] = $comment; 215 | } 216 | } 217 | } 218 | 219 | // Save comments 220 | function saveComments() { 221 | $status = "send"; 222 | $this->lockComments($this->yellow->page, true); 223 | $timeout = time()-$this->yellow->system->get("commentTimeout")*86400; 224 | $content = "---\nTitle: Comment\nStatus: unlisted\n---\n"; 225 | foreach ($this->comments as $comment) { 226 | if ($comment["meta"]["published"] !== "No" || $timeout < strtotime($comment["meta"]["created"]) || $this->yellow->system->get("commentTimeout") == 0) { 227 | $content .= "\n\n---\n"; 228 | foreach ($comment["meta"] as $key=>$value) { 229 | $content .= ucfirst($key). ": " . $value . "\n"; 230 | } 231 | $content .= "---\n"; 232 | $content .= $comment["text"] . "\n"; 233 | } 234 | } 235 | if ($this->fileHandle) { 236 | rewind($this->fileHandle); 237 | fwrite($this->fileHandle, $content); 238 | ftruncate($this->fileHandle, ftell($this->fileHandle)); 239 | } else { 240 | $status = "error"; 241 | } 242 | return $status; 243 | } 244 | 245 | // Build comment form 246 | private function buildForm() { 247 | $iconSize = $this->yellow->system->get("commentIconSize"); 248 | $maxSize = $this->yellow->system->get("commentMaxSize"); 249 | 250 | $output = null; 251 | if ($this->yellow->toolbox->getCookie("status")=="done") { 252 | setcookie("status", "", 1); 253 | $this->yellow->page->set("status", "done"); 254 | } 255 | $output .= "
\n"; 256 | if ($this->yellow->page->get("status") != "done" && $this->areOpen) { 257 | $output .= "

yellow->page->getHtml("status") . "\">" . $this->yellow->language->getTextHtml("commentStatus".ucfirst($this->yellow->page->get("status"))) . "

\n"; 258 | $output .= "
yellow->page->getLocation(true) . "#form\" method=\"post\">\n"; 259 | if ($this->yellow->system->get("commentIconGravatar")) { 260 | $output .= "
getUserIcon($this->yellow->page->get("status") == "invalid" ? "" : $this->yellow->page->getRequest("from")) . "\" width=\"" . $iconSize . "\" height=\"" . $iconSize . "\" data-default=\"" . rawurlencode($this->yellow->system->get("commentIconGravatarDefault")) . "\" alt=\"Image\" />
\n"; 261 | } else { 262 | $output .= "
getUserIcon($this->yellow->page->getRequest("from")) . "\" width=\"" . $iconSize . "\" height=\"" . $iconSize . "\" alt=\"Image\" />
\n"; 263 | } 264 | $output .= "
\n"; 265 | $output .= "

yellow->page->getRequestHtml("from") . "\" />
\n"; 266 | $output .= "

yellow->page->getRequestHtml("name") . "\" />
\n"; 267 | $output .= "

\n"; 268 | $output .= "

0 / " . $maxSize . "
\n"; 269 | $output .= ""; 270 | $output .= $this->yellow->system->get("commentConsent") ? "
yellow->page->isRequest("consent") ? " checked=\"checked\"" : "") . ">
\n" : ""; 271 | $output .= "
\n"; 272 | $output .= "\n"; 273 | $output .= "yellow->language->getTextHtml("commentButton") . "\" class=\"btn contact-btn\" />\n"; 274 | $output .= "
\n"; 275 | $output .= "
\n"; 276 | $output .= "
\n"; 277 | $output .= "

"; 278 | $output .= $this->yellow->language->getText("commentPrivacy") . " "; 279 | $output .= $this->yellow->system->get("commentIconGravatar") ? $this->yellow->language->getText("commentGravatar") . " " : ""; 280 | $output .= $this->yellow->language->getText("commentMarkdown") . " "; 281 | $output .= !$this->yellow->system->get("commentAutoPublish") ? $this->yellow->language->getText("commentManual") : ""; 282 | $output .= "

\n"; 283 | } else { 284 | $output .= "

yellow->page->getHtml("status") . "\">" . $this->yellow->language->getTextHtml("commentStatus".ucfirst($this->yellow->page->get("status"))) . "

\n"; 285 | } 286 | return $output; 287 | } 288 | 289 | // Build comment from input 290 | function buildComment() { 291 | $comment = []; 292 | $comment["meta"]["name"] = filter_var(trim($this->yellow->page->getRequest("name")), FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW); 293 | $comment["meta"]["from"] = filter_var(trim($this->yellow->page->getRequest("from")), FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW); 294 | $comment["meta"]["created"] = date("Y-m-d H:i"); 295 | $comment["meta"]["published"] = $this->yellow->system->get("commentAutoPublish") ? $comment["meta"]["created"] : "No"; 296 | $comment["meta"]["uid"] = md5($this->yellow->toolbox->getServer("REMOTE_ADDR").uniqid()); 297 | $comment["meta"]["aid"] = md5($this->yellow->toolbox->getServer("REMOTE_ADDR").uniqid()); 298 | $comment["text"] = str_replace("\r\n", "\n", trim($this->yellow->page->getRequest("comment"))); 299 | $comment["text"] = preg_replace("/^-{3,}$/m", "-$0", $comment["text"]); // safety substitution 300 | return $comment; 301 | } 302 | 303 | // Verify comment for safe use 304 | function verifyComment($comment) { 305 | $name = $comment["meta"]["name"]; 306 | $from = $comment["meta"]["from"]; 307 | $text = $comment["text"]; 308 | $consent = $this->yellow->page->getRequest("consent"); 309 | $spamFilter = $this->yellow->system->get("commentSpamFilter"); 310 | if (is_string_empty($name) || is_string_empty($from) || is_string_empty($text) || (is_string_empty($consent) && $this->yellow->system->get("commentConsent"))) { 311 | return "incomplete"; 312 | } elseif (!is_string_empty($from) && !filter_var($from, FILTER_VALIDATE_EMAIL)) { 313 | return "invalid"; 314 | } elseif (!is_string_empty($text) && preg_match('/'.str_replace(['\\', '/'], ['\\\\', '\\/'], $spamFilter).'/i', $text)) { 315 | return "error"; 316 | } elseif (!is_string_empty($this->yellow->page->getRequest("message"))) { 317 | return "error"; // honeypot 318 | } elseif (strlenu($text) > $this->yellow->system->get("commentMaxSize")) { 319 | return "toolong"; // should be avoided by maxlenght in textarea 320 | } else { 321 | return "send"; 322 | } 323 | } 324 | 325 | // Process user input 326 | function processSend() { 327 | if ($this->yellow->lookup->isCommandLine()) $this->yellow->page->error(500, "Static website not supported!"); 328 | $aid = $this->yellow->page->getRequest("aid"); 329 | $action = $this->yellow->page->getRequest("action"); 330 | if ($aid) { 331 | foreach ($this->comments as $key => &$comment) { 332 | if ($comment["meta"]["aid"] == $aid) { 333 | if ($action == "remove") { 334 | unset($this->comments[$key]); 335 | $this->saveComments(); 336 | break; 337 | } elseif ($action == "publish") { 338 | $comment["meta"]["published"] = date("Y-m-d H:i"); 339 | $this->saveComments(); 340 | if ($this->yellow->system->get("commentAuthorNotification")) { 341 | $this->sendNotificationEmail($comment); 342 | } 343 | break; 344 | } 345 | } 346 | } 347 | } 348 | $status = $this->yellow->page->getRequest("status"); 349 | if ($status == "send") { 350 | $comment = $this->buildComment(); 351 | if (!$this->areOpen) { 352 | $status = "closed"; 353 | } else { 354 | $status = $this->verifyComment($comment); 355 | } 356 | if ($status == "send") { 357 | $this->comments[] = $comment; 358 | $status = $this->saveComments(); 359 | } 360 | if ($status == "send" && $this->getEmail()) { 361 | $status = $this->sendEmail($comment); 362 | } 363 | $this->yellow->page->setHeader("Last-Modified", $this->yellow->toolbox->getHttpDateFormatted(time())); 364 | $this->yellow->page->setHeader("Cache-Control", "no-cache, must-revalidate"); 365 | } else { 366 | $status = $this->areOpen ? "none" : "closed"; 367 | } 368 | $this->yellow->page->set("status", $status); 369 | } 370 | 371 | // Send comment email 372 | function sendEmail($comment) { 373 | $mailMessage = $comment["text"]."\r\n"; 374 | $mailMessage .= "-- \r\n"; 375 | $mailMessage .= "Name: " . $comment["meta"]["name"] . "\r\n"; 376 | $mailMessage .= "Mail: " . $comment["meta"]["from"] . "\r\n"; 377 | $mailMessage .= "Uid: " . $comment["meta"]["uid"] . "\r\n"; 378 | $mailMessage .= "-- \r\n"; 379 | if (!$this->yellow->system->get("commentAutoPublish")) { 380 | $mailMessage.= "Publish: " . $this->yellow->page->getUrl(true) . "?aid=" . $comment["meta"]["aid"] . "&action=publish\r\n"; 381 | } else { 382 | $mailMessage.= "Remove: " . $this->yellow->page->getUrl(true) . "?aid=" . $comment["meta"]["aid"] . "&action=remove\r\n"; 383 | } 384 | $mailHeaders = array( 385 | "To" => $this->getEmail(), 386 | "From" => $this->yellow->system->get("sitename")." <".$this->yellow->system->get("email").">", 387 | "Reply-To" => $comment["meta"]["name"]." <".$comment["meta"]["from"].">", 388 | "Subject" => "[".$this->yellow->system->get("sitename")."] ".$this->yellow->page->get("title"), 389 | "Date" => date(DATE_RFC2822), 390 | "Mime-Version" => "1.0", 391 | "Content-Type" => "text/plain; charset=utf-8", 392 | "X-Request-Url" => $this->yellow->page->getUrl(true)); 393 | foreach([ "To", "From", "Reply-To" ] as $headerName) { 394 | $mailHeaders[$headerName] = $this->yellow->lookup->normaliseAddress($mailHeaders[$headerName]); 395 | } 396 | return $this->yellow->toolbox->mail("comment", $mailHeaders, $mailMessage) ? "done" : "error"; 397 | } 398 | 399 | // Send notification email 400 | function sendNotificationEmail($comment) { 401 | $mailMessage = $this->yellow->language->getText("commentPublished")."\r\n\r\n"; 402 | $mailMessage .= $this->yellow->page->getUrl(true) . "#" . $comment["meta"]["uid"] . "\r\n\r\n"; 403 | $mailMessage .= "-- \r\n"; 404 | $mailMessage .= $this->yellow->system->get("sitename") . "\r\n"; 405 | $mailHeaders = array( 406 | "To" => $comment["meta"]["name"]." <".$comment["meta"]["from"].">", 407 | "From" => $this->yellow->system->get("sitename")." <".$this->yellow->system->get("email").">", 408 | "Subject" => "[".$this->yellow->system->get("sitename")."] ".$this->yellow->page->get("title"), 409 | "Date" => date(DATE_RFC2822), 410 | "Mime-Version" => "1.0", 411 | "Content-Type" => "text/plain; charset=utf-8"); 412 | foreach([ "To", "From" ] as $headerName) { 413 | $mailHeaders[$headerName] = $this->yellow->lookup->normaliseAddress($mailHeaders[$headerName]); 414 | } 415 | return $this->yellow->toolbox->mail("comment", $mailHeaders, $mailMessage) ? "done" : "error"; 416 | } 417 | 418 | // Return number of visible comments 419 | function getCommentCount() { 420 | $count = 0; 421 | foreach ($this->comments as $comment) { 422 | if ($comment["meta"]["published"] !== "No") { 423 | $count++; 424 | } 425 | } 426 | return $count; 427 | } 428 | 429 | // Transform a tiny subset of Markdown 430 | function toHtml($text) { 431 | $text = htmlspecialchars($text); 432 | $text = preg_replace_callback('/\\\[\\\n]/', function($m) { return $m[0] == "\\\\" ? "\\" : "
\n"; }, $text); 433 | $text = preg_replace("/\*\*(.+?)\*\*/", "$1", $text); 434 | $text = preg_replace("/\*(.+?)\*/", "$1", $text); 435 | $text = preg_replace("/(?$1", $text); 436 | $text = preg_replace("/\[(.*?)\]\((https?:\/\/[^ )]+)\)/", "$1", $text); 437 | $text = preg_replace("/(\S+@\S+\.[a-z]+)/", "$1", $text); 438 | return $text; 439 | } 440 | 441 | function getUserIcon($email) { 442 | if ($this->yellow->system->get("commentIconGravatar")) { 443 | $base = "//gravatar.com/avatar/"; 444 | return $base . hash("md5", strtolower(trim($email))) . "?s=" . $this->yellow->system->get("commentIconSize") . "&d=" . rawurlencode($this->yellow->system->get("commentIconGravatarDefault")); 445 | } else { 446 | return "data:image/png;base64," . base64_encode($this->getUserIconPng($email)); 447 | } 448 | } 449 | 450 | // Get user icon without any service 451 | function getUserIconPng($email) { 452 | $hash = hexdec(substr(hash("sha256", strtolower(trim($email))), 0, 6)); 453 | $color_background = 0xFFFFFF; 454 | $colors = [0xFF0000, 0xCF0000, 0x00FF00, 0x00CF00, 0x0000FF, 0x0000CF, 0xFFCF000, 0xCFFF00, 0x00FFCF, 0x00CFFF, 0xCF00FF, 0xFF00CF]; 455 | $color_foreground = $colors[($hash >> 15) % count($colors)]; 456 | $multiplicator = ceil($this->yellow->system->get("commentIconSize")/40); 457 | $size = 5*8*$multiplicator; 458 | $png = "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52"; 459 | $png .= pack("N", $size) . pack("N", $size); 460 | $png .= "\x01\x03\x00\x00\x00"; 461 | $png .= hash("crc32b", substr($png, 0xc), true); 462 | $png .= "\x00\x00\x00\x06\x50\x4c\x54\x45"; 463 | $png .= substr(pack("N", $color_foreground), 1); 464 | $png .= substr(pack("N", $color_background), 1); 465 | $png .= hash("crc32b", substr($png, 0x25), true); 466 | $map = [0, 1, 2, 1, 0]; 467 | $pixel = ""; 468 | for ($y=0; $y < 5; $y++) { 469 | $line = "\x00"; 470 | for ($x=0; $x < 5; $x++) { 471 | $line .= str_repeat(((($hash>>($y*5 + $map[$x])) & 1) == 1) ? "\xff" : "\x00", $multiplicator); 472 | } 473 | $pixel .= str_repeat($line, 8*$multiplicator); 474 | } 475 | $pixel = gzcompress($pixel, 6); 476 | $png .= pack("N", strlen($pixel)); 477 | $png .= "\x49\x44\x41\x54" . $pixel; 478 | $png .= hash("crc32b", substr($png, 0x37), true); 479 | $png .= "\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82"; 480 | return $png; 481 | } 482 | } 483 | -------------------------------------------------------------------------------- /extension.ini: -------------------------------------------------------------------------------- 1 | # Datenstrom Yellow extension settings 2 | 3 | Extension: Comment 4 | Version: 0.9.1 5 | Description: Simple commenting system. 6 | Developer: Giovanni Salmeri 7 | Tag: feature 8 | DownloadUrl: https://github.com/GiovanniSalmeri/yellow-comment/archive/refs/heads/main.zip 9 | DocumentationUrl: https://github.com/GiovanniSalmeri/yellow-comment 10 | Published: 2024-04-25 20:30:00 11 | Status: experimental 12 | system/workers/comment.php: comment.php, create, update 13 | system/workers/comment-textarea.js: comment-textarea.js, create, update 14 | system/workers/comment-gravatar.js: comment-gravatar.js, create, update 15 | system/workers/comment.css: comment.css, create, update 16 | --------------------------------------------------------------------------------