Browse Source

Addition of files

pull/1/head
Edi 4 years ago
parent
commit
a48d911d1c
  1. 30
      CHANGELOG.md
  2. 43
      README.md
  3. 16
      admin/add.php
  4. 106
      admin/css/admin.snicker.css
  5. 93
      admin/edit.php
  6. 188
      admin/index-comments.php
  7. 484
      admin/index-config.php
  8. 133
      admin/index-users.php
  9. 85
      admin/index.php
  10. 125
      admin/js/admin.snicker.js
  11. 717
      includes/Gregwar/Captcha/CaptchaBuilder.php
  12. 29
      includes/Gregwar/Captcha/CaptchaBuilderInterface.php
  13. BIN
      includes/Gregwar/Captcha/Font/captcha0.ttf
  14. BIN
      includes/Gregwar/Captcha/Font/captcha1.ttf
  15. BIN
      includes/Gregwar/Captcha/Font/captcha2.ttf
  16. BIN
      includes/Gregwar/Captcha/Font/captcha3.ttf
  17. BIN
      includes/Gregwar/Captcha/Font/captcha4.ttf
  18. BIN
      includes/Gregwar/Captcha/Font/captcha5.ttf
  19. 105
      includes/Gregwar/Captcha/ImageFileHandler.php
  20. 59
      includes/Gregwar/Captcha/PhraseBuilder.php
  21. 21
      includes/Gregwar/Captcha/PhraseBuilderInterface.php
  22. 250
      includes/Identicon/Generator/BaseGenerator.php
  23. 90
      includes/Identicon/Generator/GdGenerator.php
  24. 43
      includes/Identicon/Generator/GeneratorInterface.php
  25. 98
      includes/Identicon/Generator/ImageMagickGenerator.php
  26. 88
      includes/Identicon/Generator/SvgGenerator.php
  27. 123
      includes/Identicon/Identicon.php
  28. 244
      includes/OWASP/PureCaptcha.php
  29. 516
      includes/PIT/Zip.php
  30. 23
      includes/autoload.php
  31. BIN
      includes/img/default-avatar.jpg
  32. 205
      includes/js/identicon.js
  33. 214
      includes/js/pnglib.js
  34. 6
      languages/de.json
  35. 204
      languages/de_DE.json
  36. 204
      languages/en.json
  37. 200
      languages/es.json
  38. 201
      languages/fa_IR.json
  39. 204
      languages/nl_NL.json
  40. 10
      metadata.json
  41. 893
      plugin.php
  42. 46
      system/abstract.comments-theme.php
  43. 494
      system/class.comment.php
  44. 476
      system/class.comments-index.php
  45. 392
      system/class.comments-users.php
  46. 226
      system/class.comments-votes.php
  47. 564
      system/class.comments.php
  48. 1045
      system/class.snicker.php
  49. 90
      system/functions.php
  50. 745
      themes/default/snicker.css
  51. 281
      themes/default/snicker.js
  52. 311
      themes/default/snicker.php

30
CHANGELOG.md

@ -0,0 +1,30 @@
Changelog
=========
Version 0.1.2 - Alpha
---------------------
- Bugfix: You can't comment, after you already comment (Error + Auto-Logout).
- Bugfix: Translated strings didn't returned correctly (English is returned instead),
Thansk to [#10](https://github.com/pytesNET/snicker/issues/10).
- Bugfix: The "Terms of Use" checkbox couldn't be disabled.
Thanks to [#16](https://github.com/pytesNET/snicker/issues/16).
Version 0.1.1 - Alpha
---------------------
- Add: The Persian Translation, many thanks to [abdulhalim](https://github.com/abdulhalim).
- Add: The GD-less PureCaptcha library, written by Abbas Naderi, which doesn't use PHP GD.
Thanks to [#8](https://github.com/pytesNET/snicker/issues/8)
- Add: Reload function to the Captcha Image (Click on the image).
Thanks to [#5](https://github.com/pytesNET/snicker/issues/5).
- Add: A JS-Snippet which Enables/Disables the Gravatar Select Field.
Thanks to [#9](https://github.com/pytesNET/snicker/issues/9).
- Add: The Captcha and Terms note aren't shown for logged in users.
Thanks to [#7](https://github.com/pytesNET/snicker/issues/7).
- Bugfix: The `comments_per_page` option couldn't be set on 0 to disable the limit!
Thanks to [#9](https://github.com/pytesNET/snicker/issues/9).
- Bugfix: Reload the Captcha, if it was wrong.
Thansk to [#5](https://github.com/pytesNET/snicker/issues/5).
Version 0.1.0 - Alpha
---------------------
- Initial Release owo/

43
README.md

@ -0,0 +1,43 @@
Snicker
=======
Snicker is the first native FlatFile comment system for the Content Management System
[Bludit](https://github.com/bludit/bludit). It allows to write and publish comments using basic
HTML Syntax or Markdown. The Plugin also offers an extensive environment, many settings and
possibilities and is also completely compliant with the GDPR!
Features
--------
- Level-Based, AJAX-enabled Commenting for Guests and Users
- Many Configurations and adaptable Strings and Themes
- Guest Management for Not-Logged-In Comment Authors
- Moderatable Comments (Pending, Approved, Rejected, Spam)
- Extensive Backend with many possibilities
- Compliant with the European GDPR
Requirements
------------
- PHP v5.6.0+
- Bludit v3.5.0+
Dependencies
------------
- Snicker use the awesome [Captcha PHP Library](https://github.com/Gregwar/Captcha) made by Grégoire Passault
- Snicker uses also the [PureCaptcha PHP Library](https://github.com/OWASP/PureCaptcha) as fallback by Abbas Naderi
- The Avatars are served per default by [Gravatar](https://de.gravatar.com/), made by Automattic / WordPress
- **But** you can also directly use [Identicons](http://identicon.net) instead...
- ... where we use the [Identicon PHP Library](https://github.com/yzalis/Identicon) from Benjamin Laugueux
- ... and the [Identicon JavaScript Library](https://github.com/stewartlord/identicon.js) from Stewart Lord
- ... which itself depends on the [PNG JavaScript Library](https://www.xarg.org/2010/03/generate-client-side-png-files-using-javascript/) by Robert Eisele
Thanks for this awesome packages and projects!
Installation
------------
- Download the [Snicker Plugin](https://github.com/pytesNET/snicker/zipball/master)
- Upload it to your `bl-plugins` folder of your Bludit Website
- Visit the Bludit Administration and enable the "Snicker" Plugin through "Settings" > "Plugins"
Copyright & License
-------------------
Published under the MIT-License; Copyright © 2019 SamBrishes, pytesNET

16
admin/add.php

@ -0,0 +1,16 @@
<?php
/*
| Snicker The first native FlatFile Comment Plugin 4 Bludit
| @file ./admin/edit.php
| @author SamBrishes <sam@pytes.net>
| @version 0.1.2 [0.1.0] - Alpha
|
| @website https://github.com/pytesNET/snicker
| @license X11 / MIT License
| @copyright Copyright © 2019 SamBrishes, pytesNET <info@pytes.net>
*/
if(!defined("BLUDIT")){ die("Go directly to Jail. Do not pass Go. Do not collect 200 Cookies!"); }
// Add Formular 4 Admins
?>

106
admin/css/admin.snicker.css

@ -0,0 +1,106 @@
@charset "UTF-8";
/*
| Snicker The first native FlatFile Comment Plugin 4 Bludit
| @file ./admin/css/admin.snicker.css
| @author SamBrishes <sam@pytes.net>
| @version 0.1.2 [0.1.0] - Alpha
|
| @website https://github.com/pytesNET/snicker
| @license X11 / MIT License
| @copyright Copyright © 2019 SamBrishes, pytesNET <info@pytes.net>
*/
/* @start GENERAL */
.shadow-sm-both{
box-shadow: 0 0 0.25rem rgba(0,0,0,.075) !important;
}
.table-hover-light tr:hover{
background-color: #f0f3f6;
}
/* @end GENERAL */
/* @start Admin Navigation */
.nav.nav-pills .nav-link{
color: #343a40;
font-weight: 400;
font-weight: 600;
background-color: #e9ecef;
transition: color 142ms linear, background 142ms linear;
}
.nav.nav-pills .nav-link:hover{
background-color: #d9dcdf;
}
.nav.nav-pills .nav-link.active{
color: #ffffff;
text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.75);
background-color: #343a40;
}
.nav.nav-pills .nav-link .badge{
margin-top: 3px;
text-shadow: none;
vertical-align: top;
}
.nav.nav-pills .nav-link.active .badge{
text-shadow: none;
background-color: #fff;
}
.nav.nav-pills .nav-link.nav-pending{
border-top-right-radius: 0 !important;
border-bottom-right-radius: 0 !important;
}
.nav.nav-pills .nav-link.nav-pending.active{
text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.25);
background-color: #007bff;
}
.nav.nav-pills .nav-link.nav-pending.active .badge{
color: #007bff;
}
.nav.nav-pills .nav-link.nav-approved{
border-radius: 0 !important;
}
.nav.nav-pills .nav-link.nav-approved.active{
text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.25);
background-color: #28A745;
}
.nav.nav-pills .nav-link.nav-rejected{
border-radius: 0 !important;
}
.nav.nav-pills .nav-link.nav-rejected.active{
text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.25);
background-color: #FFC107;
}
.nav.nav-pills .nav-link.nav-spam{
border-top-left-radius: 0 !important;
border-bottom-left-radius: 0 !important;
}
.nav.nav-pills .nav-link.nav-spam.active{
text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.25);
background-color: #DC3545;
}
.nav.nav-pills .nav-link.nav-spam.active .badge{
color: #DC3545;
}
.nav.nav-pills .nav-link.nav-config{
font-weight: 400;
}
.nav.nav-pills .nav-link.nav-config .oi{
margin-right: 3px;
line-height: 1.5;
vertical-align: top;
}
/* @end Admin Navigation */
/* @start Admin Pagination */
.btn-group.btn-group-pagination .btn{
width: 34px;
padding-top: 5px;
padding-bottom: 7px;
font-size: 20px;
line-height: 20px;
text-align: center;
}
.btn-group.btn-group-pagination .btn.disabled{
cursor: not-allowed;
}
/* @end Admin Pagination */

93
admin/edit.php

@ -0,0 +1,93 @@
<?php
/*
| Snicker The first native FlatFile Comment Plugin 4 Bludit
| @file ./admin/edit.php
| @author SamBrishes <sam@pytes.net>
| @version 0.1.2 [0.1.0] - Alpha
|
| @website https://github.com/pytesNET/snicker
| @license X11 / MIT License
| @copyright Copyright © 2019 SamBrishes, pytesNET <info@pytes.net>
*/
if(!defined("BLUDIT")){ die("Go directly to Jail. Do not pass Go. Do not collect 200 Cookies!"); }
global $login, $pages, $security, $SnickerIndex;
$data = $SnickerIndex->getComment($_GET["uid"]);
$comment = new Comment($_GET["uid"], $data["page_uuid"]);
$page = new Page($pages->getByUUID($data["page_uuid"]));
?><h2 class="mt-0 mb-3">
<span class="oi oi-comment-square" style="font-size: 0.7em;"></span> Snicker <?php sn_e("Comments"); ?> / <?php sn_e("Edit"); ?>
</h2>
<form method="post" action="<?php echo HTML_PATH_ADMIN_ROOT; ?>snicker">
<div class="card" style="margin: 1.5rem 0;">
<div class="card-body">
<div class="row">
<div class="col-sm-6">
<input type="hidden" id="tokenUser" name="tokenUser" value="<?php echo $login->username(); ?>" />
<input type="hidden" id="tokenCSRF" name="tokenCSRF" value="<?php echo $security->getTokenCSRF(); ?>" />
<input type="hidden" id="sn-action" name="action" value="snicker" />
<input type="hidden" id="sn-snicker" name="snicker" value="edit" />
<input type="hidden" id="sn-unique" name="uid" value="<?php echo $comment->uid(); ?>" />
<button class="btn btn-primary" name="type" value="edit"><?php sn_e("Update Comment"); ?></button>
</div>
<div class="col-sm-6 text-right">
<button class="btn btn-danger" name="type" value="delete"><?php sn_e("Delete Comment"); ?></button>
</div>
</div>
</div>
</div>
<div class="row mb-4">
<div class="col">
<input type="text" name="comment[title]" value="<?php echo $comment->title(); ?>"
class="form-control form-control-lg" placeholder="<?php sn_e("Comment Title"); ?>" />
</div>
</div>
<div class="row">
<div class="col-sm-8">
<textarea name="comment[comment]" class="form-control" placeholder="<?php sn_e("Comment Text"); ?>"
style="min-height: 275px;"><?php echo $comment->commentRaw(); ?></textarea>
</div>
<div class="col-sm-4">
<div class="card">
<div class="card-header"><?php sn_e("Meta Settings"); ?></div>
<div class="card-body">
<?php if(strpos($comment->getValue("author"), "bludit") === 0){ ?>
<p>
<input type="text" value="<?php echo $comment->username(); ?>" class="form-control" disabled />
</p>
<p>
<input type="text" value="<?php sn_e("Registered User"); ?>" class="form-control" disabled />
</p>
<?php } else { ?>
<p>
<input type="text" name="comment[username]" value="<?php echo $comment->username(); ?>"
class="form-control" placeholder="<?php sn_e("Comment Username"); ?>" />
</p>
<p>
<input type="text" name="comment[email]" value="<?php echo $comment->email(); ?>"
class="form-control" placeholder="<?php sn_e("Comment eMail"); ?>" />
</p>
<?php } ?>
<p>
<select name="comment[status]" class="custom-select">
<option value="pending"<?php echo ($comment->isPending())? ' selected="selected"': ''; ?>><?php sn_e("Pending"); ?></option>
<option value="approved"<?php echo ($comment->isApproved())? ' selected="selected"': ''; ?>><?php sn_e("Approved"); ?></option>
<option value="rejected"<?php echo ($comment->isRejected())? ' selected="selected"': ''; ?>><?php sn_e("Rejected"); ?></option>
<option value="spam"<?php echo ($comment->isSpam())? ' selected="selected"': ''; ?>><?php sn_e("Spam"); ?></option>
</select>
</p>
</div>
</div>
<p class="mt-4 text-center">
<a href="<?php echo $page->permalink(); ?>" target="_blank" class="btn btn-primary"><?php sn_e("View Page"); ?></a>
</p>
</div>
</div>
</form>

188
admin/index-comments.php

@ -0,0 +1,188 @@
<?php
/*
| Snicker The first native FlatFile Comment Plugin 4 Bludit
| @file ./admin/index-comments.php
| @author SamBrishes <sam@pytes.net>
| @version 0.1.2 [0.1.0] - Alpha
|
| @website https://github.com/pytesNET/snicker
| @license X11 / MIT License
| @copyright Copyright © 2019 SamBrishes, pytesNET <info@pytes.net>
*/
if(!defined("BLUDIT")){ die("Go directly to Jail. Do not pass Go. Do not collect 200 Cookies!"); }
global $pages, $security, $Snicker, $SnickerIndex, $SnickerPlugin, $SnickerUsers;
// Get Data
$limit = $SnickerPlugin->getValue("frontend_per_page");
if($limit === 0){
$limit = 15;
}
$current = isset($_GET["tab"])? $_GET["tab"]: "pending";
// Get View
$view = "index";
if(isset($_GET["view"]) && in_array($_GET["view"], array("search", "single", "uuid", "user"))){
$view = $current = $_GET["view"];
$tabs = array($view);
} else {
$tabs = array("pending", "approved", "rejected", "spam");
}
// Render Comemnts Tab
foreach($tabs AS $status){
if(isset($_GET["tab"]) && $_GET["tab"] === $status){
$page = max((isset($_GET["page"])? (int) $_GET["page"]: 1), 1);
} else {
$page = 1;
}
// Get Comments
if($view === "index"){
$comments = $SnickerIndex->getList($status, $page, $limit);
$total = $SnickerIndex->count($status);
} else if($view === "search"){
$comments = $SnickerIndex->searchComments(isset($_GET["search"])? $_GET["search"]: "");
$total = count($comments);
} else if($view === "single"){
$comments = $SnickerIndex->getListByParent(isset($_GET["single"])? $_GET["single"]: "");
$total = count($comments);
} else if($view === "uuid"){
$comments = $SnickerIndex->getListByUUID(isset($_GET["uuid"])? $_GET["uuid"]: "");
$total = count($comments);
} else if($view === "user"){
$comments = $SnickerIndex->getListByUser(isset($_GET["user"])? $_GET["user"]: "");
$total = count($comments);
}
// Render Tab Content
$link = DOMAIN_ADMIN . "snicker?page=%d&tab={$status}#{$status}";
?>
<div id="snicker-<?php echo $status; ?>" class="tab-pane <?php echo($current === $status)? "active": ""; ?>">
<div class="card shadow-sm" style="margin: 1.5rem 0;">
<div class="card-body">
<div class="row">
<form class="col-sm-6" method="get" action="<?php echo DOMAIN_ADMIN; ?>snicker">
<div class="form-row align-items-center">
<div class="col-sm-8">
<?php $search = isset($_GET["search"])? $_GET["search"]: ""; ?>
<input type="text" name="search" value="<?php echo $search; ?>" class="form-control" placeholder="<?php sn_e("Comment Title or Excerpt"); ?>" />
</div>
<div class="col-sm-4">
<button class="btn btn-primary" name="view" value="search"><?php sn_e("Search Comments"); ?></button>
</div>
</div>
</form>
<div class="col-sm-6 text-right">
<?php if($total > $limit){ ?>
<div class="btn-group btn-group-pagination">
<?php if($page <= 1){ ?>
<span class="btn btn-secondary disabled">&laquo;</span>
<span class="btn btn-secondary disabled">&lsaquo;</span>
<?php } else { ?>
<a href="<?php printf($link, 1); ?>" class="btn btn-secondary">&laquo;</a>
<a href="<?php printf($link, $page-1); ?>" class="btn btn-secondary">&lsaquo;</a>
<?php } ?>
<?php if(($page * $limit) < $total){ ?>
<a href="<?php printf($link, $page+1); ?>" class="btn btn-secondary">&rsaquo;</a>
<a href="<?php printf($link, ceil($total / $limit)); ?>" class="btn btn-secondary">&raquo;</a>
<?php } else { ?>
<span class="btn btn-secondary disabled">&rsaquo;</span>
<span class="btn btn-secondary disabled">&raquo;</span>
<?php } ?>
</div>
<?php } ?>
</div>
</div>
</div>
</div>
<?php /* No Comments available */ ?>
<?php if(count($comments) < 1){ ?>
<div class="row justify-content-md-center">
<div class="col-sm-6">
<div class="card w-100 shadow-sm bg-light">
<div class="card-body text-center p-4"><i><?php sn_e("No Comments available"); ?></i></div>
</div>
</div>
</div>
</div>
<?php continue; ?>
<?php } ?>
<?php /* Comments Table */ ?>
<?php $link = DOMAIN_ADMIN . "snicker?action=snicker&snicker=%s&uid=%s&status=%s&tokenCSRF=" . $security->getTokenCSRF(); ?>
<table class="table table-bordered table-hover-light shadow-sm mt-3">
<?php foreach(array("thead", "tfoot") AS $tag){ ?>
<<?php echo $tag; ?>>
<tr class="thead-light">
<th width="56%" class="border-0 p-3 text-uppercase text-muted"><?php sn_e("Comment"); ?></th>
<th width="22%" class="border-0 p-3 text-uppercase text-muted text-center"><?php sn_e("Author"); ?></th>
<th width="22%" class="border-0 p-3 text-uppercase text-muted text-center"><?php sn_e("Actions"); ?></th>
</tr>
</<?php echo $tag; ?>>
<?php } ?>
<tbody class="shadow-sm-both">
<?php foreach($comments AS $uid){ ?>
<?php
$data = $SnickerIndex->getComment($uid, $status);
if(!(isset($data["page_uuid"]) && is_string($data["page_uuid"]))){
continue;
}
$user = $SnickerUsers->getByString($data["author"]);
?>
<tr>
<td class="pt-3 pb-3 pl-3 pr-3">
<?php
if($SnickerPlugin->getValue("comment_title") !== "disabled" && !empty($data["title"])){
echo '<b class="d-inline-block">' . $data["title"] . '</b>';
}
echo '<p class="text-muted m-0" style="font-size:12px;">' . (isset($data["excerpt"])? $data["excerpt"]: "") . '</p>';
if(!empty($data["parent_uid"]) && $SnickerIndex->exists($data["parent_uid"]) && $view !== "single"){
$reply = DOMAIN_ADMIN . "snicker?view=single&single={$uid}";
$reply = '<a href="'.$reply.'" title="'.sn__("Show all replies").'">' . $SnickerIndex->getComment($data["parent_uid"])["title"] . '</a>';
echo "<div class='text-muted mt-1' style='font-size:12px;'>" . sn__("Reply To") . ": " . $reply . "</div>";
}
?>
</td>
<td class="align-middle pt-2 pb-2 pl-3 pr-3">
<span class="d-inline-block"><?php echo $user["username"]; ?></span>
<small class='d-block'><?php echo $user["email"]; ?></small>
</td>
<td class="text-center align-middle pt-2 pb-2 pl-1 pr-1">
<div class="btn-group">
<button class="btn btn-outline-secondary btn-sm dropdown-toggle" data-toggle="dropdown">
<?php sn_e("Change"); ?>
</button>
<div class="dropdown-menu dropdown-menu-right">
<a class="dropdown-item text-primary" href="<?php echo DOMAIN_ADMIN . "snicker/edit/?uid=" . $uid; ?>"><?php sn_e("Edit Comment"); ?></a>
<a class="dropdown-item text-danger" href="<?php printf($link, "delete", $uid, "delete"); ?>"><?php sn_e("Delete Comment"); ?></a>
<div class="dropdown-divider"></div>
<?php if($status !== "approved"){ ?>
<a class="dropdown-item" href="<?php printf($link, "moderate", $uid, "approved"); ?>"><?php sn_e("Approve Comment"); ?></a>
<?php } ?>
<?php if($status !== "rejected"){ ?>
<a class="dropdown-item" href="<?php printf($link, "moderate", $uid, "rejected"); ?>"><?php sn_e("Reject Comment"); ?></a>
<?php } ?>
<?php if($status !== "spam"){ ?>
<a class="dropdown-item" href="<?php printf($link, "moderate", $uid, "spam"); ?>"><?php sn_e("Mark as Spam"); ?></a>
<?php } ?>
<?php if($status !== "pending"){ ?>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="<?php printf($link, "moderate", $uid, "pending"); ?>"><?php sn_e("Back to Pending"); ?></a>
<?php } ?>
</div>
</div>
<?php $page = new Page($pages->getByUUID($data["page_uuid"])); ?>
<a href="<?php echo $page->permalink(); ?>#comment-<?php echo $uid; ?>" class="btn btn-outline-primary btn-sm" target="_blank"><?php sn_e("View"); ?></a>
</td>
</tr>
<?php } ?>
</tbody>
</table>
</div>
<?php
}

484
admin/index-config.php

@ -0,0 +1,484 @@
<?php
/*
| Snicker The first native FlatFile Comment Plugin 4 Bludit
| @file ./admin/index-config.php
| @author SamBrishes <sam@pytes.net>
| @version 0.1.2 [0.1.0] - Alpha
|
| @website https://github.com/pytesNET/snicker
| @license X11 / MIT License
| @copyright Copyright © 2019 SamBrishes, pytesNET <info@pytes.net>
*/
if(!defined("BLUDIT")){ die("Go directly to Jail. Do not pass Go. Do not collect 200 Cookies!"); }
global $L, $login, $pages, $security, $Snicker, $SnickerPlugin;
// Get Static Pages
$static = $pages->getStaticDB(false);
?>
<div id="snicker-configure" class="tab-pane">
<form method="post" action="<?php echo HTML_PATH_ADMIN_ROOT; ?>snicker#configure">
<div class="card shadow-sm" style="margin: 1.5rem 0;">
<div class="card-body">
<div class="row">
<div class="col-sm-6">
<input type="hidden" id="tokenUser" name="tokenUser" value="<?php echo $login->username(); ?>" />
<input type="hidden" id="tokenCSRF" name="tokenCSRF" value="<?php echo $security->getTokenCSRF(); ?>" />
<input type="hidden" id="sn-action" name="action" value="snicker" />
<button class="btn btn-primary" name="snicker" value="configure"><?php sn_e("Save Settings"); ?></button>
</div>
<div class="col-sm-6">
</div>
</div>
</div>
</div>
<div class="accordion shadow-sm" id="accordion-settings">
<div class="card">
<div class="card-header text-uppercase pt-3 pb-3 pl-4 pr-4" data-toggle="collapse" data-target="#accordion-general"><?php sn_e("General Settings"); ?></div>
<div id="accordion-general" class="collapse show" data-parent="#accordion-settings">
<div class="card-body">
<div class="form-group row">
<label for="sn-moderation" class="col-sm-3 col-form-label"><?php sn_e("Comment Moderation"); ?></label>
<div class="col-sm-9">
<select id="sn-moderation" name="moderation" class="custom-select custom-select-sm w-auto">
<option value="true" <?php sn_selected("moderation", true); ?>><?php sn_e("Moderate"); ?></option>
<option value="false" <?php sn_selected("moderation", false); ?>><?php sn_e("Pass"); ?></option>
</select>
<label for="sn-moderation" class="col-form-label-sm ml-2 align-top"><?php sn_e("each Comment") ?></label>
<div class="custom-control custom-checkbox pl-5 mt-1">
<input type="checkbox" id="sn-moderation-loggedin" name="moderation_loggedin" value="true"
class="custom-control-input" <?php sn_checked("moderation_loggedin"); ?> />
<label class="custom-control-label" for="sn-moderation-loggedin"><?php sn_e("Unless the user is logged in"); ?></label>
</div>
<div class="custom-control custom-checkbox pl-5">
<input type="checkbox" value="true" class="custom-control-input" checked="checked" disabled="disabled" />
<label class="custom-control-label"><?php sn_e("Unless the user is admin or the content author"); ?></label>
</div>
<div class="custom-control custom-checkbox pl-5 mb-2">
<input type="checkbox" id="sn-moderation-approved" name="moderation_approved" value="true"
class="custom-control-input" <?php sn_checked("moderation_approved"); ?> />
<label class="custom-control-label" for="sn-moderation-approved"><?php sn_e("Unless the user has an already approved comment"); ?></label>
</div>
</div>
</div>
<div class="form-group row">
<label for="sn-comment-title" class="col-sm-3 col-form-label"><?php sn_e("Allow Comments"); ?></label>
<div class="col-sm-9">
<div class="custom-control custom-checkbox">
<input type="checkbox" id="sn-comment-on-public" name="comment_on_public" value="true"
class="custom-control-input" <?php sn_checked("comment_on_public"); ?> />
<label class="custom-control-label" for="sn-comment-on-public"><?php sn_e("... on Public Pages"); ?></label>
</div>
<div class="custom-control custom-checkbox">
<input type="checkbox" id="sn-comment-on-sticky" name="comment_on_sticky" value="true"
class="custom-control-input" <?php sn_checked("comment_on_sticky"); ?> />
<label class="custom-control-label" for="sn-comment-on-sticky"><?php sn_e("... on Sticky Pages"); ?></label>
</div>
<div class="custom-control custom-checkbox">
<input type="checkbox" id="sn-comment-on-static" name="comment_on_static" value="true"
class="custom-control-input" <?php sn_checked("comment_on_static"); ?> />
<label class="custom-control-label" for="sn-comment-on-static"><?php sn_e("... on Static Pages"); ?></label>
</div>
</div>
</div>
<div class="form-group row">
<label for="sn-comment-title" class="col-sm-3 col-form-label"><?php sn_e("Comment Title"); ?></label>
<div class="col-sm-9">
<select id="sn-comment-title" name="comment_title" class="form-control custom-select">
<option value="optional" <?php sn_selected("comment_title", "optional"); ?>><?php sn_e("Enable (Optional)"); ?></option>
<option value="required" <?php sn_selected("comment_title", "required"); ?>><?php sn_e("Enable (Required)"); ?></option>
<option value="disabled" <?php sn_selected("comment_title", "disabled"); ?>><?php sn_e("Disable"); ?></option>
</select>
</div>
</div>
<div class="form-group row">
<label for="sn-comment-limit" class="col-sm-3 col-form-label"><?php sn_e("Comment Limit"); ?></label>
<div class="col-sm-9">
<input type="number" id="sn-comment-limit" name="comment_limit" value="<?php echo sn_config("comment_limit"); ?>"
class="form-control" min="0" placeholder="<?php sn_e("Use '0' to disable any limit!"); ?>" />
</div>
</div>
<div class="form-group row">
<label for="sn-comment-depth" class="col-sm-3 col-form-label"><?php sn_e("Comment Depth"); ?></label>
<div class="col-sm-9">
<input type="number" id="sn-comment-depth" name="comment_depth" value="<?php echo sn_config("comment_depth"); ?>"
class="form-control" min="0" placeholder="<?php sn_e("Use '0' to disable any limit!"); ?>" />
<small class="form-text text-muted"><?php sn_e("Use '0' to disable any limit!"); ?></small>
</div>
</div>
<div class="form-group row">
<label class="col-sm-3 col-form-label"><?php sn_e("Comment Markup"); ?></label>
<div class="col-sm-9">
<div class="custom-control custom-checkbox">
<input type="checkbox" id="sn-markup-html" name="comment_markup_html" value="true"
class="custom-control-input" <?php sn_checked("comment_markup_html"); ?> />
<label class="custom-control-label" for="sn-markup-html"><?php sn_e("Allow Basic HTML"); ?></label>
</div>
<div class="custom-control custom-checkbox">
<input type="checkbox" id="sn-markup-markdown" name="comment_markup_markdown" value="true"
class="custom-control-input" <?php sn_checked("comment_markup_markdown"); ?> />
<label class="custom-control-label" for="sn-markup-markdown"><?php sn_e("Allow Markdown"); ?></label>
</div>
</div>
</div>
<div class="form-group row">
<label class="col-sm-3 col-form-label"><?php sn_e("Comment Voting"); ?></label>
<div class="col-sm-9">
<label for="sn-vote-storage" class="col-form-label-sm mr-2 align-top"><?php sn_e("Store Votes made by Guests in the") ?></label>
<select id="sn-vote-storage" name="comment_vote_storage" class="custom-select custom-select-sm w-auto">
<option value="cookie" <?php sn_selected("comment_vote_storage", "cookie"); ?>><?php sn_e("Cookie Storage"); ?></option>
<option value="session" <?php sn_selected("comment_vote_storage", "session"); ?>><?php sn_e("Session Storage"); ?></option>
<option value="database" <?php sn_selected("comment_vote_storage", "database"); ?>><?php sn_e("Database Storage"); ?></option>
</select>
<a href="#" class="ml-2 align-top" data-container="body" data-toggle="popover" data-placement="left"
data-trigger="focus" data-target="#help-content">(<?php sn_e("What?"); ?>)</a>
<div id="help-content" class="hide d-none" style="width: 100%;">
<p>
<?php sn_e("The <b>Cookie Storage</b> is located on the Computer of the user. So you don't have the full control AND you require the appropriate permissions from the user."); ?>
</p>
<p>
<?php sn_e("The <b>Session Storage</b> is just stored temporary on the server, it gets cleaned up when the user closes the browser. Therefore you don't need any permissions from the user."); ?>
</p>
<p>
<?php sn_e("The <b>Database Storage</b> generates and stores an anonymized but assignable value of the user, which also requires the appropriate permissions from the user."); ?>
</p>
<p class="bg-light border-top" style="margin: -.5rem -.75rem;padding: .5rem .75rem;border-radius: 0 0 3px 3px;">
<?php sn_e("<b>Please Note:</b> You are responsible for obtaining the appropriate permissions, Snicker just handles the permissions for data send (and stored) via the comment form!"); ?>
</p>
</div>
<div class="custom-control custom-checkbox">
<input type="checkbox" id="sn-like" name="comment_enable_like" value="true"
class="custom-control-input" <?php sn_checked("comment_enable_like"); ?> />
<label class="custom-control-label" for="sn-like"><?php sn_e("Allow to %s comments", array("<b>".sn__("Like")."</b>")); ?></label>
</div>
<div class="custom-control custom-checkbox">
<input type="checkbox" id="sn-dislike" name="comment_enable_dislike" value="true"
class="custom-control-input" <?php sn_checked("comment_enable_dislike"); ?>/>
<label class="custom-control-label" for="sn-dislike"><?php sn_e("Allow to %s comments", array("<b>".sn__("Dislike")."</b>")); ?></label>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header text-uppercase pt-3 pb-3 pl-4 pr-4" data-toggle="collapse" data-target="#accordion-frontend"><?php sn_e("Frontend Settings"); ?></div>
<div id="accordion-frontend" class="collapse" data-parent="#accordion-settings">
<div class="card-body">
<div class="form-group row">
<label for="sn-filter" class="col-sm-3 col-form-label"><?php sn_e("Page Filter"); ?></label>
<div class="col-sm-9">
<select id="sn-filter" name="frontend_filter" class="form-control custom-select">
<option value="disabled" <?php sn_selected("frontend_filter", "disabled"); ?>><?php sn_e("Disable Page Filter"); ?></option>
<option value="pageBegin" <?php sn_selected("frontend_filter", "pageBegin"); ?>><?php sn_e("Use 'pageBegin'"); ?></option>
<option value="pageEnd" <?php sn_selected("frontend_filter", "pageEnd"); ?>><?php sn_e("Use 'pageEnd'"); ?></option>
<option value="siteBodyBegin" <?php sn_selected("frontend_filter", "siteBodyBegin"); ?>><?php sn_e("Use 'siteBodyBegin'"); ?></option>
<option value="siteBodyEnd" <?php sn_selected("frontend_filter", "siteBodyEnd"); ?>><?php sn_e("Use 'siteBodyEnd'"); ?></option>
</select>
</div>
</div>
<div class="form-group row">
<label for="sn-captcha" class="col-sm-3 col-form-label"><?php sn_e("Comment Captcha"); ?></label>
<div class="col-sm-9">
<select id="sn-captcha" name="frontend_captcha" class="form-control custom-select">
<option value="disabled" <?php sn_selected("frontend_captcha", "disabled"); ?>><?php sn_e("Disable Captcha"); ?></option>
<option value="purecaptcha" <?php sn_selected("frontend_captcha", "purecaptcha"); ?>><?php sn_e("Use OWASP's PureCaptcha"); ?></option>
<?php if(function_exists("imagettfbbox")){ ?>
<option value="gregwar" <?php sn_selected("frontend_captcha", "gregwar"); ?>><?php sn_e("Use Gregway's Captcha"); ?></option>
<?php } else { ?>
<option disabled="disabled"><?php sn_e("Use Gregway's Captcha (GD library is missing!)"); ?></option>
<?php } ?>
<option value="recaptcha" <?php sn_selected("frontend_captcha", "recaptcha"); ?> disabled="disabled"><?php sn_e("Use Googles reCaptcha (Not available yet)"); ?></option>
</select>
</div>
</div>
<div class="form-group row">
<label for="sn-template" class="col-sm-3 col-form-label"><?php sn_e("Comment Template"); ?></label>
<div class="col-sm-9">
<select id="sn-template" name="frontend_template" class="form-control custom-select">
<?php
foreach($Snicker->themes AS $key => $theme){
?>
<option value="<?php echo $key; ?>" <?php sn_selected("frontend_template", $key); ?>><?php echo $theme::SNICKER_NAME; ?></option>
<?php
}
?>
</select>
</div>
</div>
<div class="form-group row">
<label for="sn-order" class="col-sm-3 col-form-label"><?php sn_e("Comment Order"); ?></label>
<div class="col-sm-9">
<select id="sn-order" name="frontend_order" class="form-control custom-select">
<option value="date_desc" <?php sn_selected("frontend_order", "date_desc"); ?>><?php sn_e("Newest Comments First"); ?></option>
<option value="date_asc" <?php sn_selected("frontend_order", "date_asc"); ?>><?php sn_e("Oldest Comments First"); ?></option>
</select>
</div>
</div>
<div class="form-group row">
<label for="sn-order" class="col-sm-3 col-form-label"><?php sn_e("Comment Form Position"); ?></label>
<div class="col-sm-9">
<select id="sn-order" name="frontend_form" class="form-control custom-select">
<option value="top" <?php sn_selected("frontend_form", "top"); ?>><?php sn_e("Show Comment Form above Comments"); ?></option>
<option value="bottom" <?php sn_selected("frontend_form", "bottom"); ?>><?php sn_e("Show Comment Form below Comments"); ?></option>
</select>
</div>
</div>
<div class="form-group row">
<label for="sn-per-page" class="col-sm-3 col-form-label"><?php sn_e("Comments Per Page"); ?></label>
<div class="col-sm-9">
<input type="number" id="sn-per-page" name="frontend_per_page" value="<?php echo sn_config("frontend_per_page"); ?>"
class="form-control" min="0" step="1" placheolder="<?php sn_e("Use '0' to show all available comments!"); ?>" />
<small class="form-text text-muted"><?php sn_e("Use '0' to show all available comments!"); ?></small>
</div>
</div>
<div class="form-group row">
<label for="sn-terms" class="col-sm-3 col-form-label"><?php sn_e("Terms of Use Checkbox"); ?></label>
<div class="col-sm-9">
<select id="sn-terms" name="frontend_terms" class="form-control custom-select">
<option value="disabled" <?php sn_selected("frontend_terms", "disabled"); ?>><?php sn_e("Disable this field"); ?></option>
<option value="default" <?php sn_selected("frontend_terms", "default"); ?>><?php sn_e("Show Message (See Strings)"); ?></option>
<?php foreach($static AS $key => $value){ ?>
<option value="<?php echo $key; ?>" <?php sn_selected("frontend_terms", $key); ?>><?php sn_e("Page"); ?>: <?php echo $value["title"]; ?></option>
<?php } ?>
</select>
<small class="form-text text-muted"><?php sn_e("Show the default GDPR Text or Select your own static 'Terms of Use' page!"); ?></small>
</div>
</div>
<div class="form-group row">
<label for="sn-ajax" class="col-sm-3 col-form-label"><?php sn_e("AJAX Script"); ?></label>
<div class="col-sm-9">
<select id="sn-ajax" name="frontend_ajax" class="form-control custom-select">
<option value="true" <?php sn_selected("frontend_ajax", true); ?>><?php sn_e("Embed AJAX Script"); ?></option>
<option value="false" <?php sn_selected("frontend_ajax", false); ?>><?php sn_e("Don't use AJAX"); ?></option>
</select>
<small class="form-text text-muted"><?php sn_e("The AJAX Script hands over the request (comment, like, dislike) directly without reloading the page!"); ?></small>
</div>
</div>
<hr style="margin: 30px -20px;" />
<div class="form-group row">
<label for="sn-avatar" class="col-sm-3 col-form-label"><?php sn_e("Comment Avatar"); ?></label>
<div class="col-sm-9">
<select id="sn-avatar" name="frontend_avatar" class="form-control custom-select">
<option value="gravatar" <?php sn_selected("frontend_avatar", "gravatar"); ?>><?php sn_e("Use Gravatar"); ?></option>
<option value="identicon" <?php sn_selected("frontend_avatar", "identicon"); ?>><?php sn_e("Use Identicon"); ?></option>
<option value="static" <?php sn_selected("frontend_avatar", "static"); ?>><?php sn_e("Use Mystery Men"); ?></option>
</select>
<div class="custom-control custom-checkbox mt-1">
<input type="checkbox" id="sn-moderation-users" name="frontend_avatar_users" value="true"
class="custom-control-input" <?php sn_checked("frontend_avatar_users"); ?> />
<label class="custom-control-label" for="sn-moderation-users"><?php sn_e("Use & Prefer profile pictures on logged-in Users"); ?></label>
</div>
</div>
</div>
<div class="form-group row">
<label for="sn-gravatar" class="col-sm-3 col-form-label"><?php sn_e("Comment Gravatar"); ?></label>
<div class="col-sm-9">
<select id="sn-gravatar" name="frontend_gravatar" class="form-control custom-select">
<option value="mp" <?php sn_selected("frontend_gravatar", "mp"); ?>><?php sn_e("Show Mystery Person"); ?></option>
<option value="identicon" <?php sn_selected("frontend_gravatar", "identicon"); ?>><?php sn_e("Show"); ?> Identicon</option>
<option value="monsterid" <?php sn_selected("frontend_gravatar", "monsterid"); ?>><?php sn_e("Show"); ?> Monster ID</option>
<option value="wavatar" <?php sn_selected("frontend_gravatar", "wavatar"); ?>><?php sn_e("Show"); ?> WAvatar</option>
</select>
<small class="form-text text-muted"><?php sn_e("The default Gravatar image, if the user has no Gravatar!"); ?></small>
</div>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header text-uppercase pt-3 pb-3 pl-4 pr-4" data-toggle="collapse" data-target="#accordion-subscripton"><?php sn_e("Subscription Settings"); ?></div>
<div id="accordion-subscripton" class="collapse" data-parent="#accordion-settings">
<div class="card-body">
<div class="alert alert-info"><?php sn_e("The Subscription system isn't available yet!"); ?> :(</div>
<div class="form-group row">
<label for="sn-subscription" class="col-sm-3 col-form-label text-muted"><?php sn_e("eMail Subscription"); ?></label>
<div class="col-sm-9">
<select id="sn-subscription" name="subscription" class="form-control custom-select" disabled="disabled">
<option value="true" <?php sn_selected("subscription", true); ?> disabled="disabled"><?php sn_e("Enable"); ?></option>
<option value="false" <?php sn_selected("subscription", false); ?>><?php sn_e("Disable"); ?></option>
</select>
</div>
</div>
<div class="form-group row">
<label for="sn-subscription-from" class="col-sm-3 col-form-label text-muted"><?php sn_e("eMail 'From' Address"); ?></label>
<div class="col-sm-9">
<input type="text" id="sn-subscription-from" name="subscription_from" value="<?php echo sn_config("subscription_from"); ?>"
class="form-control" placeholder="<?php sn_e("eMail 'From' Address"); ?>" disabled="disabled" />
</div>
</div>
<div class="form-group row">
<label for="sn-subscription-reply" class="col-sm-3 col-form-label text-muted"><?php sn_e("eMail 'ReplyTo' Address"); ?></label>
<div class="col-sm-9">
<input type="text" id="sn-subscription-reply" name="subscription_reply" value="<?php echo sn_config("subscription_reply"); ?>"
class="form-control" placeholder="<?php sn_e("eMail 'ReplyTo' Address"); ?>" disabled="disabled" />
</div>
</div>
<div class="form-group row">
<label for="sn-subscription-optin" class="col-sm-3 col-form-label text-muted"><?php sn_e("eMail Body (Opt-In)"); ?></label>
<div class="col-sm-9">
<select id="sn-subscription-optin" name="subscription_optin" class="form-control custom-select" disabled="disabled">
<option value="default" <?php sn_selected("subscription_optin", "default"); ?>><?php sn_e("Use default Subscription eMail"); ?></option>
<?php foreach($static AS $key => $value){ ?>
<option value="<?php echo $key; ?>" <?php sn_selected("subscription_optin", $key); ?>><?php sn_e("Page"); ?>: <?php echo $value["title"]; ?></option>
<?php } ?>
</select>
</div>
</div>
<div class="form-group row">
<label for="sn-subscription-ticker" class="col-sm-3 col-form-label text-muted"><?php sn_e("eMail Body (Notification)"); ?></label>
<div class="col-sm-9">
<select id="sn-subscription-ticker" name="subscription_ticker" class="form-control custom-select" disabled="disabled">
<option value="default" <?php sn_selected("subscription_ticker", "default"); ?>><?php sn_e("Use default Notification eMail"); ?></option>
<?php foreach($static AS $key => $value){ ?>
<option value="<?php echo $key; ?>" <?php sn_selected("subscription_ticker", $key); ?>><?php sn_e("Page"); ?>: <?php echo $value["title"]; ?></option>
<?php } ?>
</select>
<small class="form-text text-muted"><?php sn_e("Read more about a custom Notification eMails %s!", array('<a href="#" target="_blank">'.sn__("here").'</a>')); ?></small>
</div>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header text-uppercase pt-3 pb-3 pl-4 pr-4" data-toggle="collapse" data-target="#accordion-strings"><?php sn_e("Strings"); ?></div>
<div id="accordion-strings" class="collapse" data-parent="#accordion-settings">
<div class="card-body">
<div class="form-group row">
<label for="sn-success-1" class="col-sm-3 col-form-label"><?php sn_e("Default Thanks Message"); ?></label>
<div class="col-sm-9">
<input type="text" id="sn-success-1" name="string_success_1" value="<?php echo sn_config("string_success_1"); ?>"
class="form-control" placeholder="<?php echo $SnickerPlugin->dbFields["string_success_1"]; ?>" />
</div>
</div>
<div class="form-group row">
<label for="sn-success-2" class="col-sm-3 col-form-label"><?php sn_e("Thanks Message with Subscription"); ?></label>
<div class="col-sm-9">
<input type="text" id="sn-success-2" name="string_success_2" value="<?php echo sn_config("string_success_2"); ?>"
class="form-control" placeholder="<?php echo $SnickerPlugin->dbFields["string_success_2"]; ?>" />
</div>
</div>
<div class="form-group row">
<label for="sn-success-3" class="col-sm-3 col-form-label"><?php sn_e("Thanks Message for Voting"); ?></label>
<div class="col-sm-9">
<input type="text" id="sn-success-3" name="string_success_3" value="<?php echo sn_config("string_success_3"); ?>"
class="form-control" placeholder="<?php echo $SnickerPlugin->dbFields["string_success_3"]; ?>" />
</div>
</div>
<div class="form-group row">
<label for="sn-error-1" class="col-sm-3 col-form-label"><?php sn_e("Error: Unknown Error, Try again"); ?></label>
<div class="col-sm-9">
<input type="text" id="sn-error-1" name="string_error_1" value="<?php echo sn_config("string_error_1"); ?>"
class="form-control" placeholder="<?php echo $SnickerPlugin->dbFields["string_error_1"]; ?>" />
</div>
</div>
<div class="form-group row">
<label for="sn-error-2" class="col-sm-3 col-form-label"><?php sn_e("Error: Username is invalid"); ?></label>
<div class="col-sm-9">
<input type="text" id="sn-error-2" name="string_error_2" value="<?php echo sn_config("string_error_2"); ?>"
class="form-control" placeholder="<?php echo $SnickerPlugin->dbFields["string_error_2"]; ?>" />
</div>
</div>
<div class="form-group row">
<label for="sn-error-3" class="col-sm-3 col-form-label"><?php sn_e("Error: eMail Address is invalid"); ?></label>
<div class="col-sm-9">
<input type="text" id="sn-error-3" name="string_error_3" value="<?php echo sn_config("string_error_3"); ?>"
class="form-control" placeholder="<?php echo $SnickerPlugin->dbFields["string_error_3"]; ?>" />
</div>
</div>
<div class="form-group row">
<label for="sn-error-4" class="col-sm-3 col-form-label"><?php sn_e("Error: Comment Text is missing"); ?></label>
<div class="col-sm-9">
<input type="text" id="sn-error-4" name="string_error_4" value="<?php echo sn_config("string_error_4"); ?>"
class="form-control" placeholder="<?php echo $SnickerPlugin->dbFields["string_error_4"]; ?>" />
</div>
</div>
<div class="form-group row">
<label for="sn-error-5" class="col-sm-3 col-form-label"><?php sn_e("Error: Comment Title is missing"); ?></label>
<div class="col-sm-9">
<input type="text" id="sn-error-5" name="string_error_5" value="<?php echo sn_config("string_error_5"); ?>"
class="form-control" placeholder="<?php echo $SnickerPlugin->dbFields["string_error_5"]; ?>" />
</div>
</div>
<div class="form-group row">
<label for="sn-error-6" class="col-sm-3 col-form-label"><?php sn_e("Error: Terms not accepted"); ?></label>
<div class="col-sm-9">
<input type="text" id="sn-error-6" name="string_error_6" value="<?php echo sn_config("string_error_6"); ?>"
class="form-control" placeholder="<?php echo $SnickerPlugin->dbFields["string_error_6"]; ?>" />
</div>
</div>
<div class="form-group row">
<label for="sn-error-7" class="col-sm-3 col-form-label"><?php sn_e("Error: Marked as SPAM"); ?></label>
<div class="col-sm-9">
<input type="text" id="sn-error-7" name="string_error_7" value="<?php echo sn_config("string_error_7"); ?>"
class="form-control" placeholder="<?php echo $SnickerPlugin->dbFields["string_error_7"]; ?>" />
</div>
</div>
<div class="form-group row">
<label for="sn-error-8" class="col-sm-3 col-form-label"><?php sn_e("Error: Already Voted"); ?></label>
<div class="col-sm-9">
<input type="text" id="sn-error-8" name="string_error_8" value="<?php echo sn_config("string_error_8"); ?>"
class="form-control" placeholder="<?php echo $SnickerPlugin->dbFields["string_error_8"]; ?>" />
</div>
</div>
<div class="form-group row">
<label for="sn-terms-of-use" class="col-sm-3 col-form-label"><?php sn_e("Terms of Use"); ?></label>
<div class="col-sm-9">
<input type="text" id="sn-terms-of-use" name="string_terms_of_use" value="<?php echo sn_config("string_terms_of_use"); ?>"
class="form-control" placeholder="<?php echo $SnickerPlugin->dbFields["string_terms_of_use"]; ?>" />
</div>
</div>
</div>
</div>
</div>
</div>
<div class="card shadow-sm mt-4 mb-4">
<div class="card-body">
<button class="btn btn-primary" name="snicker" value="configure"><?php sn_e("Save Settings"); ?></button>
</div>
</div>
</form>
</div>

133
admin/index-users.php

@ -0,0 +1,133 @@
<?php
/*
| Snicker The first native FlatFile Comment Plugin 4 Bludit
| @file ./admin/index-users.php
| @author SamBrishes <sam@pytes.net>
| @version 0.1.2 [0.1.0] - Alpha
|
| @website https://github.com/pytesNET/snicker
| @license X11 / MIT License
| @copyright Copyright © 2019 SamBrishes, pytesNET <info@pytes.net>
*/
if(!defined("BLUDIT")){ die("Go directly to Jail. Do not pass Go. Do not collect 200 Cookies!"); }
global $SnickerUsers;
// Get Data
$page = max((isset($_GET["page"])? (int) $_GET["page"]: 1), 1);
$limit = sn_config("frontend_per_page");
$total = count($SnickerUsers->db);
// Get Users
$search = null;
if(isset($_GET["view"]) && $_GET["view"] === "users"){
$page = 1;
$limit = -1;
$search = isset($_GET["search"])? $_GET["search"]: null;
}
$users = $SnickerUsers->getList($search, $page, $limit);
// Link
$link = DOMAIN_ADMIN . "snicker?page=%d&tab=users#users";
?>
<div id="snicker-users" class="tab-pane">
<div class="card shadow-sm" style="margin: 1.5rem 0;">
<div class="card-body">
<div class="row">
<form class="col-sm-6" method="get" action="<?php echo DOMAIN_ADMIN; ?>snicker#users">
<div class="form-row align-items-center">
<div class="col-sm-8">
<input type="text" name="search" value="<?php echo $search; ?>" class="form-control" placeholder="<?php sn_e("Username or eMail Address"); ?>" />
</div>
<div class="col-sm-4">
<button class="btn btn-primary" name="view" value="users"><?php sn_e("Search Users"); ?></button>
</div>
</div>
</form>
<div class="col-sm-6 text-right">
<?php if($total > $limit){ ?>
<div class="btn-group btn-group-pagination">
<?php if($page <= 1){ ?>
<span class="btn btn-secondary disabled">&laquo;</span>
<span class="btn btn-secondary disabled">&lsaquo;</span>
<?php } else { ?>
<a href="<?php printf($link, 1); ?>" class="btn btn-secondary">&laquo;</a>
<a href="<?php printf($link, $page-1); ?>" class="btn btn-secondary">&lsaquo;</a>
<?php } ?>
<?php if(($page * $limit) < $total){ ?>
<a href="<?php printf($link, $page+1); ?>" class="btn btn-secondary">&rsaquo;</a>
<a href="<?php printf($link, ceil($total / $limit)); ?>" class="btn btn-secondary">&raquo;</a>
<?php } else { ?>
<span class="btn btn-secondary disabled">&rsaquo;</span>
<span class="btn btn-secondary disabled">&raquo;</span>
<?php } ?>
</div>
<?php } ?>
</div>
</div>
</div>
</div>
<?php if(!$users || count($users) === 0){ ?>
<div class="row justify-content-md-center">
<div class="col-sm-6">
<div class="card w-100 shadow-sm bg-light">
<div class="card-body text-center p-4"><i><?php sn_e("No Users available"); ?></i></div>
</div>
</div>
</div>
<?php } else { ?>
<?php $link = DOMAIN_ADMIN . "snicker?action=snicker&snicker=users&uuid=%s&handle=%s&tokenCSRF=" . $security->getTokenCSRF(); ?>
<table class="table table-bordered table-hover-light shadow-sm mt-3">
<?php foreach(array("thead", "tfoot") AS $tag){ ?>
<<?php echo $tag; ?>>
<tr class="thead-light">
<th width="38%" class="border-0 p-3 text-uppercase text-muted"><?php sn_e("Username"); ?></th>
<th width="15%" class="border-0 p-3 text-uppercase text-muted"><?php sn_e("eMail"); ?></th>
<th width="22%" class="border-0 p-3 text-uppercase text-muted"><?php sn_e("Comments"); ?></th>
<th width="25%" class="border-0 p-3 text-uppercase text-muted text-center"><?php sn_e("Actions"); ?></th>
</tr>
</<?php echo $tag; ?>>
<?php } ?>
<tbody class="shadow-sm-both">
<?php foreach($users AS $uuid => $user){ ?>
<tr>
<td class="p-3">
<?php echo $user["username"]; ?>
</td>
<td class="p-3">
<?php echo $user["email"]; ?>
</td>
<td class="text-center align-middle pt-2 pb-2 pl-1 pr-1">
<a href="<?php echo DOMAIN_ADMIN; ?>snicker?view=user&user=<?php echo $uuid; ?>">
<?php echo count(isset($user["comments"])? $user["comments"]: array()); ?>
<?php sn_e("Comments"); ?>
</a>
</td>
<td class="text-center align-middle pt-2 pb-2 pl-1 pr-1">
<div class="btn-group">
<button class="btn btn-outline-secondary btn-sm dropdown-toggle" data-toggle="dropdown">
<?php sn_e("Handle"); ?>
</button>
<div class="dropdown-menu dropdown-menu-right">
<a class="dropdown-item text-danger" href="<?php printf($link, $uuid, "delete"); ?>&anonymize=true"><?php sn_e("Delete (Anonymize)"); ?></a>
<a class="dropdown-item text-danger" href="<?php printf($link, $uuid, "delete"); ?>&anonymize=false"><?php sn_e("Delete (Completely)"); ?></a>
<div class="dropdown-divider"></div>
<?php if($user["blocked"]){ ?>
<a class="dropdown-item" href="<?php printf($link, $uuid, "unblock"); ?>"><?php sn_e("Unblock User"); ?></a>
<?php } else { ?>
<a class="dropdown-item" href="<?php printf($link, $uuid, "block"); ?>"><?php sn_e("Block User"); ?></a>
<?php } ?>
</div>
</div>
</td>
</tr>
<?php } ?>
</tbody>
</table>
<?php } ?>
</div>

85
admin/index.php

@ -0,0 +1,85 @@
<?php
/*
| Snicker The first native FlatFile Comment Plugin 4 Bludit
| @file ./admin/index.php
| @author SamBrishes <sam@pytes.net>
| @version 0.1.2 [0.1.0] - Alpha
|
| @website https://github.com/pytesNET/snicker
| @license X11 / MIT License
| @copyright Copyright © 2019 SamBrishes, pytesNET <info@pytes.net>
*/
if(!defined("BLUDIT")){ die("Go directly to Jail. Do not pass Go. Do not collect 200 Cookies!"); }
global $L, $Snicker;
// Pending Counter
$count = count($Snicker->getIndex("pending"));
$count = ($count > 99)? "99+": $count;
$spam = count($Snicker->getIndex("spam"));
// Tab Strings
$strings = array(
"pending" => sn__("Pending"),
"approved" => sn__("Approved"),
"rejected" => sn__("Rejected"),
"spam" => sn__("Spam"),
"search" => sn__("Search Comments"),
"single" => sn__("Single Comment"),
"uuid" => sn__("Page Comments"),
"user" => sn__("User Comments")
);
// Current Tab
$view = "index";
if(isset($_GET["view"]) && in_array($_GET["view"], array("search", "single", "uuid", "user"))){
$view = $current = $_GET["view"];
$tabs = array($view);
} else {
$current = isset($_GET["tab"])? $_GET["tab"]: "pending";
$tabs = array("pending", "approved", "rejected", "spam");
}
?>
<h2 class="mt-0 mb-3">
<span class="oi oi-comment-square" style="font-size: 0.7em;"></span> Snicker <?php sn_e("Comments"); ?>
</h2>
<ul class="nav nav-pills" data-handle="tabs">
<?php foreach($tabs AS $tab){ ?>
<?php $class = "nav-link nav-{$tab}" . ($current === $tab? " active": ""); ?>
<li class="nav-item">
<a id="<?php echo $tab; ?>-tab" href="#snicker-<?php echo $tab; ?>" class="<?php echo $class; ?>" data-toggle="tab">
<?php
echo $strings[$tab];
if($tab === "pending" && !empty($count)){
?> <span class="badge badge-primary"><?php echo $count; ?></span><?php
}
if($tab === "spam" && !empty($spam)){
?> <span class="badge badge-danger"><?php echo $spam; ?></span><?php
}
?>
</a>
</li>
<?php } ?>
<li class="nav-item flex-grow-1"></li>
<li class="nav-item mr-2">
<a id="users-tab" href="#snicker-users" class="nav-link nav-config" data-toggle="tab">
<span class="oi oi-people"></span> <?php sn_e("Users"); ?>
</a>
</li>
<li class="nav-item">
<a id="configure-tab" href="#snicker-configure" class="nav-link nav-config" data-toggle="tab">
<span class="oi oi-cog"></span> <?php sn_e("Configuration"); ?>
</a>
</li>
</ul>
<div class="tab-content">
<?php
include "index-comments.php";
include "index-users.php";
include "index-config.php";
?>
</div>

125
admin/js/admin.snicker.js

@ -0,0 +1,125 @@
/*
| Snicker The first native FlatFile Comment Plugin 4 Bludit
| @file ./admin/js/admin.snicker.js
| @author SamBrishes <sam@pytes.net>
| @version 0.1.2 [0.1.0] - Alpha
|
| @website https://github.com/pytesNET/snicker
| @license X11 / MIT License
| @copyright Copyright © 2019 SamBrishes, pytesNET <info@pytes.net>
*/
;(function(root){
"use strict";
var w = root, d = root.document;
/*
| HELPER :: LOOP
| @since 0.1.0
*/
var each = function(elements, callback){
if(elements instanceof HTMLElement){
callback.call(elements, elements);
} else if(elements.length && elements.length > 0){
for(var l = elements.length, i = 0; i < l; i++){
callback.call(elements[i], elements[i], i);
}
}
};
// Ready?
d.addEventListener("DOMContentLoaded", function(){
"use strict";
/*
| BOOTSTRAP POPOVER
| @since 0.1.0
*/
jQuery('[data-toggle="popover"]').popover({
content: function(){
var data = d.querySelector(this.getAttribute("data-target"));
return data.innerHTML;
},
html: true
}).click(function(event){
event.preventDefault();
}).on("inserted.bs.popover", function(event){
d.querySelector("#" + this.getAttribute("aria-describedby")).style.width = "410px";
d.querySelector("#" + this.getAttribute("aria-describedby")).style.maxWidth = "410px";
})
/*
| MAIN MENU LINK HANDLER
| @since 0.1.0
*/
var mainMenu = d.querySelector("[data-handle='tabs']");
if(mainMenu){
var menuLink = function(link){
if(typeof(link) === "undefined"){
if(w.location.hash.length == 0){
var link = mainMenu.querySelector("li a");
} else {
var link = mainMenu.querySelector("[href='#snicker-" + w.location.hash.substr(1) + "']");
}
}
if(!(link instanceof Element)){
return false;
}
// Handle
if(link && !link.classList.contains("active")){
link.click();
}
if(link){
w.location.hash = link.getAttribute("href").replace("snicker-", "");
}
};
// Current Hash Handler
if(w.location.hash.length > 0){
menuLink();
}
// Local Hash Handler
each(mainMenu.querySelectorAll("li > a"), function(){
this.addEventListener("click", function(event){
menuLink(this);
});
});
// History Hash Handler
w.onhashchange = function(event){
if(w.location.hash.length == 0){
var link = mainMenu.querySelector("li a");
} else {
var link = mainMenu.querySelector("[href='#snicker-" + w.location.hash.substr(1) + "']");
}
menuLink(link);
};
}
/*
| MAIN MENU LINK HANDLER
| @since 0.1.1
*/
var avatar = document.getElementById("sn-avatar"),
gravatar = document.getElementById("sn-gravatar");
if(avatar && gravatar){
var GravatarOption = function(){
console.log(avatar.value)
if(avatar.value == "gravatar"){
gravatar.disabled = false;
document.querySelector("label[for='sn-gravatar']").classList.remove("text-muted");
} else {
gravatar.disabled = true;
document.querySelector("label[for='sn-gravatar']").classList.add("text-muted");
}
};
avatar.addEventListener("change", function(){
GravatarOption();
});
GravatarOption();
}
});
})(window);

717
includes/Gregwar/Captcha/CaptchaBuilder.php

@ -0,0 +1,717 @@
<?php
namespace Gregwar\Captcha;
use \Exception;
/**
* Builds a new captcha image
* Uses the fingerprint parameter, if one is passed, to generate the same image
*
* @author Gregwar <g.passault@gmail.com>
* @author Jeremy Livingston <jeremy.j.livingston@gmail.com>
*/
class CaptchaBuilder implements CaptchaBuilderInterface
{
/**
* @var array
*/
protected $fingerprint = array();
/**
* @var bool
*/
protected $useFingerprint = false;
/**
* @var array
*/
protected $textColor = array();
/**
* @var array
*/
protected $backgroundColor = null;
/**
* @var array
*/
protected $backgroundImages = array();
/**
* @var resource
*/
protected $contents = null;
/**
* @var string
*/
protected $phrase = null;
/**
* @var PhraseBuilderInterface
*/
protected $builder;
/**
* @var bool
*/
protected $distortion = true;
/**
* The maximum number of lines to draw in front of
* the image. null - use default algorithm
*/
protected $maxFrontLines = null;
/**
* The maximum number of lines to draw behind
* the image. null - use default algorithm
*/
protected $maxBehindLines = null;
/**
* The maximum angle of char
*/
protected $maxAngle = 8;
/**
* The maximum offset of char
*/
protected $maxOffset = 5;
/**
* Is the interpolation enabled ?
*
* @var bool
*/
protected $interpolation = true;
/**
* Ignore all effects
*
* @var bool
*/
protected $ignoreAllEffects = false;
/**
* Allowed image types for the background images
*
* @var array
*/
protected $allowedBackgroundImageTypes = array('image/png', 'image/jpeg', 'image/gif');
/**
* The image contents
*/
public function getContents()
{
return $this->contents;
}
/**
* Enable/Disables the interpolation
*
* @param $interpolate bool True to enable, false to disable
*
* @return CaptchaBuilder
*/
public function setInterpolation($interpolate = true)
{
$this->interpolation = $interpolate;
return $this;
}
/**
* Temporary dir, for OCR check
*/
public $tempDir = 'temp/';
public function __construct($phrase = null, PhraseBuilderInterface $builder = null)
{
if ($builder === null) {
$this->builder = new PhraseBuilder;
} else {
$this->builder = $builder;
}
$this->phrase = is_string($phrase) ? $phrase : $this->builder->build($phrase);
}
/**
* Setting the phrase
*/
public function setPhrase($phrase)
{
$this->phrase = (string) $phrase;
}
/**
* Enables/disable distortion
*/
public function setDistortion($distortion)
{
$this->distortion = (bool) $distortion;
return $this;
}
public function setMaxBehindLines($maxBehindLines)
{
$this->maxBehindLines = $maxBehindLines;
return $this;
}
public function setMaxFrontLines($maxFrontLines)
{
$this->maxFrontLines = $maxFrontLines;
return $this;
}
public function setMaxAngle($maxAngle)
{
$this->maxAngle = $maxAngle;
return $this;
}
public function setMaxOffset($maxOffset)
{
$this->maxOffset = $maxOffset;
return $this;
}
/**
* Gets the captcha phrase
*/
public function getPhrase()
{
return $this->phrase;
}
/**
* Returns true if the given phrase is good
*/
public function testPhrase($phrase)
{
return ($this->builder->niceize($phrase) == $this->builder->niceize($this->getPhrase()));
}
/**
* Instantiation
*/
public static function create($phrase = null)
{
return new self($phrase);
}
/**
* Sets the text color to use
*/
public function setTextColor($r, $g, $b)
{
$this->textColor = array($r, $g, $b);
return $this;
}
/**
* Sets the background color to use
*/
public function setBackgroundColor($r, $g, $b)
{
$this->backgroundColor = array($r, $g, $b);
return $this;
}
/**
* Sets the ignoreAllEffects value
*
* @param bool $ignoreAllEffects
* @return CaptchaBuilder
*/
public function setIgnoreAllEffects($ignoreAllEffects)
{
$this->ignoreAllEffects = $ignoreAllEffects;
return $this;
}
/**
* Sets the list of background images to use (one image is randomly selected)
*/
public function setBackgroundImages(array $backgroundImages)
{
$this->backgroundImages = $backgroundImages;
return $this;
}
/**
* Draw lines over the image
*/
protected function drawLine($image, $width, $height, $tcol = null)
{
if ($tcol === null) {
$tcol = imagecolorallocate($image, $this->rand(100, 255), $this->rand(100, 255), $this->rand(100, 255));
}
if ($this->rand(0, 1)) { // Horizontal
$Xa = $this->rand(0, $width/2);
$Ya = $this->rand(0, $height);
$Xb = $this->rand($width/2, $width);
$Yb = $this->rand(0, $height);
} else { // Vertical
$Xa = $this->rand(0, $width);
$Ya = $this->rand(0, $height/2);
$Xb = $this->rand(0, $width);
$Yb = $this->rand($height/2, $height);
}
imagesetthickness($image, $this->rand(1, 3));
imageline($image, $Xa, $Ya, $Xb, $Yb, $tcol);
}
/**
* Apply some post effects
*/
protected function postEffect($image)
{
if (!function_exists('imagefilter')) {
return;
}
if ($this->backgroundColor != null || $this->textColor != null) {
return;
}
// Negate ?
if ($this->rand(0, 1) == 0) {
imagefilter($image, IMG_FILTER_NEGATE);
}
// Edge ?
if ($this->rand(0, 10) == 0) {
imagefilter($image, IMG_FILTER_EDGEDETECT);
}
// Contrast
imagefilter($image, IMG_FILTER_CONTRAST, $this->rand(-50, 10));
// Colorize
if ($this->rand(0, 5) == 0) {
imagefilter($image, IMG_FILTER_COLORIZE, $this->rand(-80, 50), $this->rand(-80, 50), $this->rand(-80, 50));
}
}
/**
* Writes the phrase on the image
*/
protected function writePhrase($image, $phrase, $font, $width, $height)
{
$length = mb_strlen($phrase);
if ($length === 0) {
return \imagecolorallocate($image, 0, 0, 0);
}
// Gets the text size and start position
$size = $width / $length - $this->rand(0, 3) - 1;
$box = \imagettfbbox($size, 0, $font, $phrase);
$textWidth = $box[2] - $box[0];
$textHeight = $box[1] - $box[7];
$x = ($width - $textWidth) / 2;
$y = ($height - $textHeight) / 2 + $size;
if (!$this->textColor) {
$textColor = array($this->rand(0, 150), $this->rand(0, 150), $this->rand(0, 150));
} else {
$textColor = $this->textColor;
}
$col = \imagecolorallocate($image, $textColor[0], $textColor[1], $textColor[2]);
// Write the letters one by one, with random angle
for ($i=0; $i<$length; $i++) {
$symbol = mb_substr($phrase, $i, 1);
$box = \imagettfbbox($size, 0, $font, $symbol);
$w = $box[2] - $box[0];
$angle = $this->rand(-$this->maxAngle, $this->maxAngle);
$offset = $this->rand(-$this->maxOffset, $this->maxOffset);
\imagettftext($image, $size, $angle, $x, $y + $offset, $col, $font, $symbol);
$x += $w;
}
return $col;
}
/**
* Try to read the code against an OCR
*/
public function isOCRReadable()
{
if (!is_dir($this->tempDir)) {
@mkdir($this->tempDir, 0755, true);
}
$tempj = $this->tempDir . uniqid('captcha', true) . '.jpg';
$tempp = $this->tempDir . uniqid('captcha', true) . '.pgm';
$this->save($tempj);
shell_exec("convert $tempj $tempp");
$value = trim(strtolower(shell_exec("ocrad $tempp")));
@unlink($tempj);
@unlink($tempp);
return $this->testPhrase($value);
}
/**
* Builds while the code is readable against an OCR
*/
public function buildAgainstOCR($width = 150, $height = 40, $font = null, $fingerprint = null)
{
do {
$this->build($width, $height, $font, $fingerprint);
} while ($this->isOCRReadable());
}
/**
* Generate the image
*/
public function build($width = 150, $height = 40, $font = null, $fingerprint = null)
{
if (null !== $fingerprint) {
$this->fingerprint = $fingerprint;
$this->useFingerprint = true;
} else {
$this->fingerprint = array();
$this->useFingerprint = false;
}
if ($font === null) {
$font = __DIR__ . '/Font/captcha'.$this->rand(0, 5).'.ttf';
}
if (empty($this->backgroundImages)) {
// if background images list is not set, use a color fill as a background
$image = imagecreatetruecolor($width, $height);
if ($this->backgroundColor == null) {
$bg = imagecolorallocate($image, $this->rand(200, 255), $this->rand(200, 255), $this->rand(200, 255));
} else {
$color = $this->backgroundColor;
$bg = imagecolorallocate($image, $color[0], $color[1], $color[2]);
}
$this->background = $bg;
imagefill($image, 0, 0, $bg);
} else {
// use a random background image
$randomBackgroundImage = $this->backgroundImages[rand(0, count($this->backgroundImages)-1)];
$imageType = $this->validateBackgroundImage($randomBackgroundImage);
$image = $this->createBackgroundImageFromType($randomBackgroundImage, $imageType);
}
// Apply effects
if (!$this->ignoreAllEffects) {
$square = $width * $height;
$effects = $this->rand($square/3000, $square/2000);
// set the maximum number of lines to draw in front of the text
if ($this->maxBehindLines != null && $this->maxBehindLines > 0) {
$effects = min($this->maxBehindLines, $effects);
}
if ($this->maxBehindLines !== 0) {
for ($e = 0; $e < $effects; $e++) {
$this->drawLine($image, $width, $height);
}
}
}
// Write CAPTCHA text
$color = $this->writePhrase($image, $this->phrase, $font, $width, $height);
// Apply effects
if (!$this->ignoreAllEffects) {
$square = $width * $height;
$effects = $this->rand($square/3000, $square/2000);
// set the maximum number of lines to draw in front of the text
if ($this->maxFrontLines != null && $this->maxFrontLines > 0) {
$effects = min($this->maxFrontLines, $effects);
}
if ($this->maxFrontLines !== 0) {
for ($e = 0; $e < $effects; $e++) {
$this->drawLine($image, $width, $height, $color);
}
}
}
// Distort the image
if ($this->distortion && !$this->ignoreAllEffects) {
$image = $this->distort($image, $width, $height, $bg);
}
// Post effects
if (!$this->ignoreAllEffects) {
$this->postEffect($image);
}
$this->contents = $image;
return $this;
}
/**
* Distorts the image
*/
public function distort($image, $width, $height, $bg)
{
$contents = imagecreatetruecolor($width, $height);
$X = $this->rand(0, $width);
$Y = $this->rand(0, $height);
$phase = $this->rand(0, 10);
$scale = 1.1 + $this->rand(0, 10000) / 30000;
for ($x = 0; $x < $width; $x++) {
for ($y = 0; $y < $height; $y++) {
$Vx = $x - $X;
$Vy = $y - $Y;
$Vn = sqrt($Vx * $Vx + $Vy * $Vy);
if ($Vn != 0) {
$Vn2 = $Vn + 4 * sin($Vn / 30);
$nX = $X + ($Vx * $Vn2 / $Vn);
$nY = $Y + ($Vy * $Vn2 / $Vn);
} else {
$nX = $X;
$nY = $Y;
}
$nY = $nY + $scale * sin($phase + $nX * 0.2);
if ($this->interpolation) {
$p = $this->interpolate(
$nX - floor($nX),
$nY - floor($nY),
$this->getCol($image, floor($nX), floor($nY), $bg),
$this->getCol($image, ceil($nX), floor($nY), $bg),
$this->getCol($image, floor($nX), ceil($nY), $bg),
$this->getCol($image, ceil($nX), ceil($nY), $bg)
);
} else {
$p = $this->getCol($image, round($nX), round($nY), $bg);
}
if ($p == 0) {
$p = $bg;
}
imagesetpixel($contents, $x, $y, $p);
}
}
return $contents;
}
/**
* Saves the Captcha to a jpeg file
*/
public function save($filename, $quality = 90)
{
imagejpeg($this->contents, $filename, $quality);
}
/**
* Gets the image GD
*/
public function getGd()
{
return $this->contents;
}
/**
* Gets the image contents
*/
public function get($quality = 90)
{
ob_start();
$this->output($quality);
return ob_get_clean();
}
/**
* Gets the HTML inline base64
*/
public function inline($quality = 90)
{
return 'data:image/jpeg;base64,' . base64_encode($this->get($quality));
}
/**
* Outputs the image
*/
public function output($quality = 90)
{
imagejpeg($this->contents, null, $quality);
}
/**
* @return array
*/
public function getFingerprint()
{
return $this->fingerprint;
}
/**
* Returns a random number or the next number in the
* fingerprint
*/
protected function rand($min, $max)
{
if (!is_array($this->fingerprint)) {
$this->fingerprint = array();
}
if ($this->useFingerprint) {
$value = current($this->fingerprint);
next($this->fingerprint);
} else {
$value = mt_rand($min, $max);
$this->fingerprint[] = $value;
}
return $value;
}
/**
* @param $x
* @param $y
* @param $nw
* @param $ne
* @param $sw
* @param $se
*
* @return int
*/
protected function interpolate($x, $y, $nw, $ne, $sw, $se)
{
list($r0, $g0, $b0) = $this->getRGB($nw);
list($r1, $g1, $b1) = $this->getRGB($ne);
list($r2, $g2, $b2) = $this->getRGB($sw);
list($r3, $g3, $b3) = $this->getRGB($se);
$cx = 1.0 - $x;
$cy = 1.0 - $y;
$m0 = $cx * $r0 + $x * $r1;
$m1 = $cx * $r2 + $x * $r3;
$r = (int) ($cy * $m0 + $y * $m1);
$m0 = $cx * $g0 + $x * $g1;
$m1 = $cx * $g2 + $x * $g3;
$g = (int) ($cy * $m0 + $y * $m1);
$m0 = $cx * $b0 + $x * $b1;
$m1 = $cx * $b2 + $x * $b3;
$b = (int) ($cy * $m0 + $y * $m1);
return ($r << 16) | ($g << 8) | $b;
}
/**
* @param $image
* @param $x
* @param $y
*
* @return int
*/
protected function getCol($image, $x, $y, $background)
{
$L = imagesx($image);
$H = imagesy($image);
if ($x < 0 || $x >= $L || $y < 0 || $y >= $H) {
return $background;
}
return imagecolorat($image, $x, $y);
}
/**
* @param $col
*
* @return array
*/
protected function getRGB($col)
{
return array(
(int) ($col >> 16) & 0xff,
(int) ($col >> 8) & 0xff,
(int) ($col) & 0xff,
);
}
/**
* Validate the background image path. Return the image type if valid
*
* @param string $backgroundImage
* @return string
* @throws Exception
*/
protected function validateBackgroundImage($backgroundImage)
{
// check if file exists
if (!file_exists($backgroundImage)) {
$backgroundImageExploded = explode('/', $backgroundImage);
$imageFileName = count($backgroundImageExploded) > 1? $backgroundImageExploded[count($backgroundImageExploded)-1] : $backgroundImage;
throw new Exception('Invalid background image: ' . $imageFileName);
}
// check image type
$finfo = finfo_open(FILEINFO_MIME_TYPE); // return mime type ala mimetype extension
$imageType = finfo_file($finfo, $backgroundImage);
finfo_close($finfo);
if (!in_array($imageType, $this->allowedBackgroundImageTypes)) {
throw new Exception('Invalid background image type! Allowed types are: ' . join(', ', $this->allowedBackgroundImageTypes));
}
return $imageType;
}
/**
* Create background image from type
*
* @param string $backgroundImage
* @param string $imageType
* @return resource
* @throws Exception
*/
protected function createBackgroundImageFromType($backgroundImage, $imageType)
{
switch ($imageType) {
case 'image/jpeg':
$image = imagecreatefromjpeg($backgroundImage);
break;
case 'image/png':
$image = imagecreatefrompng($backgroundImage);
break;
case 'image/gif':
$image = imagecreatefromgif($backgroundImage);
break;
default:
throw new Exception('Not supported file type for background image!');
break;
}
return $image;
}
}

29
includes/Gregwar/Captcha/CaptchaBuilderInterface.php

@ -0,0 +1,29 @@
<?php
namespace Gregwar\Captcha;
/**
* A Captcha builder
*/
interface CaptchaBuilderInterface
{
/**
* Builds the code
*/
public function build($width, $height, $font, $fingerprint);
/**
* Saves the code to a file
*/
public function save($filename, $quality);
/**
* Gets the image contents
*/
public function get($quality);
/**
* Outputs the image
*/
public function output($quality);
}

BIN
includes/Gregwar/Captcha/Font/captcha0.ttf

Binary file not shown.

BIN
includes/Gregwar/Captcha/Font/captcha1.ttf

Binary file not shown.

BIN
includes/Gregwar/Captcha/Font/captcha2.ttf

Binary file not shown.

BIN
includes/Gregwar/Captcha/Font/captcha3.ttf

Binary file not shown.

BIN
includes/Gregwar/Captcha/Font/captcha4.ttf

Binary file not shown.

BIN
includes/Gregwar/Captcha/Font/captcha5.ttf

Binary file not shown.

105
includes/Gregwar/Captcha/ImageFileHandler.php

@ -0,0 +1,105 @@
<?php
namespace Gregwar\Captcha;
use Symfony\Component\Finder\Finder;
/**
* Handles actions related to captcha image files including saving and garbage collection
*
* @author Gregwar <g.passault@gmail.com>
* @author Jeremy Livingston <jeremy@quizzle.com>
*/
class ImageFileHandler
{
/**
* Name of folder for captcha images
* @var string
*/
protected $imageFolder;
/**
* Absolute path to public web folder
* @var string
*/
protected $webPath;
/**
* Frequency of garbage collection in fractions of 1
* @var int
*/
protected $gcFreq;
/**
* Maximum age of images in minutes
* @var int
*/
protected $expiration;
/**
* @param $imageFolder
* @param $webPath
* @param $gcFreq
* @param $expiration
*/
public function __construct($imageFolder, $webPath, $gcFreq, $expiration)
{
$this->imageFolder = $imageFolder;
$this->webPath = $webPath;
$this->gcFreq = $gcFreq;
$this->expiration = $expiration;
}
/**
* Saves the provided image content as a file
*
* @param string $contents
*
* @return string
*/
public function saveAsFile($contents)
{
$this->createFolderIfMissing();
$filename = md5(uniqid()) . '.jpg';
$filePath = $this->webPath . '/' . $this->imageFolder . '/' . $filename;
imagejpeg($contents, $filePath, 15);
return '/' . $this->imageFolder . '/' . $filename;
}
/**
* Randomly runs garbage collection on the image directory
*
* @return bool
*/
public function collectGarbage()
{
if (!mt_rand(1, $this->gcFreq) == 1) {
return false;
}
$this->createFolderIfMissing();
$finder = new Finder();
$criteria = sprintf('<= now - %s minutes', $this->expiration);
$finder->in($this->webPath . '/' . $this->imageFolder)
->date($criteria);
foreach ($finder->files() as $file) {
unlink($file->getPathname());
}
return true;
}
/**
* Creates the folder if it doesn't exist
*/
protected function createFolderIfMissing()
{
if (!file_exists($this->webPath . '/' . $this->imageFolder)) {
mkdir($this->webPath . '/' . $this->imageFolder, 0755);
}
}
}

59
includes/Gregwar/Captcha/PhraseBuilder.php

@ -0,0 +1,59 @@
<?php
namespace Gregwar\Captcha;
/**
* Generates random phrase
*
* @author Gregwar <g.passault@gmail.com>
*/
class PhraseBuilder implements PhraseBuilderInterface
{
/**
* @var int
*/
public $length;
/**
* @var string
*/
public $charset;
/**
* Constructs a PhraseBuilder with given parameters
*/
public function __construct($length = 5, $charset = 'abcdefghijklmnpqrstuvwxyz123456789')
{
$this->length = $length;
$this->charset = $charset;
}
/**
* Generates random phrase of given length with given charset
*/
public function build($length = null, $charset = null)
{
if ($length !== null) {
$this->length = $length;
}
if ($charset !== null) {
$this->charset = $charset;
}
$phrase = '';
$chars = str_split($this->charset);
for ($i = 0; $i < $this->length; $i++) {
$phrase .= $chars[array_rand($chars)];
}
return $phrase;
}
/**
* "Niceize" a code
*/
public function niceize($str)
{
return strtr(strtolower($str), '01', 'ol');
}
}

21
includes/Gregwar/Captcha/PhraseBuilderInterface.php

@ -0,0 +1,21 @@
<?php
namespace Gregwar\Captcha;
/**
* Interface for the PhraseBuilder
*
* @author Gregwar <g.passault@gmail.com>
*/
interface PhraseBuilderInterface
{
/**
* Generates random phrase of given length with given charset
*/
public function build();
/**
* "Niceize" a code
*/
public function niceize($str);
}

250
includes/Identicon/Generator/BaseGenerator.php

@ -0,0 +1,250 @@
<?php
namespace Identicon\Generator;
use Exception;
/**
* @author Benjamin Laugueux <benjamin@yzalis.com>
*/
class BaseGenerator
{
/**
* @var mixed
*/
protected $generatedImage;
/**
* @var array
*/
protected $color;
/**
* @var array
*/
protected $backgroundColor;
/**
* @var int
*/
protected $size;
/**
* @var int
*/
protected $pixelRatio;
/**
* @var string
*/
private $hash;
/**
* @var array
*/
private $arrayOfSquare = [];
/**
* Set the image color.
*
* @param string|array $color The color in hexa (3 or 6 chars) or rgb array
*
* @return $this
*/
public function setColor($color)
{
if (null === $color) {
return $this;
}
$this->color = $this->convertColor($color);
return $this;
}
/**
* Set the image background color.
*
* @param string|array $backgroundColor The color in hexa (3 or 6 chars) or rgb array
*
* @return $this
*/
public function setBackgroundColor($backgroundColor)
{
if (null === $backgroundColor) {
return $this;
}
$this->backgroundColor = $this->convertColor($backgroundColor);
return $this;
}
/**
* @param array|string $color
*
* @return array
*/
private function convertColor($color)
{
if (is_array($color)) {
return $color;
}
if (preg_match('/^#?([a-z\d])([a-z\d])([a-z\d])$/i', $color, $matches)) {
$color = $matches[1].$matches[1];
$color .= $matches[2].$matches[2];
$color .= $matches[3].$matches[3];
}
preg_match('/#?([a-z\d]{2})([a-z\d]{2})([a-z\d]{2})$/i', $color, $matches);
return array_map(function ($value) {
return hexdec($value);
}, array_slice($matches, 1, 3));
}
/**
* Get the color.
*
* @return array
*/
public function getColor()
{
return $this->color;
}
/**
* Get the background color.
*
* @return array
*/
public function getBackgroundColor()
{
return $this->backgroundColor;
}
/**
* Convert the hash into an multidimensional array of boolean.
*
* @return $this
*/
private function convertHashToArrayOfBoolean()
{
preg_match_all('/(\w)(\w)/', $this->hash, $chars);
foreach ($chars[1] as $i => $char) {
$index = (int) ($i / 3);
$data = $this->convertHexaToBoolean($char);
$items = [
0 => [0, 4],
1 => [1, 3],
2 => [2],
];
foreach ($items[$i % 3] as $item) {
$this->arrayOfSquare[$index][$item] = $data;
}
ksort($this->arrayOfSquare[$index]);
}
$this->color = array_map(function ($data) {
return hexdec($data) * 16;
}, array_reverse($chars[1]));
return $this;
}
/**
* Convert an hexadecimal number into a boolean.
*
* @param string $hexa
*
* @return bool
*/
private function convertHexaToBoolean($hexa)
{
return (bool) round(hexdec($hexa) / 10);
}
/**
* @return array
*/
public function getArrayOfSquare()
{
return $this->arrayOfSquare;
}
/**
* Get the identicon string hash.
*
* @return string
*/
public function getHash()
{
return $this->hash;
}
/**
* Generate a hash from the original string.
*
* @param string $string
*
* @throws \Exception
*
* @return $this
*/
public function setString($string)
{
if (null === $string) {
throw new Exception('The string cannot be null.');
}
$this->hash = md5($string);
$this->convertHashToArrayOfBoolean();
return $this;
}
/**
* Set the image size.
*
* @param int $size
*
* @return $this
*/
public function setSize($size)
{
if (null === $size) {
return $this;
}
$this->size = $size;
$this->pixelRatio = (int) round($size / 5);
return $this;
}
/**
* Get the image size.
*
* @return int
*/
public function getSize()
{
return $this->size;
}
/**
* Get the pixel ratio.
*
* @return int
*/
public function getPixelRatio()
{
return $this->pixelRatio;
}
}

90
includes/Identicon/Generator/GdGenerator.php

@ -0,0 +1,90 @@
<?php
namespace Identicon\Generator;
use Exception;
/**
* @author Benjamin Laugueux <benjamin@yzalis.com>
*/
class GdGenerator extends BaseGenerator implements GeneratorInterface
{
/**
* GdGenerator constructor.
*/
public function __construct()
{
if (!extension_loaded('gd') && !extension_loaded('ext-gd')) {
throw new Exception('GD does not appear to be available in your PHP installation. Please try another generator');
}
}
/**
* @return string
*/
public function getMimeType()
{
return 'image/png';
}
/**
* @return $this
*/
private function generateImage()
{
// prepare image
$this->generatedImage = imagecreatetruecolor($this->getPixelRatio() * 5, $this->getPixelRatio() * 5);
$rgbBackgroundColor = $this->getBackgroundColor();
if (null === $rgbBackgroundColor) {
$background = imagecolorallocate($this->generatedImage, 0, 0, 0);
imagecolortransparent($this->generatedImage, $background);
} else {
$background = imagecolorallocate($this->generatedImage, $rgbBackgroundColor[0], $rgbBackgroundColor[1], $rgbBackgroundColor[2]);
imagefill($this->generatedImage, 0, 0, $background);
}
// prepare color
$rgbColor = $this->getColor();
$gdColor = imagecolorallocate($this->generatedImage, $rgbColor[0], $rgbColor[1], $rgbColor[2]);
// draw content
foreach ($this->getArrayOfSquare() as $lineKey => $lineValue) {
foreach ($lineValue as $colKey => $colValue) {
if (true === $colValue) {
imagefilledrectangle($this->generatedImage, $colKey * $this->getPixelRatio(), $lineKey * $this->getPixelRatio(), ($colKey + 1) * $this->getPixelRatio(), ($lineKey + 1) * $this->getPixelRatio(), $gdColor);
}
}
}
return $this;
}
/**
* {@inheritdoc}
*/
public function getImageBinaryData($string, $size = null, $color = null, $backgroundColor = null)
{
ob_start();
imagepng($this->getImageResource($string, $size, $color, $backgroundColor));
$imageData = ob_get_contents();
ob_end_clean();
return $imageData;
}
/**
* {@inheritdoc}
*/
public function getImageResource($string, $size = null, $color = null, $backgroundColor = null)
{
$this
->setString($string)
->setSize($size)
->setColor($color)
->setBackgroundColor($backgroundColor)
->generateImage();
return $this->generatedImage;
}
}

43
includes/Identicon/Generator/GeneratorInterface.php

@ -0,0 +1,43 @@
<?php
namespace Identicon\Generator;
/**
* @author Benjamin Laugueux <benjamin@yzalis.com>
*/
interface GeneratorInterface
{
/**
* @param string $string
* @param int $size
* @param array|string $color
* @param array|string $backgroundColor
*
* @return mixed
*/
public function getImageBinaryData($string, $size = null, $color = null, $backgroundColor = null);
/**
* @param string $string
* @param int $size
* @param array|string $color
* @param array|string $backgroundColor
*
* @return string
*/
public function getImageResource($string, $size = null, $color = null, $backgroundColor = null);
/**
* Return the mime-type of this identicon.
*
* @return string
*/
public function getMimeType();
/**
* Return the color of the created identicon.
*
* @return array
*/
public function getColor();
}

98
includes/Identicon/Generator/ImageMagickGenerator.php

@ -0,0 +1,98 @@
<?php
namespace Identicon\Generator;
use Exception;
use ImagickDraw;
use ImagickPixel;
/**
* @author Francis Chuang <francis.chuang@gmail.com>
*/
class ImageMagickGenerator extends BaseGenerator implements GeneratorInterface
{
/**
* ImageMagickGenerator constructor.
*
* @throws \Exception
*/
public function __construct()
{
if (!extension_loaded('imagick')) {
throw new Exception('ImageMagick does not appear to be avaliable in your PHP installation. Please try another generator');
}
}
/**
* @return string
*/
public function getMimeType()
{
return 'image/png';
}
/**
* @return $this
*/
private function generateImage()
{
$this->generatedImage = new \Imagick();
$rgbBackgroundColor = $this->getBackgroundColor();
if (null === $rgbBackgroundColor) {
$background = 'none';
} else {
$background = new ImagickPixel("rgb($rgbBackgroundColor[0],$rgbBackgroundColor[1],$rgbBackgroundColor[2])");
}
$this->generatedImage->newImage($this->pixelRatio * 5, $this->pixelRatio * 5, $background, 'png');
// prepare color
$rgbColor = $this->getColor();
$color = new ImagickPixel("rgb($rgbColor[0],$rgbColor[1],$rgbColor[2])");
$draw = new ImagickDraw();
$draw->setFillColor($color);
// draw the content
foreach ($this->getArrayOfSquare() as $lineKey => $lineValue) {
foreach ($lineValue as $colKey => $colValue) {
if (true === $colValue) {
$draw->rectangle($colKey * $this->pixelRatio, $lineKey * $this->pixelRatio, ($colKey + 1) * $this->pixelRatio, ($lineKey + 1) * $this->pixelRatio);
}
}
}
$this->generatedImage->drawImage($draw);
return $this;
}
/**
* {@inheritdoc}
*/
public function getImageBinaryData($string, $size = null, $color = null, $backgroundColor = null)
{
ob_start();
echo $this->getImageResource($string, $size, $color, $backgroundColor);
$imageData = ob_get_contents();
ob_end_clean();
return $imageData;
}
/**
* {@inheritdoc}
*/
public function getImageResource($string, $size = null, $color = null, $backgroundColor = null)
{
$this
->setString($string)
->setSize($size)
->setColor($color)
->setBackgroundColor($backgroundColor)
->generateImage();
return $this->generatedImage;
}
}

88
includes/Identicon/Generator/SvgGenerator.php

@ -0,0 +1,88 @@
<?php
namespace Identicon\Generator;
/**
* @author Grummfy <grummfy@gmail.com>
*/
class SvgGenerator extends BaseGenerator implements GeneratorInterface
{
/**
* {@inheritdoc}
*/
public function getMimeType()
{
return 'image/svg+xml';
}
/**
* {@inheritdoc}
*/
public function getImageBinaryData($string, $size = null, $color = null, $backgroundColor = null)
{
return $this->getImageResource($string, $size, $color, $backgroundColor);
}
/**
* {@inheritdoc}
*/
public function getImageResource($string, $size = null, $color = null, $backgroundColor = null)
{
$this
->setString($string)
->setSize($size)
->setColor($color)
->setBackgroundColor($backgroundColor)
->_generateImage();
return $this->generatedImage;
}
/**
* @return $this
*/
protected function _generateImage()
{
// prepare image
$w = $this->getPixelRatio() * 5;
$h = $this->getPixelRatio() * 5;
$svg = '<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="'.$w.'" height="'.$h.'">';
$backgroundColor = '#FFFFFF';
$rgbBackgroundColor = $this->getBackgroundColor();
if (!is_null($rgbBackgroundColor)) {
$backgroundColor = $this->_toUnderstandableColor($rgbBackgroundColor);
}
$svg .= '<rect width="'.$w.'" height="'.$h.'" style="fill:'.$backgroundColor.';stroke-width:1;stroke:'.$backgroundColor.'"/>';
$rgbColor = $this->_toUnderstandableColor($this->getColor());
// draw content
foreach ($this->getArrayOfSquare() as $lineKey => $lineValue) {
foreach ($lineValue as $colKey => $colValue) {
if (true === $colValue) {
$svg .= '<rect x="'.$colKey * $this->getPixelRatio().'" y="'.$lineKey * $this->getPixelRatio().'" width="'.($this->getPixelRatio()).'" height="'.$this->getPixelRatio().'" style="fill:'.$rgbColor.';stroke-width:0;"/>';
}
}
}
$svg .= '</svg>';
$this->generatedImage = $svg;
return $this;
}
/**
* @param array|string $color
*
* @return string
*/
protected function _toUnderstandableColor($color)
{
if (is_array($color)) {
return 'rgb('.implode(', ', $color).')';
}
return $color;
}
}

123
includes/Identicon/Identicon.php

@ -0,0 +1,123 @@
<?php
namespace Identicon;
use Identicon\Generator\GdGenerator;
use Identicon\Generator\GeneratorInterface;
/**
* @author Benjamin Laugueux <benjamin@yzalis.com>
*/
class Identicon
{
/**
* @var \Identicon\Generator\GeneratorInterface
*/
private $generator;
/**
* Identicon constructor.
*
* @param \Identicon\Generator\GeneratorInterface|null $generator
*/
public function __construct($generator = null)
{
if (null === $generator) {
$this->generator = new GdGenerator();
} else {
$this->generator = $generator;
}
}
/**
* Set the image generator.
*
* @param \Identicon\Generator\GeneratorInterface $generator
*
* @return $this
*/
public function setGenerator(GeneratorInterface $generator)
{
$this->generator = $generator;
return $this;
}
/**
* Display an Identicon image.
*
* @param string $string
* @param int $size
* @param string $color
* @param string $backgroundColor
*/
public function displayImage($string, $size = 64, $color = null, $backgroundColor = null)
{
header('Content-Type: '.$this->generator->getMimeType());
echo $this->getImageData($string, $size, $color, $backgroundColor);
}
/**
* Get an Identicon PNG image data.
*
* @param string $string
* @param int $size
* @param string $color
* @param string $backgroundColor
*
* @return string
*/
public function getImageData($string, $size = 64, $color = null, $backgroundColor = null)
{
return $this->generator->getImageBinaryData($string, $size, $color, $backgroundColor);
}
/**
* Get an Identicon PNG image resource.
*
* @param string $string
* @param int $size
* @param string $color
* @param string $backgroundColor
*
* @return string
*/
public function getImageResource($string, $size = 64, $color = null, $backgroundColor = null)
{
return $this->generator->getImageResource($string, $size, $color, $backgroundColor);
}
/**
* Get an Identicon PNG image data as base 64 encoded.
*
* @param string $string
* @param int $size
* @param string $color
* @param string $backgroundColor
*
* @return string
*/
public function getImageDataUri($string, $size = 64, $color = null, $backgroundColor = null)
{
return sprintf('data:%s;base64,%s', $this->generator->getMimeType(), base64_encode($this->getImageData($string, $size, $color, $backgroundColor)));
}
/**
* Get the color of the Identicon
*
* Returns an array with RGB values of the Identicon's color. Colors may be NULL if no image has been generated
* so far (e.g., when calling the method on a new Identicon()).
*
* @return array
*/
public function getColor()
{
$colors = $this->generator->getColor();
return [
"r" => $colors[0],
"g" => $colors[1],
"b" => $colors[2]
];
}
}

244
includes/OWASP/PureCaptcha.php

@ -0,0 +1,244 @@
<?php
/**
* OWASP PureCaptcha
* Generates simple CAPTCHAs without requiring any third party library.
* @version 1.1
*/
namespace OWASP;
class PureCaptcha
{
protected $charWidth=6;
protected $charHeight=13;
protected $chars="2346789ABDHKLMNPRTWXYZ"; //do not modify!
protected $ascii="eNrtW0FuwyAQ/NIANjbOa3LMG6r8vWrrSonkkt0xDWDvIcGXEYHM7O6s4b
p4v3zcFlyuiwu/T/Hn4ba4h49fx7COwzqO3+P964tF+i0kViRWJLaQ4RGJF3PiETnQyFGzzidklC
A31znlkMjt7Zzb2+y/kjQ79MwEbEH/+kOftsjxLHKehK7cbYT/qu0KNBcHmosjzcVI63yi1znTyE
RHCAd6oZX479uL/yIuBho5SFgs5z8kc0YaOUm4iJfxXxVbEr23Djy0Dv+D1T/iXzvSyKjhop7/r+
M/LP5v83+w+qdM/aOP/6LKqXr9r0Lm+Z+H1uH/aPGf87/Y7X8t/jfA/2j8b43/MP/7Pv5P7frfbH
YPtKOszn8VcqIrxPrxXwetw/+5n/pfz395/Y8L2//HG+sf1ZwzjUw0Utf/byH+J+N/bf7L47/xv/
z7L7QnAFG+7NgAgDYAnRngHgog50wAXAcU9BtgaBqDEz3nTK/zVALw/QiAbwHxFvg/X4G53QJwuw
VgR4CCZYBc9wh0BpD3gKDuAVkJVE4Aw5EEgGICcF0JgG+C4vQCGI/QBTqWCT5cCSSDVhJANAH0Kw
AzwfsFMB3xHBzEJliFLHwPoMY5OBEy0cj8OWi0mAFmM8G1D0LwHgC0CYbaBIvaBB1mgHRuAajOEB
W+CcNnANGcM408UwnkYQLoTwBWApUTgN0F7vAuDNRdILsLvymA+ycgmwSd";
function __construct()
{
$this->ascii=unserialize(gzuncompress(base64_decode(
preg_replace('/\s+/', '', $this->ascii))));
$this->text=$this->randomText();
}
/**
* Contains the captcha text.
* @var string
*/
public $text;
/**
* Generates random text for use in captcha
* @param integer $length
* @return string
*/
protected function randomText($length=5)
{
$res="";
for ($i=0;$i<$length;++$i)
$res.=$this->chars[mt_rand(0,strlen($this->chars)-1)];
return $res;
}
/**
* returns the index of a char in $chars array
*/
protected function asciiEntry($char)
{
for ($i=0;$i<strlen($this->chars);++$i)
if ($this->chars[$i]==$char) return $i;
return -1;
}
/**
* converts a text to a bitmap
* which is a 2D array of ones and zeroes denoting the text
*/
protected function textBitmap($text,$spacing=2)
{
$width=$this->charWidth;
$height=$this->charHeight;
$result=array();
$baseY=$baseX=0;
for ($index=0;$index<strlen($text);++$index)
{
for ($j=0;$j<$height;++$j)
{
for ($i=0;$i<$width;++$i)
$result[$baseY+$j][$baseX+$i]=
1-$this->ascii[$this->asciiEntry($text[$index])][$j][$i];
for ($i=0;$i<$spacing;++$i)
$result[$baseY+$j][$baseX+$width+$i]=0;
}
$baseX+=$width+$spacing;
}
return $result;
}
/**
* displays a bitmap string on the browser screen
*/
protected function displayBitmap($bitmap)
{
header("Content-Type: image/bmp");
echo $this->bitmap2bmp($bitmap);
}
protected function inlineBitmap($bitmap)
{
return base64_encode($this->bitmap2bmp($bitmap));
}
/**
* generates a monochrome BMP file
* a bitmap needs to be sent to this function
* i.e a 2D array with every element being either 1 or 0
* @param integer $width
* @param integer $height
* @param array $bitmap
* @return string
*/
protected function bitmap2bmp($bitmap)
{
$width=count($bitmap[0]);
$height=count($bitmap);
$bytemap=$this->bitmap2bytemap($bitmap);
$rowSize=floor(($width+31)/32)*4;
$size=$rowSize*$height + 62; //62 metadata size
#bitmap header
$data= "BM"; //header
$data.= (pack('V',$size)); //bitmap size ,4 bytes unsigned little endian
$data.= "RRRR";
$data.= (pack('V',14+40+8)); //bitmap data start offset ,
//4 bytes unsigned little endian, 14 forced, 40 header, 8 colors
#info header
$data.= pack('V',40); //bitmap header size (min 40),
//4 bytes unsigned little-endian
$data.= (pack('V',$width)); //bitmap width , 4 bytes signed integer
$data.= (pack('V',$height)); //bitmap height , 4 bytes signed integer
$data.= (pack('v',1)); //number of colored plains , 2 bytes
$data.= (pack('v',1)); //color depth , 2 bytes
$data.= (pack('V',0)); //compression algorithm , 4 bytes (0=none, RGB)
$data.= (pack('V',0)); //size of raw data, 0 is fine for no compression
$data.= (pack('V',11808)); //horizontal resolution (dpi), 4 bytes
$data.= (pack('V',11808)); //vertical resolution (dpi), 4 bytes
$data.= (pack('V',0)); //number of colors in pallette (0 = all), 4 bytes
$data.= (pack('V',0)); //number of important colors (0 = all), 4 bytes
#color palette
$data.= (pack('V',0x00FFFFFF)); //next color, white
$data.= (pack('V',0)); //first color, black
for ($j=$height-1;$j>=0;--$j)
for ($i=0;$i<$rowSize/4;++$i)
for ($k=0;$k<4;++$k)
if (isset($bytemap[$j][$i*4+$k]))
$data.= pack('C',$bytemap[$j][$i*4+$k]);
else
$data.= pack('C',0);
return $data;
}
/**
* Converts a bitmap to a bytemap, which is necessary for outputting it
*
*/
protected function bitmap2bytemap($bitmap)
{
$width=count($bitmap[0]);
$height=count($bitmap);
$bytemap=array();
for ($j=0;$j<$height;++$j)
{
for ($i=0;$i<$width/8;++$i)
{
$bitstring="";
for ($k=0;$k<8;++$k)
if (isset($bitmap[$j][$i*8+$k]))
$bitstring.=$bitmap[$j][$i*8+$k];
else
$bitstring.="0";
$bytemap[$j][]=bindec($bitstring);
}
}
return $bytemap;
}
/**
* rotates a bitmap, returning new dimensions with the bitmap
* return bitmap
*/
protected function rotateBitmap($bitmap, $degree)
{
$c=cos(deg2rad($degree));
$s=sin(deg2rad($degree));
$width=count($bitmap[0]);
$height=count($bitmap);
$newHeight=round(abs($width*$s)+abs($height*$c));
$newWidth=round(abs($width*$c) + abs($height*$s))+1;
$x0 = $width/2 - $c*$newWidth/2 - $s*$newHeight/2;
$y0 = $height/2 - $c*$newHeight/2 + $s*$newWidth/2;
$result=array_fill(0, $newHeight, array_fill(0, $newWidth, 0));
for ($j=0;$j<$newHeight;++$j)
for ($i=1;$i<$newWidth;++$i)
{
$y=(int)(-$s*$i+$c*$j+$y0);
$x=(int)($c*$i+$s*$j+$x0);
if (isset($bitmap[$y][$x]))
$result[$j][$i]=$bitmap[$y][$x];
}
return $result;
}
/**
* scales a bitmap to be bigger
*/
protected function scaleBitmap($bitmap,$scaleX,$scaleY)
{
$width=count($bitmap[0]);
$height=count($bitmap);
$newHeight=$height*$scaleY;
$newWidth=$width*$scaleX;
$result=array_fill(0, $newHeight, array_fill(0, $newWidth, 0));
for ($j=0;$j<$newHeight;++$j)
for ($i=0;$i<$newWidth;++$i)
$result[$j][$i]=$bitmap[(int)($j/$scaleY)]
[(int)($i/$scaleX)];
return $result;
}
/**
* adds random noise to the captcha
*/
protected function distort($bitmap,$noisePercent=5)
{
for ($j=0;$j<count($bitmap);++$j)
for ($i=0;$i<count($bitmap[0]);++$i)
if (isset($bitmap[$j][$i]) && mt_rand()%100<$noisePercent)
$bitmap[$j][$i]=1;
return $bitmap;
}
/**
* draw a captcha to the screen, returning its value
*/
public function show($distort=true,$scale=2.3)
{
$bitmap=$this->textBitmap($this->text);
$degree=mt_rand(2,4);
if (mt_rand()%100<50)
$degree=-$degree;
$bitmap=$this->rotateBitmap($bitmap,$degree);
$bitmap=$this->scaleBitmap($bitmap,$scale,$scale);
if ($distort) $bitmap=$this->distort($bitmap);
return $this->inlineBitmap($bitmap);
}
}

516
includes/PIT/Zip.php

@ -0,0 +1,516 @@
<?php
/*
| ZIP A ZipArchive and Plain PKZIP PHP Helper
| @file ./PIT/ZIP.php
| @author SamBrishes <sam@pytes.net>
| @version 0.2.1 [0.2.1] - Beta
|
| @license X11 / MIT License
| @copyright Copyright © 2015 - 2019 SamBrishes, pytesNET <info@pytes.net>
*/
/*
| The following websites contains all required informations, which were unavoidable for the
| creation of this class:
|
| - https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT
| - https://users.cs.jmu.edu/buchhofp/forensics/formats/pkzip.html
| - https://php.net/manual/class.ziparchive.php
*/
namespace PIT;
class Zip{
const FLAGS = "\x00\x00";
const VERSION = "\x14\x00";
const SIGNATURE = "\x50\x4b";
const COMPRESSION = "\x08\x00";
/*
| SETTINGs
*/
public $zipArchive = false;
public $compression = 6;
/*
| ZIP ARCHIVE
*/
private $zipFilename;
private $zipInstance;
/*
| FALLBACK
*/
private $offset = 0;
private $headers = array();
private $central = array();
private $counter = 0;
/*
| CONSTRUCTOR
| @since 0.2.0
|
| @param bool TRUE to check and use ZipArchive if available,
| FALSE to use the PKZIP PHP compression per default.
| @return int The compression level between -1 and 9.
*/
public function __construct($ziparchive = false, $compression = 6){
if($ziparchive){
$this->zipArchive = class_exists("ZipArchive", false);
$this->zipInstance = ($this->zipArchive)? new ZipArchive(): false;
}
if($this->zipArchive){
$this->zipFilename = tempnam(sys_get_temp_dir(), "tzp") . ".zip";
$this->zipInstance->open($this->zipFilename, ZipArchive::CREATE);
}
$this->compression = ($compression >= -1 && $compression <= 9)? $compression: 6;
}
/*
| DESTRUCTOR
| @since 0.2.0
*/
public function __destruct(){
$this->clear(false);
}
/*
| HELPER :: CONVERT UNIX TO DOS TIME
| @since 0.2.1
|
| @param int The respective timestamp as INTEGER.
*/
protected function msDOSTime($time){
$array = getdate((is_int($time) && $time > 0)? $time: time());
if($array["year"] < 1980 || $array["year"] > 2107){
$array = getdate(time());
}
// Return as DEC
return (
(($array["year"]-1980 << 25)) |
(($array["mon"] << 21)) |
(($array["mday"] << 16)) |
(($array["hours"] << 11)) |
(($array["minutes"] << 5)) |
(($array["seconds"] >> 1))
);
}
/*
| ADD A FILE
| @since 0.1.0
| @update 0.2.1
|
| @param string The relative or absolute filepath or the respective file content.
| @param string The local path within the archive file.
| @param int The timestamp to use.
| @param string The optional file comment or just an empty string.
|
| @return bool TRUE on success, FALSE on failure.
*/
public function addFile($data, $path, $time = 0, $comment = ""){
if((!is_string($data) && !is_numeric($data)) || !is_string($path)){
return false;
}
// Sanitize Data
if(is_string($data) && file_exists($data) && is_file($data)){
$data = file_get_contents($data);
}
$path = trim(str_replace("\\", "/", $path), "/");
$time = $this->msDOSTime($time);
// Zip Archive
if($this->zipArchive){
return $this->zipInstance->addFromString($path, $data);
}
// Fallback
$crcval = crc32($data);
$length = strlen($data);
if(version_compare(PHP_VERSION, "5.4.0", ">=")){
$gzcval = gzcompress($data, $this->compression, ZLIB_ENCODING_DEFLATE);
} else {
$gzcval = gzcompress($data, $this->compression);
}
$gzcval = substr($gzcval, 2, strlen($gzcval) - 6); // Fix CRC-32 Bug
$gzclen = strlen($gzcval);
/*
| LOCAL FILE HEADER
| 01 SIGNATURE
| 02 Version needed to extract this archive.
| 03 General purpose bit flag.
| 04 Compression method.
| 05 Last modification DOS datetime.
| 06 CRC32 value.
| 07 Compressed Filesize.
| 08 Uncompressed Filesize.
| 09 Length of the filename inside the archive.
| 10 Length of the extra fields.
| 11 The relative path / filename inside the archive.
| 12 The main file data value.
*/
$this->headers[] =
self::SIGNATURE . "\x03\x04" .
self::VERSION .
self::FLAGS .
self::COMPRESSION .
pack("V", $time) .
pack("V", $crcval) .
pack("V", $gzclen) .
pack("V", $length) .
pack("v", strlen($path)) .
pack("v", 0) .
$path .
$gzcval;
/*
| CENTRAL DIRECTORY RECORD
| 01 SIGNATURE
| 02 MadeBy Version numbers.
| 03 Version needed to extract this archive.
| 04 General purpose bit flag.
| 05 Compression method.
| 06 Last modification DOS datetime.
| 07 CRC32 value.
| 08 Compressed Filesize.
| 09 Uncompressed Filesize.
| 10 Length of the filename inside the archive.
| 11 Length of the extra fields.
| 12 Length of the file comment.
| 13 The disk number where the file exists.
| 14 Internal file attributes.
| 15 External file attributes.
| 16 Offset of the local file header.
| 17 The relative path / filename inside the archive.
| 18 The file comment.
*/
$this->central[] =
self::SIGNATURE . "\x01\x02" .
"\x00\x00" .
self::VERSION .
self::FLAGS .
self::COMPRESSION .
pack("V", $time) .
pack("V", $crcval) .
pack("V", $gzclen) .
pack("V", $length) .
pack("v", strlen($path)) .
pack("v", 0) .
pack("v", strlen($comment)) .
pack("v", 0) .
pack("v", 0) .
pack("V", 32) .
pack("V", $this->offset) .
$path .
$comment;
// Count Offset and Return
$this->offset += strlen($this->headers[count($this->headers)-1]);
return true;
}
/*
| ADD MULTIPLE FILES
| @since 0.2.0
| @update 0.2.1
|
| @param array Multiple 'local/file/path' => "filepath/or/filecontent" ARRAY pairs.
| @param int The timestamp to use for all files.
| @param string The optional comment or just an empty string for alle respective files.
|
| @return int The number of successfully added elements / files.
*/
public function addFiles($array, $time = 0, $comment = ""){
if(!is_array($array)){
return false;
}
foreach($array AS $path => &$data){
$data = $this->addFile($data, $path);
}
return array_filter(array_values($array));
}
/*
| ADD FOLDER
| @since 0.2.0
|
| @param string The path to the folder, which should be zipped.
| @param string The local path within the zip file.
| @param bool TRUE to zip recursive and include all sub directories,
| FALSE to just zip all files within the $path folder.
| @param bool TRUE to include empty folders on recursive zips.
| FALSE to skip empty folders.
|
| @return multi The number as INT of successfully added elements / files,
| FALSE on failure.
*/
public function addFolder($path, $local = "/", $recursive = false, $empty = false){
if(!file_exists($path) || !is_dir($path)){
return false;
}
// Chech Path
$path = str_replace(array("/", "\\"), DIRECTORY_SEPARATOR, realpath($path));
if(strpos($path, DIRECTORY_SEPARATOR) !== strlen($path)-1){
$path .= DIRECTORY_SEPARATOR;
}
// Check Local
if(!is_string($local)){
$local = "";
}
$local = trim(str_replace("\\", "/", $local), "/") . "/";
// Start Flow
$this->counter = 0;
$this->addFolderFlow($path, "", $local, !!$recursive, !!$empty);
return $this->counter;
}
/*
| HELPER :: ADD FOLDER LOOP
| @since 0.2.0
|
| @param string The base path to the folder, which should be zipped.
| @param string The further path, within the base path, on recursive calls.
| @param string The local path within the zip file.
| @param bool TRUE to zip recursive and include all sub directories,
| FALSE to just zip all files within the $path folder.
| @param bool TRUE to include empty folders on recursive zips.
| FALSE to skip empty folders.
|
| @return int The number of successfully added elements / files.
*/
private function addFolderFlow($base, $path = "", $local = "", $recursive = false, $empty = false){
$path = str_replace(array("/", "\\"), DIRECTORY_SEPARATOR, $path);
$path = trim($path, DIRECTORY_SEPARATOR);
if(!empty($path)){
$path .= DIRECTORY_SEPARATOR;
}
$count = 0;
$handle = opendir($base . $path);
while(($file = readdir($handle)) !== false){
if(in_array($file, array(".", ".."))){
continue;
}
if(is_dir($base . $path . $file)){
if($recursive){
$count = $this->addFolderFlow($base, $path . $file, $local, $recursive, $empty);
if($count == 0 && $empty){
$this->addEmptyFolder($local . $path . $file);
}
}
continue;
}
if(is_file($base . $path . $file)){
if($this->addFile($base . $path . $file, $local . $path . $file)){
$count++;
$this->counter++;
}
}
}
closedir($handle);
return $count;
}
/*
| ADD EMPTY FOLDER
| @since 0.2.0
| @update 0.2.1
|
| @param string The local path structure within the zip file.
| @param int The timestamp to use.
| @param string The optional file comment or just an empty string.
|
| @return bool TRUE on success, FALSE on failure.
*/
public function addEmptyFolder($path, $time = 0, $comment = ""){
$path = trim(str_replace("\\", "/", $path), "/") . "/";
$time = $this->msDOSTime($time);
// ZipArchive
if($this->zipArchive){
return $this->zipInstance->addEmptyDir($path);
}
// Add Header
$this->headers[] =
self::SIGNATURE . "\x03\x04" .
self::VERSION .
self::FLAGS .
"\x00\x00" .
pack("V", $time) .
pack("V", 0) .
pack("V", 0) .
pack("V", 0) .
pack("v", strlen($path)) .
pack("v", 0) .
$path .
"";
// Add Central
$this->central[] =
self::SIGNATURE . "\x01\x02" .
"\x14\x03" .
self::VERSION .
self::FLAGS .
"\x00\x00" .
pack("V", $time) .
pack("V", 0) .
pack("V", 0) .
pack("V", 0) .
pack("v", strlen($path)) .
pack("v", 0) .
pack("v", strlen($comment)) .
pack("v", 0) .
pack("v", 0) .
"\x00\x00\xFF\x41" .
pack("V", $this->offset) .
$path .
$comment;
// Count Offset and Return
$this->offset += strlen($this->headers[count($this->headers)-1]);
return true;
}
/*
| CLEAR DATA STRINGs
| @since 0.1.0
| @update 0.2.0
*/
public function clear($new = true){
if($this->zipArchive){
if(is_a($this->zipInstance, "ZipArchive")){
$this->zipInstance->close();
}
if(strpos($this->zipFilename, sys_get_temp_dir()) === 0 && file_exists($this->zipFilename)){
@unlink($this->zipFilename);
}
if($new){
$this->zipFilename = "./temp-".time().".zip";
$this->zipInstance = new ZipArchive();
$this->zipInstance->open($this->zipFilename, ZipArchive::CREATE);
}
}
$this->offset = 0;
$this->headers = array();
$this->central = array();
return true;
}
/*
| DUMBS OUT THE FILE
| @since 0.1.0
| @update 0.2.1
*/
public function file(){
$comment = "PKZipped with https://github.com/SamBrishes/FoxCMS/tree/helpers/zip";
// ZipArchive
if($this->zipArchive){
$this->zipInstance->setArchiveComment($comment);
$this->zipInstance->close();
$content = file_get_contents($this->zipFilename);
$this->zipInstance = new ZipArchive();
$this->zipInstance->open($this->zipFilename);
return $content;
}
// Fallback
$headers = implode("", $this->headers);
$central = implode("", $this->central);
/*
| RETURN
| 01 The file header items.
| 02 The central directory items.
| 03 The signature for the end of the central directory record.
| 04 The number of this disk / part.
| 05 The number of the disk / part where the central directory starts.
| 06 The number of central directoy entries on this disk.
| 07 Total number of entries on this disk / part.
| 08 Total number of entries in general.
| 09 Length of the central directory.
| 10 Offset where the central directory starts.
| 11 The length of the following comment field.
| 12 The archive comment.
*/
return $headers . $central .
self::SIGNATURE . "\x05\x06" .
"\x00" .
"\x00" .
"\x00" .
"\x00" .
pack("v", count($this->central)) .
pack("v", count($this->central)) .
pack("V", strlen($central)) .
pack("V", strlen($headers)) .
pack("v", strlen($comment)) .
$comment;
}
/*
| STORE THE ZIP FILE
| @since 0.1.0
| @update 0.2.0
|
| @param string The filename with the respective path to store the archive.
| @param bool TRUE to overwrite existing archives, FALSE to do it not.
|
| @return bool TRUE on success, FALSE on failure.
*/
public function save($filename = "archive.zip", $overwrite = false){
if(file_exists($filename) && !$overwrite){
return false;
}
// Zip Archive
if($this->zipArchive){
if(is_a($this->zipInstance, "ZipArchive")){
$this->zipInstance->close();
}
if(@file_put_contents($filename, file_get_contents($this->zipFilename))){
@unlink($this->zipFilename);
$this->zipFilename = $filename;
$this->zipInstance = new ZipArchive();
return $this->zipInstance->open($this->zipFilename);
}
return false;
}
// Fallback
return @file_put_contents($filename, $this->file()) !== false;
}
/*
| DOWNLOAD THE FILE
| @since 0.1.0
| @update 0.2.0
|
| @param string The filename for the archiv.
| @param bool TRUE to exit after the execution, FALSE to do it not.
|
| @return void
*/
public function download($filename = "archive.zip", $exit = false){
$file = $this->file();
header("Pragma: public");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: private", false);
header("Content-Type: application/zip");
header("Content-Disposition: attachment; filename={$filename};" );
header("Content-Transfer-Encoding: binary");
header("Content-Length: " . strlen($file));
print ($file);
if($exit){
die();
}
}
}

23
includes/autoload.php

@ -0,0 +1,23 @@
<?php
/*
| Snicker The first native FlatFile Comment Plugin 4 Bludit
| @file ./includes/autoload.php
| @author SamBrishes <sam@pytes.net>
| @version 0.1.2 [0.1.0] - Alpha
|
| @website https://github.com/pytesNET/snicker
| @license X11 / MIT License
| @copyright Copyright © 2019 SamBrishes, pytesNET <info@pytes.net>
*/
spl_autoload_register(function($class){
foreach(array("Gregwar", "Identicon", "PIT", "OWASP") AS $allowed){
if(strpos($class, $allowed) !== 0){
continue;
}
$path = dirname(__FILE__) . DIRECTORY_SEPARATOR;
$class = str_replace("\\", DIRECTORY_SEPARATOR, $class);
require_once $class . ".php";
}
return false;
});

BIN
includes/img/default-avatar.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

205
includes/js/identicon.js

@ -0,0 +1,205 @@
/**
* Identicon.js 2.3.3
* http://github.com/stewartlord/identicon.js
*
* PNGLib required for PNG output
* http://www.xarg.org/download/pnglib.js
*
* Copyright 2018, Stewart Lord
* Released under the BSD license
* http://www.opensource.org/licenses/bsd-license.php
*/
(function() {
var PNGlib;
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
PNGlib = require('./pnglib');
} else {
PNGlib = window.PNGlib;
}
var Identicon = function(hash, options){
if (typeof(hash) !== 'string' || hash.length < 15) {
throw 'A hash of at least 15 characters is required.';
}
this.defaults = {
background: [240, 240, 240, 255],
margin: 0.08,
size: 64,
saturation: 0.7,
brightness: 0.5,
format: 'png'
};
this.options = typeof(options) === 'object' ? options : this.defaults;
// backward compatibility with old constructor (hash, size, margin)
if (typeof(arguments[1]) === 'number') { this.options.size = arguments[1]; }
if (arguments[2]) { this.options.margin = arguments[2]; }
this.hash = hash
this.background = this.options.background || this.defaults.background;
this.size = this.options.size || this.defaults.size;
this.format = this.options.format || this.defaults.format;
this.margin = this.options.margin !== undefined ? this.options.margin : this.defaults.margin;
// foreground defaults to last 7 chars as hue at 70% saturation, 50% brightness
var hue = parseInt(this.hash.substr(-7), 16) / 0xfffffff;
var saturation = this.options.saturation || this.defaults.saturation;
var brightness = this.options.brightness || this.defaults.brightness;
this.foreground = this.options.foreground || this.hsl2rgb(hue, saturation, brightness);
};
Identicon.prototype = {
background: null,
foreground: null,
hash: null,
margin: null,
size: null,
format: null,
image: function(){
return this.isSvg()
? new Svg(this.size, this.foreground, this.background)
: new PNGlib(this.size, this.size, 256);
},
render: function(){
var image = this.image(),
size = this.size,
baseMargin = Math.floor(size * this.margin),
cell = Math.floor((size - (baseMargin * 2)) / 5),
margin = Math.floor((size - cell * 5) / 2),
bg = image.color.apply(image, this.background),
fg = image.color.apply(image, this.foreground);
// the first 15 characters of the hash control the pixels (even/odd)
// they are drawn down the middle first, then mirrored outwards
var i, color;
for (i = 0; i < 15; i++) {
color = parseInt(this.hash.charAt(i), 16) % 2 ? bg : fg;
if (i < 5) {
this.rectangle(2 * cell + margin, i * cell + margin, cell, cell, color, image);
} else if (i < 10) {
this.rectangle(1 * cell + margin, (i - 5) * cell + margin, cell, cell, color, image);
this.rectangle(3 * cell + margin, (i - 5) * cell + margin, cell, cell, color, image);
} else if (i < 15) {
this.rectangle(0 * cell + margin, (i - 10) * cell + margin, cell, cell, color, image);
this.rectangle(4 * cell + margin, (i - 10) * cell + margin, cell, cell, color, image);
}
}
return image;
},
rectangle: function(x, y, w, h, color, image){
if (this.isSvg()) {
image.rectangles.push({x: x, y: y, w: w, h: h, color: color});
} else {
var i, j;
for (i = x; i < x + w; i++) {
for (j = y; j < y + h; j++) {
image.buffer[image.index(i, j)] = color;
}
}
}
},
// adapted from: https://gist.github.com/aemkei/1325937
hsl2rgb: function(h, s, b){
h *= 6;
s = [
b += s *= b < .5 ? b : 1 - b,
b - h % 1 * s * 2,
b -= s *= 2,
b,
b + h % 1 * s,
b + s
];
return[
s[ ~~h % 6 ] * 255, // red
s[ (h|16) % 6 ] * 255, // green
s[ (h|8) % 6 ] * 255 // blue
];
},
toString: function(raw){
// backward compatibility with old toString, default to base64
if (raw) {
return this.render().getDump();
} else {
return this.render().getBase64();
}
},
isSvg: function(){
return this.format.match(/svg/i)
}
};
var Svg = function(size, foreground, background){
this.size = size;
this.foreground = this.color.apply(this, foreground);
this.background = this.color.apply(this, background);
this.rectangles = [];
};
Svg.prototype = {
size: null,
foreground: null,
background: null,
rectangles: null,
color: function(r, g, b, a){
var values = [r, g, b].map(Math.round);
values.push((a >= 0) && (a <= 255) ? a/255 : 1);
return 'rgba(' + values.join(',') + ')';
},
getDump: function(){
var i,
xml,
rect,
fg = this.foreground,
bg = this.background,
stroke = this.size * 0.005;
xml = "<svg xmlns='http://www.w3.org/2000/svg'"
+ " width='" + this.size + "' height='" + this.size + "'"
+ " style='background-color:" + bg + ";'>"
+ "<g style='fill:" + fg + "; stroke:" + fg + "; stroke-width:" + stroke + ";'>";
for (i = 0; i < this.rectangles.length; i++) {
rect = this.rectangles[i];
if (rect.color == bg) continue;
xml += "<rect "
+ " x='" + rect.x + "'"
+ " y='" + rect.y + "'"
+ " width='" + rect.w + "'"
+ " height='" + rect.h + "'"
+ "/>";
}
xml += "</g></svg>"
return xml;
},
getBase64: function(){
if ('function' === typeof btoa) {
return btoa(this.getDump());
} else if (Buffer) {
return new Buffer(this.getDump(), 'binary').toString('base64');
} else {
throw 'Cannot generate base64 output';
}
}
};
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
module.exports = Identicon;
} else {
window.Identicon = Identicon;
}
})();

214
includes/js/pnglib.js

@ -0,0 +1,214 @@
/**
* A handy class to calculate color values.
*
* @version 1.0
* @author Robert Eisele <robert@xarg.org>
* @copyright Copyright (c) 2010, Robert Eisele
* @link http://www.xarg.org/2010/03/generate-client-side-png-files-using-javascript/
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
*
*/
(function() {
// helper functions for that ctx
function write(buffer, offs) {
for (var i = 2; i < arguments.length; i++) {
for (var j = 0; j < arguments[i].length; j++) {
buffer[offs++] = arguments[i].charAt(j);
}
}
}
function byte2(w) {
return String.fromCharCode((w >> 8) & 255, w & 255);
}
function byte4(w) {
return String.fromCharCode((w >> 24) & 255, (w >> 16) & 255, (w >> 8) & 255, w & 255);
}
function byte2lsb(w) {
return String.fromCharCode(w & 255, (w >> 8) & 255);
}
// modified from original source to support NPM
var PNGlib = function(width,height,depth) {
this.width = width;
this.height = height;
this.depth = depth;
// pixel data and row filter identifier size
this.pix_size = height * (width + 1);
// deflate header, pix_size, block headers, adler32 checksum
this.data_size = 2 + this.pix_size + 5 * Math.floor((0xfffe + this.pix_size) / 0xffff) + 4;
// offsets and sizes of Png chunks
this.ihdr_offs = 0; // IHDR offset and size
this.ihdr_size = 4 + 4 + 13 + 4;
this.plte_offs = this.ihdr_offs + this.ihdr_size; // PLTE offset and size
this.plte_size = 4 + 4 + 3 * depth + 4;
this.trns_offs = this.plte_offs + this.plte_size; // tRNS offset and size
this.trns_size = 4 + 4 + depth + 4;
this.idat_offs = this.trns_offs + this.trns_size; // IDAT offset and size
this.idat_size = 4 + 4 + this.data_size + 4;
this.iend_offs = this.idat_offs + this.idat_size; // IEND offset and size
this.iend_size = 4 + 4 + 4;
this.buffer_size = this.iend_offs + this.iend_size; // total PNG size
this.buffer = new Array();
this.palette = new Object();
this.pindex = 0;
var _crc32 = new Array();
// initialize buffer with zero bytes
for (var i = 0; i < this.buffer_size; i++) {
this.buffer[i] = "\x00";
}
// initialize non-zero elements
write(this.buffer, this.ihdr_offs, byte4(this.ihdr_size - 12), 'IHDR', byte4(width), byte4(height), "\x08\x03");
write(this.buffer, this.plte_offs, byte4(this.plte_size - 12), 'PLTE');
write(this.buffer, this.trns_offs, byte4(this.trns_size - 12), 'tRNS');
write(this.buffer, this.idat_offs, byte4(this.idat_size - 12), 'IDAT');
write(this.buffer, this.iend_offs, byte4(this.iend_size - 12), 'IEND');
// initialize deflate header
var header = ((8 + (7 << 4)) << 8) | (3 << 6);
header+= 31 - (header % 31);
write(this.buffer, this.idat_offs + 8, byte2(header));
// initialize deflate block headers
for (var i = 0; (i << 16) - 1 < this.pix_size; i++) {
var size, bits;
if (i + 0xffff < this.pix_size) {
size = 0xffff;
bits = "\x00";
} else {
size = this.pix_size - (i << 16) - i;
bits = "\x01";
}
write(this.buffer, this.idat_offs + 8 + 2 + (i << 16) + (i << 2), bits, byte2lsb(size), byte2lsb(~size));
}
/* Create crc32 lookup table */
for (var i = 0; i < 256; i++) {
var c = i;
for (var j = 0; j < 8; j++) {
if (c & 1) {
c = -306674912 ^ ((c >> 1) & 0x7fffffff);
} else {
c = (c >> 1) & 0x7fffffff;
}
}
_crc32[i] = c;
}
// compute the index into a png for a given pixel
this.index = function(x,y) {
var i = y * (this.width + 1) + x + 1;
var j = this.idat_offs + 8 + 2 + 5 * Math.floor((i / 0xffff) + 1) + i;
return j;
}
// convert a color and build up the palette
this.color = function(red, green, blue, alpha) {
alpha = alpha >= 0 ? alpha : 255;
var color = (((((alpha << 8) | red) << 8) | green) << 8) | blue;
if (typeof this.palette[color] == "undefined") {
if (this.pindex == this.depth) return "\x00";
var ndx = this.plte_offs + 8 + 3 * this.pindex;
this.buffer[ndx + 0] = String.fromCharCode(red);
this.buffer[ndx + 1] = String.fromCharCode(green);
this.buffer[ndx + 2] = String.fromCharCode(blue);
this.buffer[this.trns_offs+8+this.pindex] = String.fromCharCode(alpha);
this.palette[color] = String.fromCharCode(this.pindex++);
}
return this.palette[color];
}
// output a PNG string, Base64 encoded
this.getBase64 = function() {
var s = this.getDump();
var ch = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var c1, c2, c3, e1, e2, e3, e4;
var l = s.length;
var i = 0;
var r = "";
do {
c1 = s.charCodeAt(i);
e1 = c1 >> 2;
c2 = s.charCodeAt(i+1);
e2 = ((c1 & 3) << 4) | (c2 >> 4);
c3 = s.charCodeAt(i+2);
if (l < i+2) { e3 = 64; } else { e3 = ((c2 & 0xf) << 2) | (c3 >> 6); }
if (l < i+3) { e4 = 64; } else { e4 = c3 & 0x3f; }
r+= ch.charAt(e1) + ch.charAt(e2) + ch.charAt(e3) + ch.charAt(e4);
} while ((i+= 3) < l);
return r;
}
// output a PNG string
this.getDump = function() {
// compute adler32 of output pixels + row filter bytes
var BASE = 65521; /* largest prime smaller than 65536 */
var NMAX = 5552; /* NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 */
var s1 = 1;
var s2 = 0;
var n = NMAX;
for (var y = 0; y < this.height; y++) {
for (var x = -1; x < this.width; x++) {
s1+= this.buffer[this.index(x, y)].charCodeAt(0);
s2+= s1;
if ((n-= 1) == 0) {
s1%= BASE;
s2%= BASE;
n = NMAX;
}
}
}
s1%= BASE;
s2%= BASE;
write(this.buffer, this.idat_offs + this.idat_size - 8, byte4((s2 << 16) | s1));
// compute crc32 of the PNG chunks
function crc32(png, offs, size) {
var crc = -1;
for (var i = 4; i < size-4; i += 1) {
crc = _crc32[(crc ^ png[offs+i].charCodeAt(0)) & 0xff] ^ ((crc >> 8) & 0x00ffffff);
}
write(png, offs+size-4, byte4(crc ^ -1));
}
crc32(this.buffer, this.ihdr_offs, this.ihdr_size);
crc32(this.buffer, this.plte_offs, this.plte_size);
crc32(this.buffer, this.trns_offs, this.trns_size);
crc32(this.buffer, this.idat_offs, this.idat_size);
crc32(this.buffer, this.iend_offs, this.iend_size);
// convert PNG to string
return "\x89PNG\r\n\x1a\n"+this.buffer.join('');
}
}
// modified from original source to support NPM
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
module.exports = PNGlib;
} else {
window.PNGlib = PNGlib;
}
})();

6
languages/de.json

@ -0,0 +1,6 @@
{
"plugin-data": {
"name": "Snicker",
"description": "Ein natives, AJAX-unterstütztes FlatFile Kommentiersystem für Bludit, mit einer Abonnement-Funktion und im Einklang mit der DSGVO!"
}
}

204
languages/de_DE.json

@ -0,0 +1,204 @@
{
"plugin-data": {
"name": "Snicker",
"description": "Ein natives, AJAX-unterstütztes FlatFile Kommentiersystem für Bludit, mit einer Abonnement-Funktion und im Einklang mit der DSGVO!"
},
"s18n-a5d491060952aa8ad5fdee071be752de": "Kommentare",
"s18n-de95b43bceeb4b998aed4aed5cef1ae7": "Bearbeiten",
"s18n-f984023ed3d6df2326c9d59838c29792": "Kommentar bearbeiten",
"s18n-86448a506dd93303a72140b9124ee321": "Kommentar entfernen",
"s18n-db10a8eb963bc0e5f4483ac9b5dc554c": "Kommentar-Titel",
"s18n-335630425567dbe91768a3beffdec752": "Kommentar-Inhalt",
"s18n-bd7e63f881c7f787a9a0dce20b7f9e5b": "Meta Einstellungen",
"s18n-1acfe725df7bd12195751f0737c4d375": "Registrierter Nutzer",
"s18n-e988189db402fab453f72052629c02cd": "Kommentar-Benutzername",
"s18n-db84c6236ca6a01b9189504d78c012a5": "Kommentar-eMail",
"s18n-7c6c2e5d48ab37a007cbf70d3ea25fa4": "Ausstehend",
"s18n-787d5f05953ec39b108869dfdd7733e6": "Genehmigt",
"s18n-c7537d6d48ecf261749c09a9f284bd45": "Abgelehnt",
"s18n-e09f6a7593f8ae3994ea57e1117f67ec": "Spam",
"s18n-7a5115c2c1eb662308decbec83593494": "Seite aufrufen",
"s18n-0572a05cd6d6360391993a611099542f": "Kommentar Titel oder Auszug",
"s18n-428f14500191b5d53675da4a96bc8bba": "Kommentare suchen",
"s18n-e0be71bccdeceb713fba3c222f79a3c5": "Keine Kommentare verfügbar",
"s18n-06d4cd63bde972fc66a0aed41d2f5c51": "Kommentar",
"s18n-02bd92faa38aaa6cc0ea75e59937a1ef": "Autor",
"s18n-ebb67a4271abe715344471b0f16321f6": "Aktionen",
"s18n-58566b9a9b2733b0ceacb2186672b5d1": "Zeige alle Antworten",
"s18n-5f44c0081bd862a77ba8b24e923cadf1": "Antwort zu",
"s18n-eb399bcaca686f8609137153307eecf1": "Ändern",
"s18n-2736f4347985da50dc023444c193bfea": "Kommentar bBearbeiten",
"s18n-a107bf4b12e36a07161a26d95b03bc81": "Kommentar genehmigen",
"s18n-da937abd19cd9e1430470b8a471a41d4": "Kommentar ablehnen",
"s18n-32cb4199893d9948cc0853eef244f1fc": "Als Spam markieren",
"s18n-a6494adfb72d12d3a4da66855c284ec6": "Zurück zu ausstehend",
"s18n-1bda80f2be4d3658e0baa43fbe7ae8c1": "Anzeigen",
"s18n-c9ae5a4214e87ad6fbed44a267471eee": "Einstellungen speichern",
"s18n-e124d357c3c832434a8676a5e18db842": "Allgemeine Einstellungen",
"s18n-b62a9dcd666f3ff44197cf21ac66507d": "Kommentar Moderation",
"s18n-7f0217cdcdd58ba86aae84d9d3d79f81": "Moderiere",
"s18n-1a1dc91c907325c69271ddf0c944bc72": "Genehmige",
"s18n-df6963dc912cde9baeef10343167ba01": "jeden Kommentar",
"s18n-39ba4181e212acf183ac965c9b37da89": "Außer der Nutzer ist angemeldet",
"s18n-0dac4426a017f0a0370db32776bc30bb": "Außer der Nutzer ist Admin oder der Autor",
"s18n-c70aa8b8fd6f2652eda2d5366faf1de5": "Außer der Nutzer hat bereits einen genehmigen Kommentar",
"s18n-8b26cab9d444760b4bcc65dc4d8634f8": "Kommentare erlauben",
"s18n-67614909bf9de326de71946036de39f1": "... auf Öffentliche Seiten",
"s18n-a7c384c1b60785c40bc3a4e4dfc5a108": "... auf Fixierte Seiten",
"s18n-9907b78f8745810599dbf6fd29a01364": "... auf Statische Seiten",
"s18n-c89cbecce48d04cd76c0b95c8128ad97": "Aktivieren (Optional)",
"s18n-e9d51286fdd0ff058650392fc8b6ae30": "Aktivieren (Erforderlich)",
"s18n-0aaa87422396fdd678498793b6d5250e": "Deaktivieren",
"s18n-8da2c8185edfeb1765526f8e2e4f388d": "Kommentar Limit",
"s18n-77d6d185c93549dab24f29ff2e3b25a8": "Nutze '0' um das Limit zu deaktivieren!",
"s18n-1ef52691308c8add87723a4103a561c2": "Kommentar-Tiefe",
"s18n-d102731a5fedff24f30e24e883ef4636": "Kommentar-Markups",
"s18n-6640979a191e66655c26c59d404bf955": "Erlaube generelles HTML",
"s18n-915009e874f8bed1845060012f826fcd": "Erlaube Markdown",
"s18n-a9a36cb3d8f4f7297ebca99a322d6342": "Kommentar-Bewertungen",
"s18n-2123546d1ff8b0cb035df0c0b0d06825": "Speicher Gäste-Bewertungen im",
"s18n-dead693ab29895d302fca0e6baad6182": "Cookie Speicher",
"s18n-02b68043bdcae159e83199d64a5abd7d": "Session Speicher",
"s18n-40bd8791e523e91219886c35622163fc": "Datenbank Speicher",
"s18n-8717cfca734e8987971f63b20eeb8024": "Was?",
"s18n-5a8cbcf57f5b59f0d4b8ded97d018399": "Der <b>Cookie Speicher<\/b> befindet sich auf dem Computer des Nutzers, So hast du nicht den vollen Zugriff darauf UND benötigst zudem die Erlaubnis des Nutzers!",
"s18n-a09dbdf907873e66d9d644cca71970d5": "Der <b>Session Speicher<\/b> legt die Daten temporär auf den Server ab, bis der Nutzer den Browser schließt. Dafür benötigst du allerdings auch keine Berechtigung vom Nutzer.",
"s18n-71892ebe01ba92d1f163dc37d818b5ff": "Der <b>Datenbank Speicher<\/b> generiert und speichert einen anonymisierten aber zuweisbaren Wert des Nutzers, welche eine entsprechende Einwilligung benötigt.",
"s18n-7c35a0bcb0b0678f0829036eead5ddca": "<b>Bitte beachte:<\/b> Du bist für die Einholung der Einwilligung des Nutzers verantwortlich, Snicker übernimmt dies lediglich für die Speicherung der Daten über das Kommentar-Formular selbst!",
"s18n-82e5228061f185ee185bd9f3ecba4ee7": "Erlaube die Bewertung: %s",
"s18n-61b58693e0eceeb27ce0cc3b25b3bf31": "Webseiten-Einstellungen",
"s18n-b3c1c2c231275878abe58a55966fa9e0": "Seiten Filter",
"s18n-9cb1eef8966f93282524929f65c8b9ec": "Deaktiviere den Seiten-Filter",
"s18n-e89fd56cefec9baabcbe0db3e5a36962": "Nutze 'pageBegin'",
"s18n-3d08b5dcc1e5c3c7e7e2eaf2d0d6a12d": "Nutze 'pageEnd'",
"s18n-3771d05b6af4ebb0a303266c47809548": "Nutze 'siteBodyBegin'",
"s18n-42438edc41ab83312486009e3122e92b": "Nutze 'siteBodyEnd'",
"s18n-948da5199de32c7601a20b6107c31d4d": "Kommentar-Captcha",
"s18n-47e5c42fb9bdca3636a5d866a6794101": "Deaktiviere das Captcha",
"s18n-eedf6d23d18212016e22428658e17794": "Nutze den lokalen Captcha (von Gregway)",
"s18n-ab6ef7ef94efc86db78218c6c265243a": "Nutze OWASPs PureCaptcha",
"s18n-a58adce0085cc1a25fc8076e97c29d70": "Nutze Gregways Captcha (Die GD Bibliothek fehlt!)",
"s18n-09cddbc3627ea46a8dce692d64273b61": "Nutze Googles reCaptcha (Noch nicht verfügbar)",
"s18n-838a13e4fece7c272b960da3fb99f94d": "Kommentar-Template",
"s18n-6f95370a28520696b2a0ad34efc54d2d": "Kommentar-Reihenfolge",
"s18n-4a8dc1710396b21e7b1da8112c07c4ad": "Die neusten Kommentare zuerst",
"s18n-2dab3b12d0b0642c3964b37d675ff24b": "Die ältesten Kommentare zuerst",
"s18n-230c71c29590608034b4a590a67ace31": "Kommentar-Formular Position",
"s18n-37d988444dec2001c488806fc8401e25": "Zeige das Kommentare-Formular über den Kommentaren",
"s18n-16ac6c11951d825826f77a4097a1c2cb": "Zeige das Kommentare-Formular unter den Kommentaren",
"s18n-1e98ec9312b69676d5e3fe3caf8ecde1": "Kommentare pro Seite",
"s18n-0d46f4389ca5f882e24899fe489bf344": "Nutze '0' um alle Kommentare anzuzeigen!",
"s18n-7b6b84fbd65a4b712a5ba0dccce176d5": "Nutzungsbedingungen",
"s18n-9853383062a2e308d5aed35fe3da7953": "Deaktiviere dieses Feld",
"s18n-352fd1d7225b5ea02b8ddd9fad0d6e34": "Zeige eine Standard-Nachricht (See Zeichenketten)",
"s18n-71860c77c6745379b0d44304d66b6a13": "Seite",
"s18n-5dcb84333ae70a5bed60bf70d34dcd2b": "Zeige den Standard DSGVO Text oder wähle eine Seite für deie Nutzungsbedingungen!",
"s18n-2e3d9327c371afb7489f9b9278198622": "AJAX Script",
"s18n-c60cab6330745b41cbee05603eec6691": "AJAX Script einbetten",
"s18n-69b11f64af515ef979fbf28c5e06f370": "AJAX Script nicht einbetten",
"s18n-9bbec7b57565f06d73522669d3b836dc": "Das AJAX Script übergibt die Anfragen (Kommentare, Bewertungen) direkt ohne den Browser neu zu laden!",
"s18n-7c74c0d2d1c28d1568298c89742ce126": "Kommentar-Profilbild",
"s18n-aeab7c630dae161d8f6e2898dd83b471": "Nutze Gravatar",
"s18n-882e3436da897c055cc3f8bd2598b71a": "Nutze Identicon",
"s18n-d92d61ad0c0065170a37a1805ad1bc9e": "Nutze Mystery Men",
"s18n-b4a51a35344f9a8fa1139cfd968ab308": "Nutze & Bevorzuge das richtige Profilbild bei Nutzern",
"s18n-b50a4a96c25d745d73114af5a4b03145": "Kommentar-Gravatar",
"s18n-9ffb941a398ddee8e054eef3292c546e": "Nutze Mystery Person",
"s18n-a7dd12b1dab17d25467b0b0a4c8d4a92": "Nutze",
"s18n-0d2fc085ee57276417cf380027060760": "Das Standard-Gravatar Bild, wenn der Nutzer keines hat!",
"s18n-58e2aacf5792087168cbc62578584ecd": "Abonnement Einstellungen",
"s18n-8290ca86b8980a14bd46f34017e03f93": "Das Abonnement System ist noch nicht verfügbar!",
"s18n-0bd7ff1b4ac56a9616796bdc05609de2": "eMail Abonnement",
"s18n-208f156d4a803025c284bb595a7576b4": "Aktivieren",
"s18n-dc985a7c2144c6447674e674aee08441": "eMail 'Von' Adresse",
"s18n-f6db5b4db3f9c1ba0ffc091abc561802": "eMail 'Antwort an' Adresse",
"s18n-8d868315d258783a95336d1a6ce27e1e": "eMail Inahlt (Opt-In)",
"s18n-7721b2a2f2a453cc790e3ac7065e9b65": "Nutze die Standard Abonnements eMail",
"s18n-58ff11585a82c73f5117c91c29cb3f63": "eMail Inhalt (Benachrichtigung)",
"s18n-bea9ff19efca028c01617da5dce18171": "Nutze die Standard Benachrichtigungs eMail",
"s18n-e0024a8886a178d4f70b8c888b701680": "Finde weitere Informationen zu benutzerdefinierten eMails %s!",
"s18n-8bcf6629759bd278a5c6266bd9c054f8": "Zeichenketten",
"s18n-3979b4205954030810a8a87769348094": "Standard Danke Nachricht",
"s18n-620b528248b36bf743d1ad33e35022d6": "Danke Nachricht mit Abonnement",
"s18n-e83f6b01d1c81313f6b388281e13aacf": "Danke Nachricht für die Bewertung",
"s18n-01b7a6bc6b57783b4fd081085ff3271e": "Fehler: Unbekannter Fehler",
"s18n-19678b1419eff34dffd41d6778b1aa89": "Fehler: Benutzername ist ungültig",
"s18n-1255e6794d33b443bcee21279d9caa1a": "Fehler: eMail Adresse ist ungültig",
"s18n-ebf545757b92b3a553d83ea3db48beca": "Fehler: Kommentar-Text fehlt",
"s18n-f1bc53e2456c425578277adbc7c90f3a": "Fehler: Kommentar-Titel fehlt",
"s18n-53c8fdd7ed497dbacbef5fd5d4f38f3d": "Fehler: Nutzungsbedingungen fehlen",
"s18n-89f8cc478fdfc0a8f36c1b393f39677a": "Fehler: Als SPAM markiert",
"s18n-59ad5c9fcee1b28a5d004bcf684a5acd": "Fehler: Bereits abgestimmt",
"s18n-56ef2c600b4af0f9f7b35640525967ca": "Nutzungsbedingungen",
"s18n-08400c2e0f51197fdb3590461b15b2cc": "Nutzername oder eMail Adresse",
"s18n-b79edd2e426f90401c04869346b503c7": "Nutzer suchen",
"s18n-ee51fa9d5097c84d2fa6c885bf2d5d84": "Keine Nutzer verfügbar",
"s18n-14c4b06b824ec593239362517f538b29": "Benutzername",
"s18n-0c83f57c786a0b4a39efab23731c7ebc": "eMail",
"s18n-e1260894f59eeae98c8440899de4df8d": "Aktionem",
"s18n-4bc61296b766756f1c7296489633bf32": "Entfernen (Anonymisieren)",
"s18n-c0f4afd3614929f1c803f3a01414a6c7": "Entfernen (Vollständig)",
"s18n-6356f32d0c02c8f90cb59a77e16e8fe2": "Nutzer entblockieren",
"s18n-2327a01afbee025fb5913357c9d6b1b3": "Nutzer blockieren",
"s18n-76a0f6752a45d8af6343ef3e2b6f522a": "Einzelner Kommentar",
"s18n-c426859e50a35617d863cdad2b9c84aa": "Seiten-Kommentare",
"s18n-a64d776275f13a51790bb460774b9129": "Nutzer Kommentare",
"s18n-9bc65c2abec141778ffaa729489f3e87": "Nutzer",
"s18n-ccd1066343c95877b75b79d47c36bebe": "Konfiguration",
"s18n-5b49260517622682a058b69f996d06eb": "Vielen Dank für deinen Kommentar!",
"s18n-74196a783a6f1707a43cc8117f0d9c83": "Vielen Dank für deinen Kommentar, bitte bestätige noch dein Abonnement über die zugesandte eMail!",
"s18n-a939eb542e34cd502b3f7352b2e0f715": "Vielen Dank für deine Bewertung!",
"s18n-d2a9677817ee08ed05bf9fd868669756": "Ein unbekannter Fehler ist aufgetreten, bitte lade die Seite neu!",
"s18n-05b85714aa8f1b364f930e2539059b5e": "Ein Fehler ist aufgetreten: Der Benutzer ist ungültig (Max. 42 Zeichen)!",
"s18n-321e8b481f0ccd62df535256e8e6d2c6": "Ein Fehler ist aufgetreten: Die eMail Adresse ist ungültig!",
"s18n-fcd0c3a087c5123ffdecc20fe9015870": "Ein Fehler ist aufgetreten: Der Kommentar-Text fehlt!",
"s18n-2afa2d90ce3343fbe188b9f49ad5797d": "Ein Fehler ist aufgetreten: Der Kommentar-Titel fehlt!",
"s18n-bee1741efe0c735d2c7180771586faf0": "Ein Fehler ist aufgetreten: Du musst die Nutzungsbedingungen akzeptieren!",
"s18n-92fe96d6ccee901f94fad0000369a9b7": "Ein Fehler ist aufgetreten: Deine IP oder eMail Adresse wurde als SPAM markiert!",
"s18n-9b264fc6137096f8a40acde68f6ae562": "Ein Fehler ist aufgetreten: Du hast diesen Kommentar bereits bewertet!",
"s18n-ca62db4704290ef1a7e65df3ffc7983b": "Ich stimme der Speicherung der Daten (inkl. meiner anonymisierten IP) zu!",
"s18n-01c611362e8b046f32650b85ce161559": "Ein unbekannter Fehler ist aufgetreten!",
"s18n-c4f94c6995b0376f28276e432bed75fa": "Das Sicherheitstoken fehlt!",
"s18n-a573e7b86e41522c7a291846aa109104": "Das Sicherheitstoken ist ungültig!",
"s18n-3e8909518ce4728685aa09cdde3caa22": "Du hast nicht die Berechtigung diese Aktion aufzurufen!",
"s18n-b60a6a8a529a9f0497134205bab15e77": "Du hast nicht die Berechtigung diese Aktion duchzuführen!",
"s18n-3fee90e2f59aeb29b74c1c21648ba712": "Das Captcha Bild konnte erfolgreich erstellt werden!",
"s18n-56abae3e615ae5b5609c32852b777d46": "Die gewünschte Aktion ist ungültig doer unvollständig!",
"s18n-be5136b4f2b33c80e3afd377ee993acb": "Ein unbekannter Fehler ist aufgetreten!",
"s18n-46d4c97e91319867654f7cc80c439ba4": "Diese einzigartige Nutzer-ID existiert nicht!",
"s18n-af1ba1dd4eab5562f78c65bc89a0a7e9": "Die Aktion wurde erfolgreich durchgeführt!",
"s18n-a5193444ee82c18bac726b35a1704d03": "Die Einstellungen wurden erfolgreich aktualisiert!",
"s18n-717c8267d40664ccf7ef25a26ff9cde6": "Das Backup wurde erfolgreich erstellt!",
"s18n-a1e2b7401861cee01c172878f104bd8c": "Kommentare deaktivieren",
"s18n-15802277ea1cdfcbacd6308fa0c7c30f": "Snicker Plugin deaktivieren",
"s18n-877d58f21c87442efa4081112a6cb07a": "Du bist das das <b>Snicker<\/b> Plugin zu deaktivieren, was sämtliche Kommentare löschen würde!",
"s18n-a4983c86683f8d8598c0513339550dc0": "Möchtest du die Kommentare vorher sichern?",
"s18n-4cce9e52118cc659be5070b2f08cdd91": "Das Backup wird unter %s gespeichert!",
"s18n-5f87fd2e0fa992d37c814bb4ca299646": "Ja, Backup erstellen",
"s18n-3d414feb412a1f4cd9324f6411c76329": "Nein, einfach deativieren",
"s18n-10aec35353f9c4096a71c38654c3d402": "Abbrechen",
"s18n-bc6d6a26d44b6f39a0e7b6c7787f3295": "Die Kommentar-Sektion auf dieser Seite wurde deaktiviert!",
"s18n-a8e30d73eddea9866cf99ecd6e8467b5": "Es wurden noch keine Kommentare verfasst, sei der erste!",
"s18n-b3afbadaa2f1c79f8b3999be7fd9719f": "Die Antwort auf das Captcha wurde nicht übermittelt oder war falsch!",
"s18n-fb74aafe8bf1fd4d8f7a6e1ff73028f7": "Die Kommentar ID existiert nicht oder ist ungültig!",
"s18n-25dea7cb70f98250b388f6ab0ddf20cb": "Der Kommentar Status existiert nicht oder ist ungültig!",
"s18n-2cd68855bdc54ff5e3c191a6333ff75d": "Der neue Kommentar Status konnte nicht gespeichert werden!",
"s18n-c0937505b7afa81a053077bc7ae369a5": "Der neue Kommentar Status wurde erfolgreich gespeichert!",
"s18n-6b2c8084c67f24bb73d95031bb570ef7": "Der Kommentar konnte nicht erfolgreich gelöscht werden!",
"s18n-376388311a80dbde63fde7f6c72081e0": "Der Kommentar konnte erfolgreich gelöscht werden!",
"s18n-83bbb9e8745cc95e730fe7b8de9345f1": "Angemeldet als %s (%s)",
"s18n-91fb98e1ac4cf76b7a5b8bae09051e2a": "Dein Benutzername",
"s18n-31f6da7a30e7acf1f82451bfd1a7f8fa": "Deine eMail Adresse",
"s18n-f794080a5a29e35233c82df85f1207eb": "Dein Kommentar...",
"s18n-a363b8d13575101a0226e8d0d054f2e7": "Antworten",
"s18n-f10db888c5e63b343000cffc038e0a46": "schrieb",
"s18n-3cc5bcf15d6b8faed118e2ce72d19a1e": "Ich stimme der %s zu!",
"s18n-2af0aab477f402e0f4ad7a27e6c9f952": "Vorherigen Kommentare",
"s18n-8538431db22040e2147b363f86a2e2f0": "Nächsten Kommentare",
"s18n-ae0dbd5cc42a6db191db5e0083bcb307": "Dieser Kommentare wurde noch nicht moderiert!",
"s18n-48df9c3f3cca3fb2b8bcf811633bee06": "Geschrieben von %s",
"s18n-81fdc9813cebe0553c55e78dc2b6029f": "am %s",
"s18n-be1ab1632e4285edc3733b142935c60b": "Gefällt Mir",
"s18n-bc8b79025e4595298669fd21da814941": "Gefällt Mir nicht",
"s18n-e84afaab83ecb301b3d97ce4174d2773": "Antworten"
}

204
languages/en.json

@ -0,0 +1,204 @@
{
"plugin-data": {
"name": "Snicker",
"description": "A native, AJAX-enabled FlatFile Comment system for bludit, including a comment subscription and completely compliant with the GDPR!"
},
"s18n-a5d491060952aa8ad5fdee071be752de": "Comments",
"s18n-de95b43bceeb4b998aed4aed5cef1ae7": "Edit",
"s18n-f984023ed3d6df2326c9d59838c29792": "Update Comment",
"s18n-86448a506dd93303a72140b9124ee321": "Delete Comment",
"s18n-db10a8eb963bc0e5f4483ac9b5dc554c": "Comment Title",
"s18n-335630425567dbe91768a3beffdec752": "Comment Text",
"s18n-bd7e63f881c7f787a9a0dce20b7f9e5b": "Meta Settings",
"s18n-1acfe725df7bd12195751f0737c4d375": "Registered User",
"s18n-e988189db402fab453f72052629c02cd": "Comment Username",
"s18n-db84c6236ca6a01b9189504d78c012a5": "Comment Email",
"s18n-7c6c2e5d48ab37a007cbf70d3ea25fa4": "Pending",
"s18n-787d5f05953ec39b108869dfdd7733e6": "Approved",
"s18n-c7537d6d48ecf261749c09a9f284bd45": "Rejected",
"s18n-e09f6a7593f8ae3994ea57e1117f67ec": "Spam",
"s18n-7a5115c2c1eb662308decbec83593494": "View Page",
"s18n-0572a05cd6d6360391993a611099542f": "Comment Title or Excerpt",
"s18n-428f14500191b5d53675da4a96bc8bba": "Search Comments",
"s18n-e0be71bccdeceb713fba3c222f79a3c5": "No Comments available",
"s18n-06d4cd63bde972fc66a0aed41d2f5c51": "Comment",
"s18n-02bd92faa38aaa6cc0ea75e59937a1ef": "Author",
"s18n-ebb67a4271abe715344471b0f16321f6": "Actions",
"s18n-58566b9a9b2733b0ceacb2186672b5d1": "Show all replies",
"s18n-5f44c0081bd862a77ba8b24e923cadf1": "Reply To",
"s18n-eb399bcaca686f8609137153307eecf1": "Change",
"s18n-2736f4347985da50dc023444c193bfea": "Edit Comment",
"s18n-a107bf4b12e36a07161a26d95b03bc81": "Approve Comment",
"s18n-da937abd19cd9e1430470b8a471a41d4": "Reject Comment",
"s18n-32cb4199893d9948cc0853eef244f1fc": "Mark as Spam",
"s18n-a6494adfb72d12d3a4da66855c284ec6": "Back to Pending",
"s18n-1bda80f2be4d3658e0baa43fbe7ae8c1": "View",
"s18n-c9ae5a4214e87ad6fbed44a267471eee": "Save Settings",
"s18n-e124d357c3c832434a8676a5e18db842": "General Settings",
"s18n-b62a9dcd666f3ff44197cf21ac66507d": "Comment Moderation",
"s18n-7f0217cdcdd58ba86aae84d9d3d79f81": "Moderate",
"s18n-1a1dc91c907325c69271ddf0c944bc72": "Pass",
"s18n-df6963dc912cde9baeef10343167ba01": "each Comment",
"s18n-39ba4181e212acf183ac965c9b37da89": "Unless the user is logged in",
"s18n-0dac4426a017f0a0370db32776bc30bb": "Unless the user is admin or the content author",
"s18n-c70aa8b8fd6f2652eda2d5366faf1de5": "Unless the user has an already approved comment",
"s18n-8b26cab9d444760b4bcc65dc4d8634f8": "Allow Comments",
"s18n-67614909bf9de326de71946036de39f1": "... on Public Pages",
"s18n-a7c384c1b60785c40bc3a4e4dfc5a108": "... on Sticky Pages",
"s18n-9907b78f8745810599dbf6fd29a01364": "... on Static Pages",
"s18n-c89cbecce48d04cd76c0b95c8128ad97": "Enable (Optional)",
"s18n-e9d51286fdd0ff058650392fc8b6ae30": "Enable (Required)",
"s18n-0aaa87422396fdd678498793b6d5250e": "Disable",
"s18n-8da2c8185edfeb1765526f8e2e4f388d": "Comment Limit",
"s18n-77d6d185c93549dab24f29ff2e3b25a8": "Use '0' to disable any limit!",
"s18n-1ef52691308c8add87723a4103a561c2": "Comment Depth",
"s18n-d102731a5fedff24f30e24e883ef4636": "Comment Markup",
"s18n-6640979a191e66655c26c59d404bf955": "Allow Basic HTML",
"s18n-915009e874f8bed1845060012f826fcd": "Allow Markdown",
"s18n-a9a36cb3d8f4f7297ebca99a322d6342": "Comment Voting",
"s18n-2123546d1ff8b0cb035df0c0b0d06825": "Store Votes made by Guests in the",
"s18n-dead693ab29895d302fca0e6baad6182": "Cookie Storage",
"s18n-02b68043bdcae159e83199d64a5abd7d": "Session Storage",
"s18n-40bd8791e523e91219886c35622163fc": "Database Storage",
"s18n-8717cfca734e8987971f63b20eeb8024": "What?",
"s18n-5a8cbcf57f5b59f0d4b8ded97d018399": "The <b>Cookie Storage<\/b> is located on the Computer of the user. So you don't have the full control AND you require the appropriate permissions from the user.",
"s18n-a09dbdf907873e66d9d644cca71970d5": "The <b>Session Storage<\/b> is just stored temporary on the server, it gets cleaned up when the user closes the browser. Therefore you don't need any permissions from the user.",
"s18n-71892ebe01ba92d1f163dc37d818b5ff": "The <b>Database Storage<\/b> generates and stores an anonymized but assignable value of the user, which also requires the appropriate permissions from the user.",
"s18n-7c35a0bcb0b0678f0829036eead5ddca": "<b>Please Note:<\/b> You are responsible for obtaining the appropriate permissions, Snicker just handles the permissions for data sent (and stored) via the comment form!",
"s18n-82e5228061f185ee185bd9f3ecba4ee7": "Allow to %s comments",
"s18n-61b58693e0eceeb27ce0cc3b25b3bf31": "Frontend Settings",
"s18n-b3c1c2c231275878abe58a55966fa9e0": "Page Filter",
"s18n-9cb1eef8966f93282524929f65c8b9ec": "Disable Page Filter",
"s18n-e89fd56cefec9baabcbe0db3e5a36962": "Use 'pageBegin'",
"s18n-3d08b5dcc1e5c3c7e7e2eaf2d0d6a12d": "Use 'pageEnd'",
"s18n-3771d05b6af4ebb0a303266c47809548": "Use 'siteBodyBegin'",
"s18n-42438edc41ab83312486009e3122e92b": "Use 'siteBodyEnd'",
"s18n-948da5199de32c7601a20b6107c31d4d": "Comment Captcha",
"s18n-47e5c42fb9bdca3636a5d866a6794101": "Disable Captcha",
"s18n-ab6ef7ef94efc86db78218c6c265243a": "Use OWASP's PureCaptcha",
"s18n-a58adce0085cc1a25fc8076e97c29d70": "Use Gregway's Captcha",
"s18n-9d2f2ec577e7383b88fd481d6c566c5e": "Use Gregway's Captcha (GD library is missing!)",
"s18n-09cddbc3627ea46a8dce692d64273b61": "Use Googles reCaptcha (Not available yet)",
"s18n-838a13e4fece7c272b960da3fb99f94d": "Comment Template",
"s18n-6f95370a28520696b2a0ad34efc54d2d": "Comment Order",
"s18n-4a8dc1710396b21e7b1da8112c07c4ad": "Newest Comments First",
"s18n-2dab3b12d0b0642c3964b37d675ff24b": "Oldest Comments First",
"s18n-230c71c29590608034b4a590a67ace31": "Comment Form Position",
"s18n-37d988444dec2001c488806fc8401e25": "Show Comment Form above Comments",
"s18n-16ac6c11951d825826f77a4097a1c2cb": "Show Comment Form below Comments",
"s18n-1e98ec9312b69676d5e3fe3caf8ecde1": "Comments Per Page",
"s18n-0d46f4389ca5f882e24899fe489bf344": "Use '0' to show all available comments!",
"s18n-7b6b84fbd65a4b712a5ba0dccce176d5": "Terms of Use Checkbox",
"s18n-9853383062a2e308d5aed35fe3da7953": "Disable this field",
"s18n-352fd1d7225b5ea02b8ddd9fad0d6e34": "Show Message (See Strings)",
"s18n-71860c77c6745379b0d44304d66b6a13": "Page",
"s18n-5dcb84333ae70a5bed60bf70d34dcd2b": "Show the default GDPR Text or Select your own static 'Terms of Use' page!",
"s18n-2e3d9327c371afb7489f9b9278198622": "AJAX Script",
"s18n-c60cab6330745b41cbee05603eec6691": "Embed AJAX Script",
"s18n-69b11f64af515ef979fbf28c5e06f370": "Don't use AJAX",
"s18n-9bbec7b57565f06d73522669d3b836dc": "The AJAX Script hands over the request (comment, like, dislike) directly without reloading the page!",
"s18n-7c74c0d2d1c28d1568298c89742ce126": "Comment Avatar",
"s18n-aeab7c630dae161d8f6e2898dd83b471": "Use Gravatar",
"s18n-882e3436da897c055cc3f8bd2598b71a": "Use Identicon",
"s18n-d92d61ad0c0065170a37a1805ad1bc9e": "Use Mystery Men",
"s18n-b4a51a35344f9a8fa1139cfd968ab308": "Use & Prefer profile pictures on logged-in Users",
"s18n-b50a4a96c25d745d73114af5a4b03145": "Comment Gravatar",
"s18n-9ffb941a398ddee8e054eef3292c546e": "Show Mystery Person",
"s18n-a7dd12b1dab17d25467b0b0a4c8d4a92": "Show",
"s18n-0d2fc085ee57276417cf380027060760": "The default Gravatar image, if the user has no Gravatar!",
"s18n-58e2aacf5792087168cbc62578584ecd": "Subscription Settings",
"s18n-8290ca86b8980a14bd46f34017e03f93": "The Subscription system isn't available yet!",
"s18n-0bd7ff1b4ac56a9616796bdc05609de2": "Email Subscription",
"s18n-208f156d4a803025c284bb595a7576b4": "Enable",
"s18n-dc985a7c2144c6447674e674aee08441": "Email 'From' Address",
"s18n-f6db5b4db3f9c1ba0ffc091abc561802": "Email 'ReplyTo' Address",
"s18n-8d868315d258783a95336d1a6ce27e1e": "Email Body (Opt-In)",
"s18n-7721b2a2f2a453cc790e3ac7065e9b65": "Use default Subscription Email",
"s18n-58ff11585a82c73f5117c91c29cb3f63": "Email Body (Notification)",
"s18n-bea9ff19efca028c01617da5dce18171": "Use default Notification Email",
"s18n-e0024a8886a178d4f70b8c888b701680": "Read more about a custom Notification Emails %s!",
"s18n-8bcf6629759bd278a5c6266bd9c054f8": "Strings",
"s18n-3979b4205954030810a8a87769348094": "Default Thanks Message",
"s18n-620b528248b36bf743d1ad33e35022d6": "Thanks Message with Subscription",
"s18n-e83f6b01d1c81313f6b388281e13aacf": "Thanks Message for Voting",
"s18n-01b7a6bc6b57783b4fd081085ff3271e": "Error: Unknown Error, Try again",
"s18n-19678b1419eff34dffd41d6778b1aa89": "Error: Username is invalid",
"s18n-1255e6794d33b443bcee21279d9caa1a": "Error: Email Address is invalid",
"s18n-ebf545757b92b3a553d83ea3db48beca": "Error: Comment Text is missing",
"s18n-f1bc53e2456c425578277adbc7c90f3a": "Error: Comment Title is missing",
"s18n-53c8fdd7ed497dbacbef5fd5d4f38f3d": "Error: Terms not accepted",
"s18n-89f8cc478fdfc0a8f36c1b393f39677a": "Error: Marked as SPAM",
"s18n-59ad5c9fcee1b28a5d004bcf684a5acd": "Error: Already Voted",
"s18n-56ef2c600b4af0f9f7b35640525967ca": "Terms of Use",
"s18n-08400c2e0f51197fdb3590461b15b2cc": "Username or Email Address",
"s18n-b79edd2e426f90401c04869346b503c7": "Search Users",
"s18n-ee51fa9d5097c84d2fa6c885bf2d5d84": "No Users available",
"s18n-14c4b06b824ec593239362517f538b29": "Username",
"s18n-0c83f57c786a0b4a39efab23731c7ebc": "Email",
"s18n-e1260894f59eeae98c8440899de4df8d": "Handle",
"s18n-4bc61296b766756f1c7296489633bf32": "Delete (Anonymize)",
"s18n-c0f4afd3614929f1c803f3a01414a6c7": "Delete (Completely)",
"s18n-6356f32d0c02c8f90cb59a77e16e8fe2": "Unblock User",
"s18n-2327a01afbee025fb5913357c9d6b1b3": "Block User",
"s18n-76a0f6752a45d8af6343ef3e2b6f522a": "Single Comment",
"s18n-c426859e50a35617d863cdad2b9c84aa": "Page Comments",
"s18n-a64d776275f13a51790bb460774b9129": "User Comments",
"s18n-9bc65c2abec141778ffaa729489f3e87": "Users",
"s18n-ccd1066343c95877b75b79d47c36bebe": "Configuration",
"s18n-5b49260517622682a058b69f996d06eb": "Thanks for your comment!",
"s18n-74196a783a6f1707a43cc8117f0d9c83": "Thanks for your comment, please confirm your subscription via the link we sent to your Email address!",
"s18n-a939eb542e34cd502b3f7352b2e0f715": "Thanks for voting this comment!",
"s18n-d2a9677817ee08ed05bf9fd868669756": "An unknown error occured, please reload the page and try it again!",
"s18n-05b85714aa8f1b364f930e2539059b5e": "An error occured: The passed Username is invalid or too long (42 characters only)!",
"s18n-321e8b481f0ccd62df535256e8e6d2c6": "An error occured: The passed Email address is invalid!",
"s18n-fcd0c3a087c5123ffdecc20fe9015870": "An error occured: The comment text is missing!",
"s18n-2afa2d90ce3343fbe188b9f49ad5797d": "An error occured: The comment title is missing!",
"s18n-bee1741efe0c735d2c7180771586faf0": "An error occured: You need to accept the Terms to comment!",
"s18n-92fe96d6ccee901f94fad0000369a9b7": "An error occured: Your IP address or Email address has been marked as Spam!",
"s18n-9b264fc6137096f8a40acde68f6ae562": "An error occured: You already rated this comment!",
"s18n-ca62db4704290ef1a7e65df3ffc7983b": "I agree that my data (incl. my anonymized IP address) gets stored!",
"s18n-01c611362e8b046f32650b85ce161559": "An unknown error occured!",
"s18n-c4f94c6995b0376f28276e432bed75fa": "The CSRF Token is missing!",
"s18n-a573e7b86e41522c7a291846aa109104": "The CSRF Token is invalid!",
"s18n-3e8909518ce4728685aa09cdde3caa22": "You don't have the permission to call this action!",
"s18n-b60a6a8a529a9f0497134205bab15e77": "You don't have the permission to perform this action!",
"s18n-3fee90e2f59aeb29b74c1c21648ba712": "The Captcha image could be successfully created!",
"s18n-56abae3e615ae5b5609c32852b777d46": "The passed action is unknown or invalid!",
"s18n-be5136b4f2b33c80e3afd377ee993acb": "An unknown error is occured!",
"s18n-46d4c97e91319867654f7cc80c439ba4": "A unique user ID does not exist!",
"s18n-af1ba1dd4eab5562f78c65bc89a0a7e9": "The action has been performed successfully!",
"s18n-a5193444ee82c18bac726b35a1704d03": "The settings has been updated successfully!",
"s18n-717c8267d40664ccf7ef25a26ff9cde6": "The backup has been created successfully!",
"s18n-a1e2b7401861cee01c172878f104bd8c": "Disallow Comments",
"s18n-15802277ea1cdfcbacd6308fa0c7c30f": "Snicker Plugin Deactivation",
"s18n-877d58f21c87442efa4081112a6cb07a": "You are about to deactivate the <b>Snicker<\/b> Plugin, which will delete all written comments!",
"s18n-a4983c86683f8d8598c0513339550dc0": "Do you want to backup your comments before?",
"s18n-4cce9e52118cc659be5070b2f08cdd91": "The backup will be stored in %s!",
"s18n-5f87fd2e0fa992d37c814bb4ca299646": "Yes, create a backup",
"s18n-3d414feb412a1f4cd9324f6411c76329": "No, just deactivate",
"s18n-10aec35353f9c4096a71c38654c3d402": "Cancel",
"s18n-bc6d6a26d44b6f39a0e7b6c7787f3295": "The comment section on this page has been disabled!",
"s18n-a8e30d73eddea9866cf99ecd6e8467b5": "Currently there are no comments, so be the first!",
"s18n-b3afbadaa2f1c79f8b3999be7fd9719f": "The answer to the Captcha hasn't been passed or is wrong!",
"s18n-fb74aafe8bf1fd4d8f7a6e1ff73028f7": "The comment UID doesn't exist or is invalid!",
"s18n-25dea7cb70f98250b388f6ab0ddf20cb": "The comment status is unknown or invalid!",
"s18n-2cd68855bdc54ff5e3c191a6333ff75d": "The new comment status couldn't be updated!",
"s18n-c0937505b7afa81a053077bc7ae369a5": "The new comment status has been stored successfully!",
"s18n-6b2c8084c67f24bb73d95031bb570ef7": "The comment couldn't deleted!",
"s18n-376388311a80dbde63fde7f6c72081e0": "The comment has been deleted!",
"s18n-83bbb9e8745cc95e730fe7b8de9345f1": "Logged in as %s (%s)",
"s18n-91fb98e1ac4cf76b7a5b8bae09051e2a": "Your Username",
"s18n-31f6da7a30e7acf1f82451bfd1a7f8fa": "Your Email Address",
"s18n-f794080a5a29e35233c82df85f1207eb": "Your Comment...",
"s18n-a363b8d13575101a0226e8d0d054f2e7": "Answer",
"s18n-f10db888c5e63b343000cffc038e0a46": "wrote",
"s18n-3cc5bcf15d6b8faed118e2ce72d19a1e": "I agree the %s!",
"s18n-2af0aab477f402e0f4ad7a27e6c9f952": "Previous Comments",
"s18n-8538431db22040e2147b363f86a2e2f0": "Next Comments",
"s18n-ae0dbd5cc42a6db191db5e0083bcb307": "This comment hasn't been moderated yet!",
"s18n-48df9c3f3cca3fb2b8bcf811633bee06": "Written by %s",
"s18n-81fdc9813cebe0553c55e78dc2b6029f": "on %s",
"s18n-be1ab1632e4285edc3733b142935c60b": "Like",
"s18n-bc8b79025e4595298669fd21da814941": "Dislike",
"s18n-e84afaab83ecb301b3d97ce4174d2773": "Reply"
}

200
languages/es.json

@ -0,0 +1,200 @@
{
"plugin-data": {
"name": "Snicker",
"description": "Sistema de comentarios para Bludit. Incluye suscripción a comentarios y es completamente compatible con el RGPD (Reglamento General de Protección de datos). Usa tecnologías AJAX y Flatfile. Ligero y seguro." },
"s18n-a5d491060952aa8ad5fdee071be752de": "Comentarios",
"s18n-de95b43bceeb4b998aed4aed5cef1ae7": "Editar",
"s18n-f984023ed3d6df2326c9d59838c29792": "Actualizar",
"s18n-86448a506dd93303a72140b9124ee321": "Borrar",
"s18n-db10a8eb963bc0e5f4483ac9b5dc554c": "Título de comentario",
"s18n-335630425567dbe91768a3beffdec752": "Texto de comentario",
"s18n-bd7e63f881c7f787a9a0dce20b7f9e5b": "Configuración de metadatos",
"s18n-1acfe725df7bd12195751f0737c4d375": "Registro de usuario",
"s18n-e988189db402fab453f72052629c02cd": "Usuario",
"s18n-db84c6236ca6a01b9189504d78c012a5": "Email",
"s18n-7c6c2e5d48ab37a007cbf70d3ea25fa4": "Pendiente",
"s18n-787d5f05953ec39b108869dfdd7733e6": "Aprobado",
"s18n-c7537d6d48ecf261749c09a9f284bd45": "Rechazado",
"s18n-e09f6a7593f8ae3994ea57e1117f67ec": "Spam",
"s18n-7a5115c2c1eb662308decbec83593494": "Ver página",
"s18n-0572a05cd6d6360391993a611099542f": "Título de comentario",
"s18n-428f14500191b5d53675da4a96bc8bba": "Buscar comentarios",
"s18n-e0be71bccdeceb713fba3c222f79a3c5": "No hay comentarios aun",
"s18n-06d4cd63bde972fc66a0aed41d2f5c51": "Comentar",
"s18n-02bd92faa38aaa6cc0ea75e59937a1ef": "Autor",
"s18n-ebb67a4271abe715344471b0f16321f6": "Acciones",
"s18n-58566b9a9b2733b0ceacb2186672b5d1": "Mostrar todas las respuestas",
"s18n-5f44c0081bd862a77ba8b24e923cadf1": "Responder a",
"s18n-eb399bcaca686f8609137153307eecf1": "Cambiar",
"s18n-2736f4347985da50dc023444c193bfea": "Editar Comentario",
"s18n-a107bf4b12e36a07161a26d95b03bc81": "Aprobar Comentario",
"s18n-da937abd19cd9e1430470b8a471a41d4": "Rechazar Comentario",
"s18n-32cb4199893d9948cc0853eef244f1fc": "Marcar como espam",
"s18n-a6494adfb72d12d3a4da66855c284ec6": "Archivar como pendiente",
"s18n-1bda80f2be4d3658e0baa43fbe7ae8c1": "Ver",
"s18n-c9ae5a4214e87ad6fbed44a267471eee": "Guardar la configuración",
"s18n-e124d357c3c832434a8676a5e18db842": "Configuración General",
"s18n-b62a9dcd666f3ff44197cf21ac66507d": "Moderación",
"s18n-7f0217cdcdd58ba86aae84d9d3d79f81": "Moderar",
"s18n-1a1dc91c907325c69271ddf0c944bc72": "Aprobar",
"s18n-df6963dc912cde9baeef10343167ba01": "por defecto",
"s18n-39ba4181e212acf183ac965c9b37da89": "Aprobar usuarios identificados",
"s18n-0dac4426a017f0a0370db32776bc30bb": "Aprobar roles de admin o autor",
"s18n-c70aa8b8fd6f2652eda2d5366faf1de5": "Aprobar si ya tiene algún comentario aprobado",
"s18n-8b26cab9d444760b4bcc65dc4d8634f8": "Permitir comentarios",
"s18n-67614909bf9de326de71946036de39f1": "... en páginas Publicadas",
"s18n-a7c384c1b60785c40bc3a4e4dfc5a108": "... en páginas Ancladas (sticky)",
"s18n-9907b78f8745810599dbf6fd29a01364": "... en páginas Estáticas",
"s18n-c89cbecce48d04cd76c0b95c8128ad97": "Activo (Opcional)",
"s18n-e9d51286fdd0ff058650392fc8b6ae30": "Activo (Requerido)",
"s18n-0aaa87422396fdd678498793b6d5250e": "Inactivo",
"s18n-8da2c8185edfeb1765526f8e2e4f388d": "Limite de comentarios",
"s18n-77d6d185c93549dab24f29ff2e3b25a8": "'0' si no deseas límite!",
"s18n-1ef52691308c8add87723a4103a561c2": "Extensión de comentario",
"s18n-d102731a5fedff24f30e24e883ef4636": "Formato de comentario",
"s18n-6640979a191e66655c26c59d404bf955": "Permitir HTML",
"s18n-915009e874f8bed1845060012f826fcd": "Permitir Markdown",
"s18n-a9a36cb3d8f4f7297ebca99a322d6342": "Votos de comentarios",
"s18n-2123546d1ff8b0cb035df0c0b0d06825": "Los votos se almacenan en",
"s18n-dead693ab29895d302fca0e6baad6182": "Cookies",
"s18n-02b68043bdcae159e83199d64a5abd7d": "Sesiones",
"s18n-40bd8791e523e91219886c35622163fc": "Base de datos",
"s18n-8717cfca734e8987971f63b20eeb8024": "Qué?",
"s18n-5a8cbcf57f5b59f0d4b8ded97d018399": "Las <b>Cookies<\/b> se localizan en la computadora del usuario. Precisas su consentimiento y no tienes control",
"s18n-a09dbdf907873e66d9d644cca71970d5": "Las <b>Sesiones<\/b> se guardan en el servidor temporalmente. Desaparecen cuando caducan o el usuario cierra el navegador. No precisas permiso del usuario.",
"s18n-71892ebe01ba92d1f163dc37d818b5ff": "La <b>Base de datos<\/b> se guardan indefinidamente en el servido, pero como identifican al usuario, precisaras de su permiso.",
"s18n-7c35a0bcb0b0678f0829036eead5ddca": "<b>Atención!!:<\/b> usted es responsable de obtener los permisos apropiados, Snicker solo maneja los permisos para el envío de datos (y el almacenamiento) a través del formulario de comentarios!",
"s18n-82e5228061f185ee185bd9f3ecba4ee7": "Permitir a %s comentarios",
"s18n-61b58693e0eceeb27ce0cc3b25b3bf31": "Configuración de la vista de usuario",
"s18n-b3c1c2c231275878abe58a55966fa9e0": "Posición en la página",
"s18n-9cb1eef8966f93282524929f65c8b9ec": "Sin posición (deberá habilitarse en la plantilla)",
"s18n-e89fd56cefec9baabcbe0db3e5a36962": "Usar comienzo de página 'pageBegin'",
"s18n-3d08b5dcc1e5c3c7e7e2eaf2d0d6a12d": "Usar final de página 'pageEnd'",
"s18n-3771d05b6af4ebb0a303266c47809548": "Usar comienzo de cuerpo html 'siteBodyBegin'",
"s18n-42438edc41ab83312486009e3122e92b": "Usar final de cuerpo html 'siteBodyEnd'",
"s18n-948da5199de32c7601a20b6107c31d4d": "Captcha",
"s18n-47e5c42fb9bdca3636a5d866a6794101": "Deshabilitar Captcha",
"s18n-eedf6d23d18212016e22428658e17794": "Usar local Captcha (by Gregway)",
"s18n-09cddbc3627ea46a8dce692d64273b61": "Usar Googles reCaptcha (aun no disponible)",
"s18n-838a13e4fece7c272b960da3fb99f94d": "Plantilla de comentarios",
"s18n-6f95370a28520696b2a0ad34efc54d2d": "Orden de comentarios",
"s18n-4a8dc1710396b21e7b1da8112c07c4ad": "Primero recientes",
"s18n-2dab3b12d0b0642c3964b37d675ff24b": "Primero antiguos",
"s18n-230c71c29590608034b4a590a67ace31": "Posición del formulario",
"s18n-37d988444dec2001c488806fc8401e25": "Enseñar comentarios antes del formulario",
"s18n-16ac6c11951d825826f77a4097a1c2cb": "Enseñar comentarios tras el formulario",
"s18n-1e98ec9312b69676d5e3fe3caf8ecde1": "Comentarios por página",
"s18n-0d46f4389ca5f882e24899fe489bf344": "'0' si no deseas límite!",
"s18n-7b6b84fbd65a4b712a5ba0dccce176d5": "Casilla de Términos de Uso",
"s18n-9853383062a2e308d5aed35fe3da7953": "Desactivar este campo",
"s18n-352fd1d7225b5ea02b8ddd9fad0d6e34": "Mostrar mensaje (ver Frases por Defecto)",
"s18n-71860c77c6745379b0d44304d66b6a13": "Página",
"s18n-5dcb84333ae70a5bed60bf70d34dcd2b": "Mostrar texto por defecto de RGPD (Reglamento General de Protección de datos) o tu propia página!",
"s18n-2e3d9327c371afb7489f9b9278198622": "AJAX Script",
"s18n-c60cab6330745b41cbee05603eec6691": "AJAX Script disponible",
"s18n-69b11f64af515ef979fbf28c5e06f370": "No usar AJAX",
"s18n-9bbec7b57565f06d73522669d3b836dc": "Con AJAX acciones como comentar, me gusta, o no me gusta se realizan sin tener que actualizar la página!",
"s18n-7c74c0d2d1c28d1568298c89742ce126": "Avatares",
"s18n-aeab7c630dae161d8f6e2898dd83b471": "Usar Gravatar",
"s18n-882e3436da897c055cc3f8bd2598b71a": "Usar Identicon",
"s18n-d92d61ad0c0065170a37a1805ad1bc9e": "Usar Persona Misteriosa",
"s18n-b4a51a35344f9a8fa1139cfd968ab308": "Usar imagen de perfil de usuario si está registrado en la web",
"s18n-b50a4a96c25d745d73114af5a4b03145": "Configuaración Gravatar",
"s18n-9ffb941a398ddee8e054eef3292c546e": "Mostrar Persona Misteriosa",
"s18n-a7dd12b1dab17d25467b0b0a4c8d4a92": "Mostrar",
"s18n-0d2fc085ee57276417cf380027060760": "Imagen Gravatar por defecto si el usuario no dispone de Gravatar!",
"s18n-58e2aacf5792087168cbc62578584ecd": "Configuración de subscripción",
"s18n-8290ca86b8980a14bd46f34017e03f93": "El sistema de suscripción aun no está disponible!",
"s18n-0bd7ff1b4ac56a9616796bdc05609de2": "Subscripción por Email",
"s18n-208f156d4a803025c284bb595a7576b4": "Activo",
"s18n-dc985a7c2144c6447674e674aee08441": "Email 'From'",
"s18n-f6db5b4db3f9c1ba0ffc091abc561802": "Email 'ReplyTo'",
"s18n-8d868315d258783a95336d1a6ce27e1e": "Cuerpo de Email (Opt-In)",
"s18n-7721b2a2f2a453cc790e3ac7065e9b65": "Usar el email por defecto para la suscripción",
"s18n-58ff11585a82c73f5117c91c29cb3f63": "Cuerpo de Email (Notificación)",
"s18n-bea9ff19efca028c01617da5dce18171": "Usar el email por defecto para las notificaciones",
"s18n-e0024a8886a178d4f70b8c888b701680": "Conoce más sobre las notificaciones de Email %s!",
"s18n-8bcf6629759bd278a5c6266bd9c054f8": "Frases por Defecto",
"s18n-3979b4205954030810a8a87769348094": "Mensajes por defecto de agradecimiento",
"s18n-620b528248b36bf743d1ad33e35022d6": "Por suscripción",
"s18n-e83f6b01d1c81313f6b388281e13aacf": "Por votar",
"s18n-01b7a6bc6b57783b4fd081085ff3271e": "Opss!: Algo salió mal. Prueba de nuevo.",
"s18n-19678b1419eff34dffd41d6778b1aa89": "Opss!: Nombre de usuario incorrecto",
"s18n-1255e6794d33b443bcee21279d9caa1a": "Opss!: Dirección de correo incorrecta",
"s18n-ebf545757b92b3a553d83ea3db48beca": "Opss!: Texto de comentario vacío",
"s18n-f1bc53e2456c425578277adbc7c90f3a": "Opss!: Título de comentario vacío",
"s18n-53c8fdd7ed497dbacbef5fd5d4f38f3d": "Opss!: No has aceptado los términos",
"s18n-89f8cc478fdfc0a8f36c1b393f39677a": "Opss!: Se ha marcado como Spam",
"s18n-59ad5c9fcee1b28a5d004bcf684a5acd": "Opss!: Ya ha sido votado",
"s18n-56ef2c600b4af0f9f7b35640525967ca": "Términos de uso",
"s18n-08400c2e0f51197fdb3590461b15b2cc": "Nombre de usuario o Email",
"s18n-b79edd2e426f90401c04869346b503c7": "Busqueda de usuarios",
"s18n-ee51fa9d5097c84d2fa6c885bf2d5d84": "No hay usuarios disponibles",
"s18n-14c4b06b824ec593239362517f538b29": "Nombre de usuarios",
"s18n-0c83f57c786a0b4a39efab23731c7ebc": "Email",
"s18n-e1260894f59eeae98c8440899de4df8d": "Encargarse de",
"s18n-4bc61296b766756f1c7296489633bf32": "Borrar (Anonimizar)",
"s18n-c0f4afd3614929f1c803f3a01414a6c7": "Borrar (Completamente)",
"s18n-6356f32d0c02c8f90cb59a77e16e8fe2": "Usuario desbloqueado",
"s18n-2327a01afbee025fb5913357c9d6b1b3": "Usuario bloqueado",
"s18n-76a0f6752a45d8af6343ef3e2b6f522a": "Único comentario",
"s18n-c426859e50a35617d863cdad2b9c84aa": "Página de comentarios",
"s18n-a64d776275f13a51790bb460774b9129": "Comentarios de usuarios",
"s18n-9bc65c2abec141778ffaa729489f3e87": "Usuarios",
"s18n-ccd1066343c95877b75b79d47c36bebe": "Configuración",
"s18n-5b49260517622682a058b69f996d06eb": "Gracias por comentar!",
"s18n-74196a783a6f1707a43cc8117f0d9c83": "Gracias por tu comentario. Revisa tu correo electrónico para confirmar la subscripción!",
"s18n-a939eb542e34cd502b3f7352b2e0f715": "Gracias por valorar este comentario!",
"s18n-d2a9677817ee08ed05bf9fd868669756": "Opss!: Algo salió mal, actualiza e intentalo de nuevo!",
"s18n-05b85714aa8f1b364f930e2539059b5e": "Opss! Nombre de usuario inválido o demasiado largo (hasta 42 caracteres)!",
"s18n-321e8b481f0ccd62df535256e8e6d2c6": "Opss!: Dirección de correo inválida!",
"s18n-fcd0c3a087c5123ffdecc20fe9015870": "Opss!: No hay texto en tu comentario!",
"s18n-2afa2d90ce3343fbe188b9f49ad5797d": "Opss!: Falta el título!",
"s18n-bee1741efe0c735d2c7180771586faf0": "Opss!: No aceptaste los términos!",
"s18n-92fe96d6ccee901f94fad0000369a9b7": "Opss!: No eres bien recibido, por espamer!",
"s18n-9b264fc6137096f8a40acde68f6ae562": "Opss!: Ya votaste este comentario!",
"s18n-ca62db4704290ef1a7e65df3ffc7983b": "Conforme con almacenar mis datos (incluida dirección IP)!",
"s18n-01c611362e8b046f32650b85ce161559": "Opss!: Algo salió mal",
"s18n-c4f94c6995b0376f28276e432bed75fa": "Token perdido!",
"s18n-a573e7b86e41522c7a291846aa109104": "Token inválido!",
"s18n-3e8909518ce4728685aa09cdde3caa22": "No tienes suficientes permisos!",
"s18n-b60a6a8a529a9f0497134205bab15e77": "No tienes suficientes permisos!",
"s18n-56abae3e615ae5b5609c32852b777d46": "Acción desconocida o inválida!",
"s18n-be5136b4f2b33c80e3afd377ee993acb": "Opss!: Algo salió mal",
"s18n-46d4c97e91319867654f7cc80c439ba4": "Usuario sin ID (identificador)!",
"s18n-af1ba1dd4eab5562f78c65bc89a0a7e9": "Acción exitosa!",
"s18n-a5193444ee82c18bac726b35a1704d03": "Actualizado con éxito!",
"s18n-717c8267d40664ccf7ef25a26ff9cde6": "Copia de seguridad realizada con éxito!",
"s18n-a1e2b7401861cee01c172878f104bd8c": "Comentarios no disponibles",
"s18n-15802277ea1cdfcbacd6308fa0c7c30f": "Desactivación Snicker Plugin",
"s18n-877d58f21c87442efa4081112a6cb07a": "Desactivar <b>Snicker<\/b> Plugin, podría borrar todos los comentarios!",
"s18n-a4983c86683f8d8598c0513339550dc0": "Deseas realizar una copia de seguridad antes?",
"s18n-4cce9e52118cc659be5070b2f08cdd91": "La copia de seguridad será guardad en %s!",
"s18n-5f87fd2e0fa992d37c814bb4ca299646": "Si, crear copia de seguridad",
"s18n-3d414feb412a1f4cd9324f6411c76329": "No, solo desactiva",
"s18n-10aec35353f9c4096a71c38654c3d402": "Cancelar",
"s18n-bc6d6a26d44b6f39a0e7b6c7787f3295": "Se han desactivado los comentarios para esta página!",
"s18n-a8e30d73eddea9866cf99ecd6e8467b5": "No hay comentarios. Se el primero!",
"s18n-b3afbadaa2f1c79f8b3999be7fd9719f": "Captcha incorrecto!",
"s18n-fb74aafe8bf1fd4d8f7a6e1ff73028f7": "UID de comentario inexistente o incorrecta!",
"s18n-25dea7cb70f98250b388f6ab0ddf20cb": "Estado de comentario inexistente o incorrecto!",
"s18n-2cd68855bdc54ff5e3c191a6333ff75d": "No se ha podido actualizar el nuevo estado de comentario!",
"s18n-c0937505b7afa81a053077bc7ae369a5": "Estado de comentario actualizado con éxito!",
"s18n-6b2c8084c67f24bb73d95031bb570ef7": "No se ha podido borrar el comentario!",
"s18n-376388311a80dbde63fde7f6c72081e0": "Comentario borrado con éxito!",
"s18n-83bbb9e8745cc95e730fe7b8de9345f1": "Identificado como %s (%s)",
"s18n-91fb98e1ac4cf76b7a5b8bae09051e2a": "Nombre de usuario",
"s18n-31f6da7a30e7acf1f82451bfd1a7f8fa": "Dirección de correo",
"s18n-f794080a5a29e35233c82df85f1207eb": "Comentario...",
"s18n-a363b8d13575101a0226e8d0d054f2e7": "Respuestas",
"s18n-f10db888c5e63b343000cffc038e0a46": "Escribe",
"s18n-3cc5bcf15d6b8faed118e2ce72d19a1e": "De acuerdo con %s!",
"s18n-2af0aab477f402e0f4ad7a27e6c9f952": "Comentarios anteriores",
"s18n-8538431db22040e2147b363f86a2e2f0": "Comentarios siguientes",
"s18n-ae0dbd5cc42a6db191db5e0083bcb307": "Este comentario aun no ha sido moderado!",
"s18n-48df9c3f3cca3fb2b8bcf811633bee06": "Escrito por %s",
"s18n-81fdc9813cebe0553c55e78dc2b6029f": "sobre %s",
"s18n-be1ab1632e4285edc3733b142935c60b": "Me gusta",
"s18n-bc8b79025e4595298669fd21da814941": "No me gusta",
"s18n-e84afaab83ecb301b3d97ce4174d2773": "Responder"
}

201
languages/fa_IR.json

@ -0,0 +1,201 @@
{
"plugin-data": {
"name": "ارسال دیدگاه Snicker",
"description": "یک سیستم بومی ارسال دیدگاه فلت-فایل آجاکسی برای بلودیت، شامل اشتراک به دیدگاه‌ها و کاملاً سازگار با GDPR!"
},
"s18n-a5d491060952aa8ad5fdee071be752de": "ارسال دیدگاه",
"s18n-de95b43bceeb4b998aed4aed5cef1ae7": "ویرایش",
"s18n-f984023ed3d6df2326c9d59838c29792": "بروزرسانی دیدگاه",
"s18n-86448a506dd93303a72140b9124ee321": "حذف دیدگاه",
"s18n-db10a8eb963bc0e5f4483ac9b5dc554c": "عنوان دیدگاه",
"s18n-335630425567dbe91768a3beffdec752": "متن دیدگاه",
"s18n-bd7e63f881c7f787a9a0dce20b7f9e5b": "ابر تنظیمات",
"s18n-1acfe725df7bd12195751f0737c4d375": "کاربر ثبت شده",
"s18n-e988189db402fab453f72052629c02cd": "نام کاربری دیدگاه",
"s18n-db84c6236ca6a01b9189504d78c012a5": "ایمیل دیدگاه",
"s18n-7c6c2e5d48ab37a007cbf70d3ea25fa4": "در انتظار",
"s18n-787d5f05953ec39b108869dfdd7733e6": "تائید شده",
"s18n-c7537d6d48ecf261749c09a9f284bd45": "رد شده",
"s18n-e09f6a7593f8ae3994ea57e1117f67ec": "هرزنامه",
"s18n-7a5115c2c1eb662308decbec83593494": "مشاهده صفحه",
"s18n-0572a05cd6d6360391993a611099542f": "عنوان و یا گزیده دیدگاه",
"s18n-428f14500191b5d53675da4a96bc8bba": "جستجوی دیدگاه‌ها",
"s18n-e0be71bccdeceb713fba3c222f79a3c5": "دیدگاهی در دسترس نیست",
"s18n-06d4cd63bde972fc66a0aed41d2f5c51": "دیدگاه",
"s18n-02bd92faa38aaa6cc0ea75e59937a1ef": "نویسنده",
"s18n-ebb67a4271abe715344471b0f16321f6": "عملیات",
"s18n-58566b9a9b2733b0ceacb2186672b5d1": "نمایش تمام پاسخ ها",
"s18n-5f44c0081bd862a77ba8b24e923cadf1": "پاسخ به",
"s18n-eb399bcaca686f8609137153307eecf1": "تغییر",
"s18n-2736f4347985da50dc023444c193bfea": "ویرایش دیدگاه",
"s18n-a107bf4b12e36a07161a26d95b03bc81": "تائید دیدگاه",
"s18n-da937abd19cd9e1430470b8a471a41d4": "رد دیدگاه",
"s18n-32cb4199893d9948cc0853eef244f1fc": "هرزنامه است",
"s18n-a6494adfb72d12d3a4da66855c284ec6": "بازگشت به در انتظار",
"s18n-1bda80f2be4d3658e0baa43fbe7ae8c1": "مشاهده",
"s18n-c9ae5a4214e87ad6fbed44a267471eee": "ذخیره تنظیمات",
"s18n-e124d357c3c832434a8676a5e18db842": "تنظیمات عمومی",
"s18n-b62a9dcd666f3ff44197cf21ac66507d": "مدیریت دیدگاه‌ها",
"s18n-7f0217cdcdd58ba86aae84d9d3d79f81": "مدیریت",
"s18n-1a1dc91c907325c69271ddf0c944bc72": "تائید",
"s18n-df6963dc912cde9baeef10343167ba01": "هر دیدگاه",
"s18n-39ba4181e212acf183ac965c9b37da89": "درصورت ورود به سیستم",
"s18n-0dac4426a017f0a0370db32776bc30bb": "در صورت مدیر یا نویسنده بودن",
"s18n-c70aa8b8fd6f2652eda2d5366faf1de5": "در صورت داشتن دیدگاه از قبل",
"s18n-8b26cab9d444760b4bcc65dc4d8634f8": "پذیرفتن دیدگاه",
"s18n-67614909bf9de326de71946036de39f1": "... در نوشته های عمومی",
"s18n-a7c384c1b60785c40bc3a4e4dfc5a108": "... در نوشته های چسبنده",
"s18n-9907b78f8745810599dbf6fd29a01364": "... در نوشته های استاتیک",
"s18n-c89cbecce48d04cd76c0b95c8128ad97": "فعال (اختیاری)",
"s18n-e9d51286fdd0ff058650392fc8b6ae30": "فعال (الزامی)",
"s18n-0aaa87422396fdd678498793b6d5250e": "غیرفعال",
"s18n-8da2c8185edfeb1765526f8e2e4f388d": "محدودیت دیدگاه",
"s18n-77d6d185c93549dab24f29ff2e3b25a8": "برای غیرفعال کردن هر محدودیتی از '0' استفاده کنید",
"s18n-1ef52691308c8add87723a4103a561c2": "عمق دیدگاه",
"s18n-d102731a5fedff24f30e24e883ef4636": "Markup دیدگاه",
"s18n-6640979a191e66655c26c59d404bf955": "پذیرفتن HTML ابتدایی",
"s18n-915009e874f8bed1845060012f826fcd": "پذیرفتن Markdown",
"s18n-a9a36cb3d8f4f7297ebca99a322d6342": "امتیاز به دیدگاه",
"s18n-2123546d1ff8b0cb035df0c0b0d06825": "ذخیره امتیازهای مهمانان در",
"s18n-dead693ab29895d302fca0e6baad6182": "ذخیره سازی کوکی",
"s18n-02b68043bdcae159e83199d64a5abd7d": "ذخیره سازی نشست",
"s18n-40bd8791e523e91219886c35622163fc": "ذخیره سازی پایگاه داده",
"s18n-8717cfca734e8987971f63b20eeb8024": "توضیحات؟",
"s18n-5a8cbcf57f5b59f0d4b8ded97d018399": "<b>ذخیره سازی کوکی</b> بر روی کامپیوتر کاربر قرار دارد. بنابراین شما کنترل کامل نداشته و نیاز به مجوز مناسب از طرف کاربر را دارید.",
"s18n-a09dbdf907873e66d9d644cca71970d5": "<b>ذخیره سازی نشست</b> فقط به صورت موقت در سرور ذخیره می‌شود، زمانی که کاربر مرورگرش را ببندد این اطلاعات هم پاک می‌شود. بنابراین شما به هیچ مجوزی از طرف کاربر نیازی ندارید.",
"s18n-71892ebe01ba92d1f163dc37d818b5ff": "<b>ذخیره سازی پایگاه داده</b> تولید و در یک مقدار ناشناس ذخیره می‌شود، ولی قابل تعیین توسط کاربر است، همچنین نیاز به مجوزهای مناسب از طرف کاربر می‌باشد.",
"s18n-7c35a0bcb0b0678f0829036eead5ddca": "<b> لطفاً توجه کنید: </b>شما مسئولیت اخذ مجوزهای مناسب هستید، Snicker فقط مجوزهای ارسال (و ذخیره سازی) اطلاعات از طریق فرم دیدگاه را بکار می‌گیرد!",
"s18n-82e5228061f185ee185bd9f3ecba4ee7": "پذیرفتن به %s دیدگاه",
"s18n-61b58693e0eceeb27ce0cc3b25b3bf31": "تنظیمات محیط کاربری",
"s18n-b3c1c2c231275878abe58a55966fa9e0": "فیلتر صفحه",
"s18n-9cb1eef8966f93282524929f65c8b9ec": "غیرفعال کردن فیلتر صفحه",
"s18n-e89fd56cefec9baabcbe0db3e5a36962": "استفاده از 'pageBegin'",
"s18n-3d08b5dcc1e5c3c7e7e2eaf2d0d6a12d": "استفاده از 'pageEnd'",
"s18n-3771d05b6af4ebb0a303266c47809548": "استفاده از 'siteBodyBegin'",
"s18n-42438edc41ab83312486009e3122e92b": "استفاده از 'siteBodyEnd'",
"s18n-948da5199de32c7601a20b6107c31d4d": "کدامنیتی دیدگاه",
"s18n-47e5c42fb9bdca3636a5d866a6794101": "غیرفعال کردن دیدگاه",
"s18n-eedf6d23d18212016e22428658e17794": "استفاده از کدامنیتی محلی (مولف Gregway)",
"s18n-09cddbc3627ea46a8dce692d64273b61": "استفاده از reCaptcha گوگل (هنوز در دسترس نیست)",
"s18n-838a13e4fece7c272b960da3fb99f94d": "قالب دیدگاه",
"s18n-6f95370a28520696b2a0ad34efc54d2d": "ترتیب دیدگاه",
"s18n-4a8dc1710396b21e7b1da8112c07c4ad": "جدیدترین دیدگاه در ابتدا",
"s18n-2dab3b12d0b0642c3964b37d675ff24b": "جدیدترین دیدگاه در انتها",
"s18n-230c71c29590608034b4a590a67ace31": "موقعیت فرم دیدگاه",
"s18n-37d988444dec2001c488806fc8401e25": "نمایش فرم دیدگاه بالای دیدگاه‌ها",
"s18n-16ac6c11951d825826f77a4097a1c2cb": "نمایش فرم دیدگاه پایین دیدگاه‌ها",
"s18n-1e98ec9312b69676d5e3fe3caf8ecde1": "دیدگاه در هر صفحه",
"s18n-0d46f4389ca5f882e24899fe489bf344": "برای نمایش تمام دیدگاه‌ها از '0' استفاده کنید",
"s18n-7b6b84fbd65a4b712a5ba0dccce176d5": "جعبه شرایط استفاده",
"s18n-9853383062a2e308d5aed35fe3da7953": "غیرفعال کردن این کادر",
"s18n-352fd1d7225b5ea02b8ddd9fad0d6e34": "نمایش پیام (متون جایگزین را ببینید)",
"s18n-71860c77c6745379b0d44304d66b6a13": "صفحه",
"s18n-5dcb84333ae70a5bed60bf70d34dcd2b": "متن پیش‌فرض GDPR نمایش داده شود و یا متن صفحه استاتیک 'قوانین استفاده' خود را انتخاب کنید!",
"s18n-2e3d9327c371afb7489f9b9278198622": "اسکریپت AJAX",
"s18n-c60cab6330745b41cbee05603eec6691": "استفاده از AJAX",
"s18n-69b11f64af515ef979fbf28c5e06f370": "از AJAX استفاده نشود",
"s18n-9bbec7b57565f06d73522669d3b836dc": "اسکریپت AJAX درخواست های (دیدگاه، like،dislike) را مستقیماً بکار می‌برد بدون اینکه صفحه مجدداً بارگذاری شود!",
"s18n-7c74c0d2d1c28d1568298c89742ce126": "آواتار دیدگاه",
"s18n-aeab7c630dae161d8f6e2898dd83b471": "استفاده از Gravatar",
"s18n-882e3436da897c055cc3f8bd2598b71a": "استفاده از Identicon",
"s18n-d92d61ad0c0065170a37a1805ad1bc9e": "استفاده از Mystery Men",
"s18n-b4a51a35344f9a8fa1139cfd968ab308": "ترجیح استفاده از تصویر پروفایل کاربران وارد شده به سیستم",
"s18n-b50a4a96c25d745d73114af5a4b03145": "دیدگاه Gravatar",
"s18n-9ffb941a398ddee8e054eef3292c546e": "نمایش Mystery Person",
"s18n-a7dd12b1dab17d25467b0b0a4c8d4a92": "نمایش",
"s18n-0d2fc085ee57276417cf380027060760": "تصویر پیش فرض Gravatar، در صورتی که کاربر Gravatar ندارد!",
"s18n-58e2aacf5792087168cbc62578584ecd": "تنظیمات اشتراک",
"s18n-8290ca86b8980a14bd46f34017e03f93": "سیستم اشتراک هنوز در دسترس نیست!",
"s18n-0bd7ff1b4ac56a9616796bdc05609de2": "اشتراک ایمیلی",
"s18n-208f156d4a803025c284bb595a7576b4": "فعال",
"s18n-dc985a7c2144c6447674e674aee08441": "آدرس ایمیل 'From'",
"s18n-f6db5b4db3f9c1ba0ffc091abc561802": "آدرس ایمیل 'ReplyTo'",
"s18n-8d868315d258783a95336d1a6ce27e1e": "متن ایمیل (Opt-In)",
"s18n-7721b2a2f2a453cc790e3ac7065e9b65": "استفاده از ایمیل اشتراک پیش فرض",
"s18n-58ff11585a82c73f5117c91c29cb3f63": "متن ایمیل (اطلاع رسانی)",
"s18n-bea9ff19efca028c01617da5dce18171": "استفاده از ایمیل اطلاع رسانی پیش فرض",
"s18n-e0024a8886a178d4f70b8c888b701680": "درمورد ایمیل اطلاع رسانی های سفارشی %s مطالعه کنید!",
"s18n-8bcf6629759bd278a5c6266bd9c054f8": "متون جایگزین",
"s18n-3979b4205954030810a8a87769348094": "پیام تشکر پیش فرض",
"s18n-620b528248b36bf743d1ad33e35022d6": "پیام تشکر به همراه اشتراک",
"s18n-e83f6b01d1c81313f6b388281e13aacf": "پیام تشکر برای امتیازدهی",
"s18n-01b7a6bc6b57783b4fd081085ff3271e": "خطا: خطای ناشناخته، دوباره تلاش کنید",
"s18n-19678b1419eff34dffd41d6778b1aa89": "خطا: نام کاربری معتبر نیست",
"s18n-1255e6794d33b443bcee21279d9caa1a": "خطا: آدرس ایمیل معتبر نیست",
"s18n-ebf545757b92b3a553d83ea3db48beca": "خطا: متن دیدگاه خالی است",
"s18n-f1bc53e2456c425578277adbc7c90f3a": "خطا: عنوان دیدگاه خالی است",
"s18n-53c8fdd7ed497dbacbef5fd5d4f38f3d": "خطا: قوانین پذیرفته نشده",
"s18n-89f8cc478fdfc0a8f36c1b393f39677a": "خطا: به عنوان هرزنامه شناخته شد",
"s18n-59ad5c9fcee1b28a5d004bcf684a5acd": "خطا: قبلا امتیاز دادید",
"s18n-56ef2c600b4af0f9f7b35640525967ca": "قوانین استفاده",
"s18n-08400c2e0f51197fdb3590461b15b2cc": "نام کاربری یا آدرس ایمیل",
"s18n-b79edd2e426f90401c04869346b503c7": "جستجوی کاربران",
"s18n-ee51fa9d5097c84d2fa6c885bf2d5d84": "هیچ کاربری وجود ندارد",
"s18n-14c4b06b824ec593239362517f538b29": "نام کاربرری",
"s18n-0c83f57c786a0b4a39efab23731c7ebc": "ایمیل",
"s18n-e1260894f59eeae98c8440899de4df8d": "بکارگیری",
"s18n-4bc61296b766756f1c7296489633bf32": "حذف (ناشناس)",
"s18n-c0f4afd3614929f1c803f3a01414a6c7": "حذف (به طور کامل)",
"s18n-6356f32d0c02c8f90cb59a77e16e8fe2": "آزاد کردن کاربر",
"s18n-2327a01afbee025fb5913357c9d6b1b3": "مسدود کردن کاربر",
"s18n-76a0f6752a45d8af6343ef3e2b6f522a": "یک دیدگاه",
"s18n-c426859e50a35617d863cdad2b9c84aa": "دیدگاه های نوشته",
"s18n-a64d776275f13a51790bb460774b9129": "دیدگاه های کاربر",
"s18n-9bc65c2abec141778ffaa729489f3e87": "کاربران",
"s18n-ccd1066343c95877b75b79d47c36bebe": "پیکربندی",
"s18n-5b49260517622682a058b69f996d06eb": "متشکرم از ارسال دیدگاه!",
"s18n-74196a783a6f1707a43cc8117f0d9c83": "متشکرم از ارسال دیدگاه! لطفاً اشتراک خود را از طریق لینکی که به آدرس ایمیل شما ارسال شده را تائید کنید",
"s18n-a939eb542e34cd502b3f7352b2e0f715": "متشکرم از اینکه به این دیدگاه امتیاز دادید!",
"s18n-d2a9677817ee08ed05bf9fd868669756": "خطای ناشناخته ای رخ داد، لطفا صفحه را مجددا بارگیری کرده و دوباره تلاش کنید!",
"s18n-05b85714aa8f1b364f930e2539059b5e": "خطایی رخ داد: نام کاربری ارائه شده نامعتبر و یا بسیار طولانی است(فقط 42 کاراکتر باشد)!",
"s18n-321e8b481f0ccd62df535256e8e6d2c6": "خطایی رخ داد: آدرس ایمیل ارائه شده معتبر نیست",
"s18n-fcd0c3a087c5123ffdecc20fe9015870": "خطایی رخ داد: متن دیدگاه خالی است!",
"s18n-2afa2d90ce3343fbe188b9f49ad5797d": "خطایی رخ داد: عنوان دیدگاه خالی است!",
"s18n-bee1741efe0c735d2c7180771586faf0": "خطایی رخ داد: برای ارسال دیدگاه باید قوانین را بپذیرید!",
"s18n-92fe96d6ccee901f94fad0000369a9b7": "خطایی رخ داد: آدرس IP و یا آدرس ایمیل شما به عنوان هرزنامه شناخته شده!",
"s18n-9b264fc6137096f8a40acde68f6ae562": "خطایی رخ داد: شما قبلا به این دیدگاه رای داده اید!",
"s18n-ca62db4704290ef1a7e65df3ffc7983b": "من موافقم که اطلاعات من (شامل آدرس IP ناشناس) من ذخیره شود!",
"s18n-01c611362e8b046f32650b85ce161559": "خطای ناشناخته رخ داد!",
"s18n-c4f94c6995b0376f28276e432bed75fa": "توکن CSRF وجود ندارد!",
"s18n-a573e7b86e41522c7a291846aa109104": "توکن CSRF معتبر نیست!",
"s18n-3e8909518ce4728685aa09cdde3caa22": "برای فراخوانی این اقدام مجوز لازم را ندارید!",
"s18n-b60a6a8a529a9f0497134205bab15e77": "برای اجرای این اقدام مجوز لازم را ندارید!",
"s18n-56abae3e615ae5b5609c32852b777d46": "اقدام انجام شده ناشناخته و یا معتبر نیست!",
"s18n-be5136b4f2b33c80e3afd377ee993acb": "خطای ناشناخته رخ داد!",
"s18n-46d4c97e91319867654f7cc80c439ba4": "شناسه منحصر به فرد کاربری وجود ندارد!",
"s18n-af1ba1dd4eab5562f78c65bc89a0a7e9": "اقدام با موفقیت انجام شد!",
"s18n-a5193444ee82c18bac726b35a1704d03": "تنظیمات با موفقیت به روز شد!",
"s18n-717c8267d40664ccf7ef25a26ff9cde6": "نسخه پشتیبان با موفقیت تهیه شد!",
"s18n-a1e2b7401861cee01c172878f104bd8c": "نپذیرفتن دیدگاه",
"s18n-15802277ea1cdfcbacd6308fa0c7c30f": "غیر فعال کردن پلاگین ارسال دیدگاه Snicker",
"s18n-877d58f21c87442efa4081112a6cb07a": "شما دارید پلاگین <b>Snicker</b> را غیرفعال می‌کنید، که تمام دیدگاه‌های نوشته شده حذف خواهند شد!",
"s18n-a4983c86683f8d8598c0513339550dc0": "آیا تمایل دارید قبل از اینکار از دیدگاهها نسخه پشتیبان تهیه کنید؟",
"s18n-4cce9e52118cc659be5070b2f08cdd91": "نسخه پشتیبان در %s ذخیره می شود!",
"s18n-5f87fd2e0fa992d37c814bb4ca299646": "بله، تهیه پشتیبان",
"s18n-3d414feb412a1f4cd9324f6411c76329": "خیر، غیرفعال کردن",
"s18n-10aec35353f9c4096a71c38654c3d402": "لغو",
"s18n-bc6d6a26d44b6f39a0e7b6c7787f3295": "ارسال دیدگاه در این صفحه غیرفعال است!",
"s18n-a8e30d73eddea9866cf99ecd6e8467b5": "اولین نفری باشید که دیدگاهی ارسال می کند!",
"s18n-b3afbadaa2f1c79f8b3999be7fd9719f": "پاسخ به کدامنیتی انجام نشده و یا اشتباه وارد کردید!",
"s18n-fb74aafe8bf1fd4d8f7a6e1ff73028f7": "شناسه منحصر به فرد دیدگاه وجود نداشته و یا معتبر نیست!",
"s18n-25dea7cb70f98250b388f6ab0ddf20cb": "وضعیت دیدگاه ناشناخته و یا معتبر نیست!",
"s18n-2cd68855bdc54ff5e3c191a6333ff75d": "وضعیت دیدگاه جدید نمی تواند با موفقیت به روز شود!",
"s18n-c0937505b7afa81a053077bc7ae369a5": "وضعیت دیدگاه جدید با موفقیت ذخیره شد!",
"s18n-6b2c8084c67f24bb73d95031bb570ef7": "دیدگاه نمی تواند با موفقیت حذف شود!",
"s18n-376388311a80dbde63fde7f6c72081e0": "دیدگاه با موفقیت حذف شد!",
"s18n-83bbb9e8745cc95e730fe7b8de9345f1": "ورود به نام %s (%s)",
"s18n-91fb98e1ac4cf76b7a5b8bae09051e2a": "نام شما",
"s18n-31f6da7a30e7acf1f82451bfd1a7f8fa": "آدرس ایمیل شما",
"s18n-f794080a5a29e35233c82df85f1207eb": "متن دیدگاه...",
"s18n-a363b8d13575101a0226e8d0d054f2e7": "کدامنیتی",
"s18n-f10db888c5e63b343000cffc038e0a46": "نوشته:",
"s18n-3cc5bcf15d6b8faed118e2ce72d19a1e": "با %s موافقت می کنم!",
"s18n-2af0aab477f402e0f4ad7a27e6c9f952": "دیدگاه‌های قبلی",
"s18n-8538431db22040e2147b363f86a2e2f0": "دیدگاههای بعدی",
"s18n-ae0dbd5cc42a6db191db5e0083bcb307": "این دیدگاه هنوز تائید نشده!",
"s18n-48df9c3f3cca3fb2b8bcf811633bee06": "نوشته %s",
"s18n-81fdc9813cebe0553c55e78dc2b6029f": "در مورخه %s",
"s18n-be1ab1632e4285edc3733b142935c60b": "Like",
"s18n-bc8b79025e4595298669fd21da814941": "Dislike",
"s18n-e84afaab83ecb301b3d97ce4174d2773": "پاسخ"
}

204
languages/nl_NL.json

@ -0,0 +1,204 @@
{
"plugin-data": {
"name": "Snicker",
"description": "Een native, AJAX-enabled FlatFile reactiesysteem voor bludit volgens de GDPR-normen en met ondersteuning voor het abonneren op reacties."
},
"s18n-a5d491060952aa8ad5fdee071be752de": "Reacties",
"s18n-de95b43bceeb4b998aed4aed5cef1ae7": "Bewerken",
"s18n-f984023ed3d6df2326c9d59838c29792": "Reactie bijwerken",
"s18n-86448a506dd93303a72140b9124ee321": "Reactie verwijderen",
"s18n-db10a8eb963bc0e5f4483ac9b5dc554c": "Titel",
"s18n-335630425567dbe91768a3beffdec752": "Bericht",
"s18n-bd7e63f881c7f787a9a0dce20b7f9e5b": "Metadata",
"s18n-1acfe725df7bd12195751f0737c4d375": "Geregistreerde gebruiker",
"s18n-e988189db402fab453f72052629c02cd": "Gebruikersnaam",
"s18n-db84c6236ca6a01b9189504d78c012a5": "E-mail",
"s18n-7c6c2e5d48ab37a007cbf70d3ea25fa4": "Wordt beoordeeld",
"s18n-787d5f05953ec39b108869dfdd7733e6": "Goedgekeurd",
"s18n-c7537d6d48ecf261749c09a9f284bd45": "Afgekeurd",
"s18n-e09f6a7593f8ae3994ea57e1117f67ec": "Spam",
"s18n-7a5115c2c1eb662308decbec83593494": "Pagina bekijken",
"s18n-0572a05cd6d6360391993a611099542f": "Titel of samenvatting reactie",
"s18n-428f14500191b5d53675da4a96bc8bba": "Zoeken",
"s18n-e0be71bccdeceb713fba3c222f79a3c5": "Geen reacties beschikbaar",
"s18n-06d4cd63bde972fc66a0aed41d2f5c51": "Reactie",
"s18n-02bd92faa38aaa6cc0ea75e59937a1ef": "Auteur",
"s18n-ebb67a4271abe715344471b0f16321f6": "Acties",
"s18n-58566b9a9b2733b0ceacb2186672b5d1": "Alle antwoorden tonen",
"s18n-5f44c0081bd862a77ba8b24e923cadf1": "Antwoorden op",
"s18n-eb399bcaca686f8609137153307eecf1": "Wijzigen",
"s18n-2736f4347985da50dc023444c193bfea": "Reactie bewerken",
"s18n-a107bf4b12e36a07161a26d95b03bc81": "Reactie goedkeuren",
"s18n-da937abd19cd9e1430470b8a471a41d4": "Reactie afkeuren",
"s18n-32cb4199893d9948cc0853eef244f1fc": "Markeren als Spam",
"s18n-a6494adfb72d12d3a4da66855c284ec6": "Terug naar reacties in afwachting",
"s18n-1bda80f2be4d3658e0baa43fbe7ae8c1": "Bekijken",
"s18n-c9ae5a4214e87ad6fbed44a267471eee": "Instellingen opslaan",
"s18n-e124d357c3c832434a8676a5e18db842": "Algemene instellingen",
"s18n-b62a9dcd666f3ff44197cf21ac66507d": "Reactiebeheer",
"s18n-7f0217cdcdd58ba86aae84d9d3d79f81": "Modereren",
"s18n-1a1dc91c907325c69271ddf0c944bc72": "Goedkeuren",
"s18n-df6963dc912cde9baeef10343167ba01": "iedere reactie",
"s18n-39ba4181e212acf183ac965c9b37da89": "Tenzij de gebruiker is ingelogd",
"s18n-0dac4426a017f0a0370db32776bc30bb": "Tenzij de gebruiker beheerder of auteur van de pagina is",
"s18n-c70aa8b8fd6f2652eda2d5366faf1de5": "Tenzij er reeds een eerdere reactie van de gebruiker is goedgekeurd",
"s18n-8b26cab9d444760b4bcc65dc4d8634f8": "Reacties toestaan",
"s18n-67614909bf9de326de71946036de39f1": "... op gepubliceerde pagina's",
"s18n-a7c384c1b60785c40bc3a4e4dfc5a108": "... op vastgezette pagina's",
"s18n-9907b78f8745810599dbf6fd29a01364": "... op statische pagina's",
"s18n-c89cbecce48d04cd76c0b95c8128ad97": "Inschakelen (optioneel)",
"s18n-e9d51286fdd0ff058650392fc8b6ae30": "Inschakelen (vereist)",
"s18n-0aaa87422396fdd678498793b6d5250e": "Uitschakelen",
"s18n-8da2c8185edfeb1765526f8e2e4f388d": "Limiet voor reacties",
"s18n-77d6d185c93549dab24f29ff2e3b25a8": "Voer '0' in om geen limiet toe te passen.",
"s18n-1ef52691308c8add87723a4103a561c2": "Diepte reacties",
"s18n-d102731a5fedff24f30e24e883ef4636": "Formaat voor reacties",
"s18n-6640979a191e66655c26c59d404bf955": "HTML beperkt toestaan",
"s18n-915009e874f8bed1845060012f826fcd": "Markdown toestaan",
"s18n-a9a36cb3d8f4f7297ebca99a322d6342": "Stemmen op reacties",
"s18n-2123546d1ff8b0cb035df0c0b0d06825": "Stemmen door gasten opslaan in",
"s18n-dead693ab29895d302fca0e6baad6182": "Cookies",
"s18n-02b68043bdcae159e83199d64a5abd7d": "Sessie-opslag",
"s18n-40bd8791e523e91219886c35622163fc": "Database",
"s18n-8717cfca734e8987971f63b20eeb8024": "Wat betekent dit?",
"s18n-5a8cbcf57f5b59f0d4b8ded97d018399": "<b>Cookies<\/b> worden opgeslagen op de computer van de gebruiker; u heeft geen volledige controle EN de gebruiker moet cookies toestaan.",
"s18n-a09dbdf907873e66d9d644cca71970d5": "<b>Sessie-opslag<\/b> wordt slechts tijdelijk op de server bewaard en wordt verwijderd wanneer de gebruiker de browser afsluit. Daarom is er géén toestemming van de gebruiker benodigd.",
"s18n-71892ebe01ba92d1f163dc37d818b5ff": "Opslaan in de <b>Database<\/b> genereert een gepseudonimiseerde waarde voor iedere gebruiker die stemt en slaat die op in de database. Daarom is er toestemming van de gebruiker benodigd.",
"s18n-7c35a0bcb0b0678f0829036eead5ddca": "<b>Waarschuwing:<\/b> U bent zelf verantwoordelijk voor het verkrijgen van goedkeuring door gebruikers; Snicker behandelt alleen de machtigingen voor de gegevens die verstuurd en/of opgeslagen worden via het reactieformulier!",
"s18n-82e5228061f185ee185bd9f3ecba4ee7": "%s toestaan voor reacties",
"s18n-61b58693e0eceeb27ce0cc3b25b3bf31": "Instellingen front-end",
"s18n-b3c1c2c231275878abe58a55966fa9e0": "Paginafilter",
"s18n-9cb1eef8966f93282524929f65c8b9ec": "Paginafilter uitschakelen",
"s18n-e89fd56cefec9baabcbe0db3e5a36962": "'pageBegin' gebruiken",
"s18n-3d08b5dcc1e5c3c7e7e2eaf2d0d6a12d": "'pageEnd' gebruiken",
"s18n-3771d05b6af4ebb0a303266c47809548": "'siteBodyBegin' gebruiken",
"s18n-42438edc41ab83312486009e3122e92b": "'siteBodyEnd' gebruiken",
"s18n-948da5199de32c7601a20b6107c31d4d": "Captcha",
"s18n-47e5c42fb9bdca3636a5d866a6794101": "Captcha uitschakelen",
"s18n-ab6ef7ef94efc86db78218c6c265243a": "OWASP's PureCaptcha gebruiken",
"s18n-a58adce0085cc1a25fc8076e97c29d70": "Gregway's Captcha gebruiken",
"s18n-9d2f2ec577e7383b88fd481d6c566c5e": "Gregway's Captcha gebruiken (GD library niet beschikbaar!)",
"s18n-09cddbc3627ea46a8dce692d64273b61": "Googles reCaptcha gebruiken (nog niet beschikbaar)",
"s18n-838a13e4fece7c272b960da3fb99f94d": "Sjabloon",
"s18n-6f95370a28520696b2a0ad34efc54d2d": "Volgorde",
"s18n-4a8dc1710396b21e7b1da8112c07c4ad": "Nieuwste reacties eerst",
"s18n-2dab3b12d0b0642c3964b37d675ff24b": "Oudste reacties eerst",
"s18n-230c71c29590608034b4a590a67ace31": "Positie reactieformulier",
"s18n-37d988444dec2001c488806fc8401e25": "Boven reacties tonen",
"s18n-16ac6c11951d825826f77a4097a1c2cb": "Onder reacties tonen",
"s18n-1e98ec9312b69676d5e3fe3caf8ecde1": "Reacties per pagina",
"s18n-0d46f4389ca5f882e24899fe489bf344": "Voer '0' in om alle reacties te tonen.",
"s18n-7b6b84fbd65a4b712a5ba0dccce176d5": "Accepteren gebruiksvoorwaarden",
"s18n-9853383062a2e308d5aed35fe3da7953": "Dit veld uitschakelen",
"s18n-352fd1d7225b5ea02b8ddd9fad0d6e34": "Bericht tonen (zie Meldingen)",
"s18n-71860c77c6745379b0d44304d66b6a13": "Pagina",
"s18n-5dcb84333ae70a5bed60bf70d34dcd2b": "Toon de standaardtekst AVG (GDPR) of kies een eigen statische pagina voor de gebruiksvoorwaarden.",
"s18n-2e3d9327c371afb7489f9b9278198622": "AJAX-script",
"s18n-c60cab6330745b41cbee05603eec6691": "AJAX-script insluiten",
"s18n-69b11f64af515ef979fbf28c5e06f370": "AJAX niet gebruiken",
"s18n-9bbec7b57565f06d73522669d3b836dc": "The AJAX Script hands over the request (reactie, like, dislike) directly without reloading the page!",
"s18n-7c74c0d2d1c28d1568298c89742ce126": "Avatar",
"s18n-aeab7c630dae161d8f6e2898dd83b471": "Gravatar gebruiken",
"s18n-882e3436da897c055cc3f8bd2598b71a": "Identicon gebruiken",
"s18n-d92d61ad0c0065170a37a1805ad1bc9e": "Mystery Men gebruiken",
"s18n-b4a51a35344f9a8fa1139cfd968ab308": "Profielfoto's gebruiken voor ingelogde gebruikers",
"s18n-b50a4a96c25d745d73114af5a4b03145": "Gravatar",
"s18n-9ffb941a398ddee8e054eef3292c546e": "Mystery Person tonen",
"s18n-a7dd12b1dab17d25467b0b0a4c8d4a92": "Tonen",
"s18n-0d2fc085ee57276417cf380027060760": "Standaardafbeelding van Gravatar als de gebruiker geen eigen Gravatar heeft.",
"s18n-58e2aacf5792087168cbc62578584ecd": "Abonneren",
"s18n-8290ca86b8980a14bd46f34017e03f93": "De functie Abonneren is nog niet beschikbaar.",
"s18n-0bd7ff1b4ac56a9616796bdc05609de2": "Abonneren met e-mail",
"s18n-208f156d4a803025c284bb595a7576b4": "Inschakelen",
"s18n-dc985a7c2144c6447674e674aee08441": "E-mailadres afzender ('From')",
"s18n-f6db5b4db3f9c1ba0ffc091abc561802": "E-mailadres beantwoorden ('ReplyTo')",
"s18n-8d868315d258783a95336d1a6ce27e1e": "Inhoud e-mail bij abonneren",
"s18n-7721b2a2f2a453cc790e3ac7065e9b65": "Standaardinhoud voor e-mail bij abonneren gebruiken",
"s18n-58ff11585a82c73f5117c91c29cb3f63": "Inhoud e-mail bij notificatie",
"s18n-bea9ff19efca028c01617da5dce18171": "Standaardinhoud voor e-mail bij notificatie gebruiken",
"s18n-e0024a8886a178d4f70b8c888b701680": "Lees meer over eigen inhoud voor e-mail bij notificaties: %s.",
"s18n-8bcf6629759bd278a5c6266bd9c054f8": "Meldingen",
"s18n-3979b4205954030810a8a87769348094": "Standaardbericht voor bevestiging",
"s18n-620b528248b36bf743d1ad33e35022d6": "Bevestiging na abonneren",
"s18n-e83f6b01d1c81313f6b388281e13aacf": "Bevestiging na stemmen",
"s18n-01b7a6bc6b57783b4fd081085ff3271e": "Fout: onbekende fout, probeer het nogmaals",
"s18n-19678b1419eff34dffd41d6778b1aa89": "Fout: ongeldige gebruikersnaam",
"s18n-1255e6794d33b443bcee21279d9caa1a": "Fout: ongeldig e-mailadres",
"s18n-ebf545757b92b3a553d83ea3db48beca": "Fout: geen bericht ingevoerd",
"s18n-f1bc53e2456c425578277adbc7c90f3a": "Fout: geen titel ingevoerd",
"s18n-53c8fdd7ed497dbacbef5fd5d4f38f3d": "Fout: gebruiksvoorwaarden niet geaccepteerd",
"s18n-89f8cc478fdfc0a8f36c1b393f39677a": "Fout: gemarkeerd als SPAM",
"s18n-59ad5c9fcee1b28a5d004bcf684a5acd": "Fout: u heeft reeds gestemd",
"s18n-56ef2c600b4af0f9f7b35640525967ca": "Gebruiksvoorwaarden",
"s18n-08400c2e0f51197fdb3590461b15b2cc": "Gebruikersnaam of e-mailadres",
"s18n-b79edd2e426f90401c04869346b503c7": "Gebruikers zoeken",
"s18n-ee51fa9d5097c84d2fa6c885bf2d5d84": "Geen gebruikers gevonden",
"s18n-14c4b06b824ec593239362517f538b29": "Gebruikersnaam",
"s18n-0c83f57c786a0b4a39efab23731c7ebc": "E-mail",
"s18n-e1260894f59eeae98c8440899de4df8d": "Afhandelen",
"s18n-4bc61296b766756f1c7296489633bf32": "Verwijderen (pseudonimiseren)",
"s18n-c0f4afd3614929f1c803f3a01414a6c7": "Verwijderen (volledig)",
"s18n-6356f32d0c02c8f90cb59a77e16e8fe2": "Gebruiker deblokkeren",
"s18n-2327a01afbee025fb5913357c9d6b1b3": "Gebruiker blokkeren",
"s18n-76a0f6752a45d8af6343ef3e2b6f522a": "Enkele reactie",
"s18n-c426859e50a35617d863cdad2b9c84aa": "Reacties op pagina",
"s18n-a64d776275f13a51790bb460774b9129": "Reacties van gebruiker",
"s18n-9bc65c2abec141778ffaa729489f3e87": "Gebruikers",
"s18n-ccd1066343c95877b75b79d47c36bebe": "Configuratie",
"s18n-5b49260517622682a058b69f996d06eb": "Bedankt voor uw reactie!",
"s18n-74196a783a6f1707a43cc8117f0d9c83": "Bedankt voor uw reactie! Bevestig het abonneren op verdere reacties via de link in het bericht dat u per e-mail ontvangt.",
"s18n-a939eb542e34cd502b3f7352b2e0f715": "Bedankt voor het stemmen op deze reactie!",
"s18n-d2a9677817ee08ed05bf9fd868669756": "Onbekende fout opgetreden, herlaad de pagina probeer het nogmaals.",
"s18n-05b85714aa8f1b364f930e2539059b5e": "Fout opgetreden: de gebruikersnaam is ongeldig of langer dan 42 tekens.",
"s18n-321e8b481f0ccd62df535256e8e6d2c6": "Fout opgetreden: het e-mailadres is ongeldig.",
"s18n-fcd0c3a087c5123ffdecc20fe9015870": "Fout opgetreden: er is geen bericht ingevoerd.",
"s18n-2afa2d90ce3343fbe188b9f49ad5797d": "Fout opgetreden: er is geen titel ingevoerd.",
"s18n-bee1741efe0c735d2c7180771586faf0": "Fout opgetreden: de gebruiksvoorwaarden zijn niet geaccepteerd.",
"s18n-92fe96d6ccee901f94fad0000369a9b7": "Fout opgetreden: uw IP-adres of e-mailadres is gemarkeerd als SPAM.",
"s18n-9b264fc6137096f8a40acde68f6ae562": "Fout opgetreden: u heeft reeds gestemd op deze reactie.",
"s18n-ca62db4704290ef1a7e65df3ffc7983b": "Mijn gegevens (inclusief mijn gepseudonimiseerd IP-adres) mogen worden opgeslagen.",
"s18n-01c611362e8b046f32650b85ce161559": "Ongeldige fout opgetreden.",
"s18n-c4f94c6995b0376f28276e432bed75fa": "Het CSRF-token ontbreekt.",
"s18n-a573e7b86e41522c7a291846aa109104": "Het CSRF-token is ongeldig.",
"s18n-3e8909518ce4728685aa09cdde3caa22": "U heeft geen machtiging voor het aanroepen van deze functie.",
"s18n-b60a6a8a529a9f0497134205bab15e77": "U heeft geen machtiging voor het uitvoeren van deze actie.",
"s18n-3fee90e2f59aeb29b74c1c21648ba712": "De Captcha is succesvol gegenereerd.",
"s18n-56abae3e615ae5b5609c32852b777d46": "De actie is onbekend of ongeldig.",
"s18n-be5136b4f2b33c80e3afd377ee993acb": "Er is een onbekende fout opgetreden.",
"s18n-46d4c97e91319867654f7cc80c439ba4": "Geen uniek gebruikers-ID gevonden.",
"s18n-af1ba1dd4eab5562f78c65bc89a0a7e9": "Actie is succesvol afgerond.",
"s18n-a5193444ee82c18bac726b35a1704d03": "Instellingen zijn opgeslagen.",
"s18n-717c8267d40664ccf7ef25a26ff9cde6": "Backup is voltooid.",
"s18n-a1e2b7401861cee01c172878f104bd8c": "Reacties niet toestaan",
"s18n-15802277ea1cdfcbacd6308fa0c7c30f": "Snicker-plugin uitschakelen",
"s18n-877d58f21c87442efa4081112a6cb07a": "Het uitschakelen van de <b>Snicker<\/b>-plugin zal alle reacties verwijderen!",
"s18n-a4983c86683f8d8598c0513339550dc0": "Eerst een backup maken van de reacties?",
"s18n-4cce9e52118cc659be5070b2f08cdd91": "De backup zal worden opgeslagen in %s.",
"s18n-5f87fd2e0fa992d37c814bb4ca299646": "Ja, backup maken",
"s18n-3d414feb412a1f4cd9324f6411c76329": "Nee, alleen uitschakelen",
"s18n-10aec35353f9c4096a71c38654c3d402": "Annuleren",
"s18n-bc6d6a26d44b6f39a0e7b6c7787f3295": "Reacties zijn voor deze pagina uitgeschakeld.",
"s18n-a8e30d73eddea9866cf99ecd6e8467b5": "Wees de eerste met een reactie hier!",
"s18n-b3afbadaa2f1c79f8b3999be7fd9719f": "Het antwoord op de Captcha is onjuist.",
"s18n-fb74aafe8bf1fd4d8f7a6e1ff73028f7": "UID voor de reactie bestaat niet of is ongeldig.",
"s18n-25dea7cb70f98250b388f6ab0ddf20cb": "Status van de reactie is onbekend of ongeldig.",
"s18n-2cd68855bdc54ff5e3c191a6333ff75d": "De nieuwe status van de reactie kon niet worden opgeslagen.",
"s18n-c0937505b7afa81a053077bc7ae369a5": "De nieuwe status van de reactie is opgeslagen.",
"s18n-6b2c8084c67f24bb73d95031bb570ef7": "De reactie kon niet worden verwijderd.",
"s18n-376388311a80dbde63fde7f6c72081e0": "De reactie is verwijderd.",
"s18n-83bbb9e8745cc95e730fe7b8de9345f1": "Ingelogd als %s (%s)",
"s18n-91fb98e1ac4cf76b7a5b8bae09051e2a": "Gebruikersnaam",
"s18n-31f6da7a30e7acf1f82451bfd1a7f8fa": "E-mailadres",
"s18n-f794080a5a29e35233c82df85f1207eb": "Reactie...",
"s18n-a363b8d13575101a0226e8d0d054f2e7": "Beantwoorden",
"s18n-f10db888c5e63b343000cffc038e0a46": "schreef",
"s18n-3cc5bcf15d6b8faed118e2ce72d19a1e": "Ik ga akkoord met de %s!",
"s18n-2af0aab477f402e0f4ad7a27e6c9f952": "Vorige",
"s18n-8538431db22040e2147b363f86a2e2f0": "Volgende",
"s18n-ae0dbd5cc42a6db191db5e0083bcb307": "Deze reactie moet nog worden goedgekeurd.",
"s18n-48df9c3f3cca3fb2b8bcf811633bee06": "Geschreven door %s",
"s18n-81fdc9813cebe0553c55e78dc2b6029f": "op %s",
"s18n-be1ab1632e4285edc3733b142935c60b": "Vind ik leuk",
"s18n-bc8b79025e4595298669fd21da814941": "Vind ik niet leuk",
"s18n-e84afaab83ecb301b3d97ce4174d2773": "Beantwoorden"
}

10
metadata.json

@ -0,0 +1,10 @@
{
"author": "SamBrishes, pytesNET",
"email": "sam@pytes.net",
"website": "https://www.pytes.net",
"version": "0.1.2",
"releaseDate": "2019-05-06",
"license": "MIT",
"compatible": "3.5.0",
"notes": ""
}

893
plugin.php

@ -0,0 +1,893 @@
<?php
/*
| Snicker The first native FlatFile Comment Plugin 4 Bludit
| @file ./plugin.php
| @author SamBrishes <sam@pytes.net>
| @version 0.1.2 [0.1.0] - Alpha
|
| @website https://github.com/pytesNET/snicker
| @license X11 / MIT License
| @copyright Copyright © 2019 SamBrishes, pytesNET <info@pytes.net>
*/
if(!defined("BLUDIT")){ die("Go directly to Jail. Do not pass Go. Do not collect 200 Cookies!"); }
require_once "system/functions.php"; // Load Basic Functions
class SnickerPlugin extends Plugin{
/*
| BACKEND VARIABLES
*/
private $backend = false; // Is Backend
private $backendView = null; // Backend View / File
private $backendRequest = null; // Backend Request Type ("post", "get", "ajax")
/*
| CONSTRUCTOR
| @since 0.1.0
*/
public function __construct(){
global $SnickerPlugin;
$SnickerPlugin = $this;
parent::__construct();
}
##
## HELPER METHODs
##
/*
| HELPER :: SELECTED
| @since 0.1.0
|
| @param string The respective option key (used in `getValue()`).
| @param multi The value to compare with.
| @param bool TRUE to print `selected="selected"`, FALSE to return the string.
| Use `null` to return as boolean!
|
| @return multi The respective string, nothing or a BOOLEAN indicator.
*/
public function selected($field, $value = true, $print = true){
if(sn_config($field) == $value){
$selected = 'selected="selected"';
} else {
$selected = '';
}
if($print === null){
return !empty($selected);
}
if(!$print){
return $selected;
}
print($selected);
}
/*
| HELPER :: CHECKED
| @since 0.1.0
|
| @param string The respective option key (used in `getValue()`).
| @param multi The value to compare with.
| @param bool TRUE to print `checked="checked"`, FALSE to return the string.
| Use `null` to return as boolean!
|
| @return multi The respective string, nothing or a BOOLEAN indicator.
*/
public function checked($field, $value = true, $print = true){
if(sn_config($field) == $value){
$checked = 'checked="checked"';
} else {
$checked = '';
}
if($print === null){
return !empty($checked);
}
if(!$print){
return $checked;
}
print($checked);
}
##
## PLUGIN HOOKs
##
/*
| PLUGIN :: GET VALUE
| @since 0.1.2
*/
public function getValue($field, $html = true){
if(isset($this->db[$field])){
$data = strpos($field, "string_") === 0? sn__($this->db[$field]): $this->db[$field];
return ($html)? $data: Sanitize::htmlDecode($data);
}
return isset($this->dbFields[$field])? $this->dbFields[$field]: null;
}
/*
| PLUGIN :: INIT
| @since 0.1.0
| @update 0.1.1
*/
public function init(){
global $url;
// Init Default Settings
$this->dbFields = array(
"moderation" => true,
"moderation_loggedin" => true,
"moderation_approved" => true,
"comment_on_public" => true,
"comment_on_static" => false,
"comment_on_sticky" => true,
"comment_title" => "optional",
"comment_limit" => 0,
"comment_depth" => 3,
"comment_markup_html" => true,
"comment_markup_markdown" => false,
"comment_vote_storage" => "session",
"comment_enable_like" => true,
"comment_enable_dislike" => true,
"frontend_captcha" => function_exists("imagettfbbox")? "gregwar": "purecaptcha",
"frontend_recaptcha_public" => "",
"frontend_recaptcha_private"=> "",
"frontend_terms" => "default",
"frontend_filter" => "pageEnd",
"frontend_template" => "default",
"frontend_order" => "date_desc",
"frontend_form" => "top",
"frontend_per_page" => 15,
"frontend_ajax" => true,
"frontend_avatar" => "gravatar",
"frontend_avatar_users" => true,
"frontend_gravatar" => "mp",
"subscription" => false,
"subscription_from" => "ticker@{$_SERVER["SERVER_NAME"]}",
"subscription_reply" => "noreply@{$_SERVER["SERVER_NAME"]}",
"subscription_optin" => "default",
"subscription_ticker" => "default",
// Frontend Messages, can be changed by the user
"string_success_1" => sn__("Thanks for your comment!"),
"string_success_2" => sn__("Thanks for your comment, please confirm your subscription via the link we sent to your eMail address!"),
"string_success_3" => sn__("Thanks for voting this comment!"),
"string_error_1" => sn__("An unknown error occured, please reload the page and try it again!"),
"string_error_2" => sn__("An error occured: The passed Username is invalid or too long (42 characters only)!"),
"string_error_3" => sn__("An error occured: The passed eMail address is invalid!"),
"string_error_4" => sn__("An error occured: The comment text is missing!"),
"string_error_5" => sn__("An error occured: The comment title is missing!"),
"string_error_6" => sn__("An error occured: You need to accept the Terms to comment!"),
"string_error_7" => sn__("An error occured: Your IP address or eMail address has been marked as Spam!"),
"string_error_8" => sn__("An error occured: You already rated this comment!"),
"string_terms_of_use" => sn__("I agree that my data (incl. my anonymized IP address) gets stored!")
);
// Check Backend
$this->backend = (trim($url->activeFilter(), "/") == ADMIN_URI_FILTER);
}
/*
| PLUGIN :: OVERWRITE INSTALLED
| @since 0.1.0
| @update 0.1.1
*/
public function installed(){
global $Snicker, // Main Comment Handler
$SnickerIndex, // Main Comment Indexer
$SnickerUsers, // Main Comment Users
$SnickerVotes; // Main Comment Votes
if(file_exists($this->filenameDb)){
if(!defined("SNICKER")){
define("SNICKER", true);
define("SNICKER_PATH", PATH_PLUGINS . basename(__DIR__) . DS);
define("SNICKER_DOMAIN", DOMAIN_PLUGINS . basename(__DIR__) . "/");
define("SNICKER_VERSION", "0.1.2");
// DataBases
define("DB_SNICKER_COMMENTS", $this->workspace() . "pages" . DS);
define("DB_SNICKER_INDEX", $this->workspace() . "comments-index.php");
define("DB_SNICKER_USERS", $this->workspace() . "comments-users.php");
define("DB_SNICKER_VOTES", $this->workspace() . "comments-votes.php");
// Pages Filter
if(!file_exists(DB_SNICKER_COMMENTS)){
@mkdir(DB_SNICKER_COMMENTS);
}
// Load Plugin
require_once "system/abstract.comments-theme.php";
require_once "system/class.comment.php";
require_once "system/class.comments.php";
require_once "system/class.comments-index.php";
require_once "system/class.comments-users.php";
require_once "system/class.comments-votes.php";
require_once "system/class.snicker.php";
require_once "includes/autoload.php";
} else {
$Snicker = new Snicker();
$SnickerIndex = new CommentsIndex();
$SnickerUsers = new CommentsUsers();
$SnickerVotes = new CommentsVotes();
$this->request();
}
return true;
}
return false;
}
##
## API METHODs
##
/*
| API :: HANDLE RESPONSE
| @since 0.1.0
|
| @param array The response data, which MUST contain at least the status:
| "error" The error message (required).
| "success" The success message (required).
|
| :: NON-AJAX ONLY
| "referer" A referer URL (The current URL is used, if not present)
|
| :: AJAX-BASED ONLY
| :any Any additional data, which should return to the client.
|
| @return none This method calls the die(); method at any time!
*/
public function response($data = array(), $key = null){
global $url;
// Validate
if(isset($data["success"]) || isset($data["error"])){
$status = isset($data["success"]);
} else {
$status = false;
$data["error"] = sn__("An unknown error occured!");
}
// POST Redirect
if($this->backendRequest !== "ajax"){
if($status){
$key = empty($key)? "snicker-success": $key;
Alert::set($data["success"], ALERT_STATUS_OK, $key);
} else {
$key = empty($key)? "snicker-alert": $key;
Alert::set($data["error"], ALERT_STATUS_FAIL, $key);
}
if($data["referer"]){
Redirect::url($data["referer"]);
} else {
$action = isset($_GET["snicker"])? $_GET["snicker"]: $_POST["snicker"];
Redirect::url(HTML_PATH_ADMIN_ROOT . $url->slug() . "#{$action}");
}
die();
}
// AJAX Print
if(!is_array($data)){
$data = array();
}
$data["status"] = ($status)? "success": "error";
$data = json_encode($data);
header("Content-Type: application/json");
header("Content-Length: " . strlen($data));
print($data);
die();
}
/*
| API :: HANDLE REQUESTS
| @since 0.1.0
| @update 0.1.1
*/
public function request(){
global $login, $security, $url, $Snicker;
// Get POST/GET Request
if(isset($_POST["action"]) && $_POST["action"] === "snicker"){
$data = $_POST;
$this->backendRequest = "post";
} else if(isset($_GET["action"]) && $_GET["action"] === "snicker"){
$data = $_GET;
$this->backendRequest = "get";
}
if(!(isset($data) && isset($data["snicker"]))){
$this->backendRequest = null;
return null;
}
// Get AJAX Request
$ajax = "HTTP_X_REQUESTED_WITH";
if(strpos($url->slug(), "snicker/ajax") === 0){
if(isset($_SERVER[$ajax]) && $_SERVER[$ajax] === "XMLHttpRequest"){
$this->backendRequest = "ajax";
} else {
return Redirect::url(HTML_PATH_ADMIN_ROOT . "snicker/");
}
} else if(isset($_SERVER[$ajax]) && $_SERVER[$ajax] === "XMLHttpRequest"){
print("Invalid AJAX Call"); die();
}
if($this->backendRequest === "ajax" && !sn_config("frontend_template")){
print("AJAX Calls has been disabled"); die();
}
// Start Session
if(!Session::started()){
Session::start();
}
$key = null;
if(in_array($data["snicker"], array("add", "edit", "delete", "config", "users", "backup", "moderate"))){
$key = "alert";
}
// Check CSRF Token
if(!empty($key)){
if(!isset($data["tokenCSRF"])){
return $this->response(array(
"error" => sn__("The CSRF Token is missing!")
));
}
if(!$security->validateTokenCSRF($data["tokenCSRF"])){
return $this->response(array(
"error" => sn__("The CSRF Token is invalid!")
));
}
}
// Check Permissions
if(!empty($key)){
if(!is_a($login, "Login")){
$login = new Login();
}
if(!$login->isLogged()){
return $this->response(array(
"error" => sn__("You don't have the permission to call this action!")
));
}
if($login->role() !== "admin"){
return $this->response(array(
"error" => sn__("You don't have the permission to perform this action!")
));
}
}
// Route
switch($data["snicker"]){
case "comment": //@fallthrough
case "reply": //@fallthrough
case "add":
return $Snicker->writeComment($data["comment"], $key);
/* case "update": */ //@todo User can edit his own comments
case "edit":
return $Snicker->editComment($data["uid"], $data["comment"], $key);
/* case "remove": */ //@todo User can delete his own comments
case "delete":
return $Snicker->deleteComment($data["uid"], $key);
case "moderate":
return $Snicker->moderateComment($data["uid"], $data["status"], $key);
case "list": //@fallthrough
case "get":
return $Snicker->renderComment($data);
case "rate":
return $Snicker->rateComment($data["uid"], $data["type"]);
case "users":
return $this->user($data);
case "configure":
return $this->config($data);
case "backup":
return $this->backup();
case "captcha":
return $this->response(array(
"success" => sn__("The Captcha Image could be successfully created!"),
"captcha" => $Snicker->generateCaptcha(150, 40, true)
));
}
return $this->response(array(
"error" => sn__("The passed action is unknown or invalid!")
), "alert");
}
/*
| API :: HANDLE USERs
| @since 0.1.0
*/
private function user($data){
global $SnickerIndex, $SnickerUsers;
// Validate Data
if(!isset($data["uuid"]) || !isset($data["handle"])){
return $this->response(array(
"error" => sn__("An unknown error is occured!")
), "alert");
}
// Validata UUID
if(!$SnickerUsers->exists($data["uuid"])){
return $this->response(array(
"error" => sn__("An unique user ID does not exist!")
), "alert");
}
// Handle
if($data["handle"] === "delete"){
$comments = $SnickerUsers->db[$data["uuid"]]["comments"];
foreach($comments AS $uid){
if(!$SnickerIndex->exists($uid)){
continue;
}
$index = $SnickerIndex->getComment($uid);
$comment = new Comments($index["page_uuid"]);
if(isset($data["anonymize"]) && $data["anonymize"] === "true"){
$comment = new Comments($index["page_uuid"]);
$comment->edit($uid, array("author" => "anonymous"));
} else {
$comment = new Comments($index["page_uuid"]);
$comment->delete($uid);
}
}
$status = $SnickerUsers->delete($data["uuid"]);
} else if($data["handle"] === "block"){
$status = $SnickerUsers->edit($data["uuid"], null, null, true);
} else if($data["handle"] === "unblock"){
$status = $SnickerUsers->edit($data["uuid"], null, null, false);
}
// Redirect
if(!isset($status)){
return $this->response(array(
"error" => sn__("The passed action is unknown or invalid!")
), "alert");
}
if($status === false){
return $this->response(array(
"error" => sn__("An unknown error is occured!")
), "alert");
}
return $this->response(array(
"success" => sn__("The action has been performed successfully!")
), "alert");
}
/*
| API :: HANDLE CONFIGURATION
| @since 0.1.0
| @update 0.2.0
*/
private function config($data){
global $pages, $Snicker;
$config = array();
// Validations
$text = array("frontend_recaptcha_public", "frontend_recaptcha_private");
$numbers = array("comment_limit", "comment_depth", "frontend_per_page");
$selects = array(
"comment_title" => array("optional", "required", "disabled"),
"comment_vote_storage" => array("cookie", "session", "database"),
"frontend_captcha" => array("disabled", "purecaptcha", "gregwar", "recaptchav2", "recaptchav3"),
"frontend_avatar" => array("gravatar", "identicon", "static", "initials"),
"frontend_gravatar" => array("mp", "identicon", "monsterid", "wavatar", "retro", "robohash", "blank"),
"frontend_filter" => array("disabled", "pageBegin", "pageEnd", "siteBodyBegin", "siteBodyEnd"),
"frontend_order" => array("date_desc", "date_asc"),
"frontend_form" => array("top", "bottom")
);
$emails = array("subscription_from", "subscription_reply");
$pageid = array("frontend_terms", "subscription_optin", "subscription_ticker");
// Loop DB Fields
foreach($this->dbFields AS $field => $value){
if(!isset($data[$field])){
$config[$field] = is_bool($value)? false: "";
continue;
}
// Sanitize Booleans
if(is_bool($value)){
$config[$field] = ($data[$field] === "true" || $data[$field] === true);
continue;
}
// Sanitize Numbers
if(in_array($field, $numbers)){
if($data[$field] < 0 || !is_numeric($data[$field])){
$config[$field] = 0;
}
$config[$field] = (int) $data[$field];
continue;
}
// Sanitize Selection
if(array_key_exists($field, $selects)){
if(in_array($data[$field], $selects[$field])){
$config[$field] = $data[$field];
} else {
$config[$field] = $value;
}
continue;
}
// Sanitize eMails
if(in_array($field, $emails)){
if(Valid::email($data[$field])){
$config[$field] = Sanitize::email($data[$field]);
} else {
$config[$field] = $value;
}
continue;
}
// Sanitize Pages
if(in_array($field, $pageid)){
$default = in_array($data[$field], array("default", "disabled"));
if($default || $pages->exists($data[$field])){
$config[$field] = $data[$field];
} else {
$config[$field] = $value;
}
continue;
}
// Sanitize Template
if($field == "frontend_template"){
if($Snicker->hasTheme($data[$field])){
$config[$field] = $data[$field];
} else {
$config[$field] = $value;
}
continue;
}
// Sanitize Strings
if(strpos($field, "string_") === 0 || in_array($field, $text)){
$config[$field] = Sanitize::html(strip_tags($data[$field]));
if(empty($config[$field])){
$config[$field] = $value;
}
continue;
}
}
// Save & Return
$this->db = array_merge($this->db, $config);
if(!$this->save()){
return $this->response(array(
"error" => sn__("An unknown error is occured!")
), "alert");
}
return $this->response(array(
"success" => sn__("The settings has been updated successfully!")
), "alert");
}
/*
| API :: CREATE BACKUP
| @since 0.1.0
*/
private function backup(){
$filename = "snicker-backup-" . time() . ".zip";
// Create Backup
$zip = new PIT\Zip();
$zip->addFolder($this->workspace(), "/", true, true);
$zip->save(PATH_TMP . $filename);
// Return
return $this->response(array(
"success" => sn__("The backup has been created successfully!"),
"referer" => DOMAIN_ADMIN . "uninstall-plugin/SnickerPlugin"
), "alert");
}
##
## BACKEND HOOKs
##
/*
| HOOK :: INIT ADMINISTRATION
| @since 0.1.0
*/
public function beforeAdminLoad(){
global $url;
// Check if the current View is the "snicker"
if(strpos($url->slug(), "snicker") !== 0){
return false;
}
checkRole(array("admin"));
// Set Backend View
$split = str_replace("snicker", "", trim($url->slug(), "/"));
if(!empty($split) && $split !== "/" && isset($_GET["uid"])){
$this->backendView = "edit";
} else {
$this->backendView = "index";
}
}
/*
| HOOK :: LOAD ADMINISTRATION FILES
| @since 0.1.0
*/
public function adminHead(){
global $page, $security, $url;
$js = SNICKER_DOMAIN . "admin/js/";
$css = SNICKER_DOMAIN . "admin/css/";
$slug = explode("/", str_replace(HTML_PATH_ADMIN_ROOT, "", $url->uri()));
// Admin Header
ob_start();
if($slug[0] === "new-content" || $slug[0] === "edit-content"){
?>
<script type="text/javascript">
(function(){
"use strict";
var w = window, d = window.document;
// Render Field
var HANDLE_COMMENTS_FIELD = '<?php echo Bootstrap::formSelectBlock(array(
'name' => 'allowComments',
'label' => sn__('Page Comments'),
'selected' => (!$page)? '1': ($page->allowComments()? '1': '0'),
'class' => '',
'options' => array(
'1' => sn__('Allow Comments'),
'0' => sn__('Disallow Comments')
)
)); ?>';
// Ready ?
d.addEventListener("DOMContentLoaded", function(){
if(d.querySelector("#jscategory")){
var form = d.querySelector("#jscategory").parentElement;
form.insertAdjacentHTML("afterend", HANDLE_COMMENTS_FIELD);
}
});
}());
</script>
<?php
} else if($slug[0] === "snicker"){
?>
<script type="text/javascript" src="<?php echo $js; ?>admin.snicker.js"></script>
<link type="text/css" rel="stylesheet" href="<?php echo $css; ?>admin.snicker.css" />
<?php
} else if($slug[0] === "plugins"){
$link = DOMAIN_ADMIN . "snicker?action=snicker&snicker=backup&tokenCSRF=" . $security->getTokenCSRF();
?>
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", function(){
var link = document.querySelector("tr#SnickerPlugin td a");
if(link){
link.addEventListener("click", function(event){
event.preventDefault();
jQuery("#dialog-deactivate-snicker").modal();
});
jQuery("#dialog-deactivate-snicker button[data-snicker='backup']").click(function(){
console.log("owo");
window.location.replace("<?php echo $link; ?>&referer=" + link.href);
});
jQuery("#dialog-deactivate-snicker button[data-snicker='deactivate']").click(function(){
window.location.replace(link.href);
});
}
})
</script>
<?php
}
$content = ob_get_contents();
ob_end_clean();
return $content;
}
/*
| HOOK :: BEFORE ADMIN CONTENT
| @since 0.1.0
*/
public function adminBodyBegin(){
if(!$this->backend || !$this->backendView){
return false;
}
ob_start();
}
/*
| HOOK :: AFTER ADMIN CONTENT
| @since 0.1.0
*/
public function adminBodyEnd(){
global $url, $SnickerPlugin;
if(!$this->backend || !$this->backendView){
$slug = explode("/", str_replace(HTML_PATH_ADMIN_ROOT, "", $url->uri()));
if($slug[0] === "plugins"){
?>
<div id="dialog-deactivate-snicker" class="modal fade" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><?php sn_e("Snicker Plugin Deactivation"); ?></h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p>
<?php sn_e("You are about to deactivate the <b>Snicker</b> Plugin, which will delete all written comments!"); ?>
<?php sn_e("Do you want to Backup your comments before?"); ?>
</p>
<p>
<?php sn_e("The Backup will be stored in %s!", array("<code>./bl-content/tmp/</code>")); ?>
</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-snicker="backup"><?php sn_e("Yes, create a Backup"); ?></button>
<button type="button" class="btn btn-danger" data-snicker="deactivate"><?php sn_e("No, just Deactivate"); ?></button>
<button type="button" class="btn btn-secondary" data-dismiss="modal"><?php sn_e("Cancel"); ?></button>
</div>
</div>
</div>
</div>
<?php
}
return false;
}
// Fetch Content
$content = ob_get_contents();
ob_end_clean();
// Snicker Admin Content
ob_start();
if(file_exists(SNICKER_PATH . "admin" . DS . "{$this->backendView}.php")){
require SNICKER_PATH . "admin" . DS . "{$this->backendView}.php";
$add = ob_get_contents();
}
ob_end_clean();
// Inject Code
if(isset($add) && !empty($add)){
$regexp = "#(\<div class=\"col-lg-10 pt-3 pb-1 h-100\"\>)(.*?)(\<\/div\>)#s";
$content = preg_replace($regexp, "$1{$add}$3", $content);
}
print($content);
}
/*
| HOOK :: SHOW SIDEBAR MENU
| @since 0.1.0
*/
public function adminSidebar(){
global $SnickerIndex;
$count = $SnickerIndex->count("pending");
$count = ($count > 99)? "99+": $count;
ob_start();
?>
<a href="<?php echo HTML_PATH_ADMIN_ROOT; ?>snicker" class="nav-link" style="white-space: nowrap;">
<span class="oi oi-comment-square"></span>Snicker <?php sn_e("Comments"); ?>
<?php if(!empty($count)){ ?>
<span class="badge badge-success badge-pill"><?php echo $count; ?></span>
<?php } ?>
</a>
<?php
$content = ob_get_contents();
ob_end_clean();
return $content;
}
##
## FRONTEND HOOKs
##
/*
| HOOK :: BEFORE FRONTEND LOAD
| @since 0.1.0
| @update 0.1.2
*/
public function beforeSiteLoad(){
global $comments, $page, $url;
// Start Session
if(!Session::started()){
Session::start();
}
// Init Comments
if(is_a($page, "Page") && $page->published() && !empty($page->uuid())){
$comments = new Comments($page->uuid());
} else {
$comments = false;
}
}
/*
| HOOK :: FRONTEND HEADER
| @since 0.1.0
*/
public function siteHead(){
global $Snicker;
if(($theme = $Snicker->getTheme()) === false){
return false;
}
if(!empty($theme::SNICKER_JS)){
$file = SNICKER_DOMAIN . "themes/" . sn_config("frontend_template") . "/" . $theme::SNICKER_JS;
?>
<script type="text/javascript">
var SNICKER_AJAX = <?php echo sn_config("frontend_ajax")? "true": "false"; ?>;
var SNICKER_PATH = "<?php echo HTML_PATH_ADMIN_ROOT ?>snicker/ajax/";
</script>
<script id="snicker-js" type="text/javascript" src="<?php echo $file; ?>"></script>
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
<?php
}
if(!empty($theme::SNICKER_CSS)){
$file = SNICKER_DOMAIN . "themes/" . sn_config("frontend_template") . "/" . $theme::SNICKER_CSS;
?>
<link id="snicker-css" type="text/css" rel="stylesheet" href="<?php echo $file; ?>" />
<?php
}
}
/*
| HOOK :: FRONTEND CONTENT
| @since 0.1.0
*/
public function siteBodyBegin(){
global $Snicker;
if(sn_config("frontend_filter") !== "siteBodyBegin"){
return false; // owo
}
print($Snicker->render());
}
/*
| HOOK :: FRONTEND CONTENT
| @since 0.1.0
*/
public function pageBegin(){
global $Snicker;
if(sn_config("frontend_filter") !== "pageBegin"){
return false; // Owo
}
print($Snicker->render());
}
/*
| HOOK :: FRONTEND CONTENT
| @since 0.1.0
*/
public function pageEnd(){
global $Snicker;
if(sn_config("frontend_filter") !== "pageEnd"){
return false; // owO
}
print($Snicker->render());
}
/*
| HOOK :: FRONTEND CONTENT
| @since 0.1.0
*/
public function siteBodyEnd(){
global $Snicker;
if(sn_config("frontend_filter") !== "siteBodyEnd"){
return false; // OwO
}
print($Snicker->render());
}
}

46
system/abstract.comments-theme.php

@ -0,0 +1,46 @@
<?php
/*
| Snicker The first native FlatFile Comment Plugin 4 Bludit
| @file ./system/abstract.comments-theme.php
| @author SamBrishes <sam@pytes.net>
| @version 0.1.2 [0.1.0] - Alpha
|
| @website https://github.com/pytesNET/snicker
| @license X11 / MIT License
| @copyright Copyright © 2019 SamBrishes, pytesNET <info@pytes.net>
*/
if(!defined("BLUDIT")){ die("Go directly to Jail. Do not pass Go. Do not collect 200 Cookies!"); }
abstract class CommentsTheme{
/*
| REQUIRED :: FORM
| @note This method renders the comment form used on the frontend.
|
| @param multi The previously passed username (on errors only)
| An `array(username, hash, nickname)` array if the user is logged in.
| @param string The previously passed email address (on errors only)!
| @param string The previously passed comment title (on errors only)!
| @param string The previously passed comment message (on errors only)!
*/
abstract public function form($username = "", $email = "", $title = "", $message = "");
/*
| REQUIRED :: COMMENT
| @note This method renders the single shown comments on the frontend.
|
| @param object The comment instance.
| @param string The unique comment UID.
*/
abstract public function comment($comment, $uid, $depth);
/*
| REQUIRED :: PAGINATION
| @note This method renders the pagination for the comment section.
|
| @param string The called location: "top" or "bottom".
| @param int The current comment page.<, startin with 1.
| @param int The number of comments to be shown per page.
| @param int The total number of comments for the content page.
*/
abstract public function pagination($loction, $cpage, $limit, $count);
}

494
system/class.comment.php

@ -0,0 +1,494 @@
<?php
/*
| Snicker The first native FlatFile Comment Plugin 4 Bludit
| @file ./system/class.comment.php
| @author SamBrishes <sam@pytes.net>
| @version 0.1.2 [0.1.0] - Alpha
|
| @website https://github.com/pytesNET/snicker
| @license X11 / MIT License
| @copyright Copyright © 2019 SamBrishes, pytesNET <info@pytes.net>
*/
if(!defined("BLUDIT")){ die("Go directly to Jail. Do not pass Go. Do not collect 200 Cookies!"); }
class Comment{
/*
| CONSTRUCTOR
| @since 0.1.0
|
| @param multi The unique comment id or FALSE.
| @param multi The unique page comment ID.
*/
public function __construct($uid, $uuid){
$this->vars["uid"] = $uid;
if($uid === false){
$row = array(
"type" => "comment",
"depth" => 1,
"title" => "",
"comment" => "",
"rating" => [0, 0],
"page_uuid" => "",
"parent_uid" => "",
"author" => "",
"subscribe" => false,
"date" => "",
"dateModified" => "",
"dateAudit" => "",
"custom" => array()
);
} else {
$comments = new Comments($uuid);
if(Text::isEmpty($uid) || !$comments->exists($uid)){
// @todo Throw Error
}
$row = $comments->getCommentDB($uid);
}
// Set Class
foreach($row AS $field => $value){
if(strpos($field, "date") === 0){
$this->vars["{$field}Raw"] = $value;
} else {
$this->vars[$field] = $value;
}
}
}
/*
| PUBLIC :: GET VALUE
| @since 0.1.0
|
| @param string The unique field key.
| @param multi The default value, which should return if the field key doesnt exist.
|
| @multi multi The respective field value on success, $default otherwise.
*/
public function getValue($field, $default = false){
if(isset($this->vars[$field])){
return $this->vars[$field];
}
return $default;
}
/*
| PUBLIC :: SET FIELD
| @since 0.1.0
|
| @param string The unique field key.
| @param multi The respective field value, which you want to set.
|
| @return bool TRUE
*/
public function setField($field, $value = NULL){
if(is_array($field)){
foreach($field AS $k => $v){
$this->setField($k, $v);
}
return true;
}
$this->vars[$field] = $value;
return true;
}
/*
| FIELD :: COMMENT RAW
| @since 0.1.0
|
| @return string The (sanitized) raw content on success, FALSE on failure.
*/
public function commentRaw(){
return $this->getValue("comment");
}
/*
| FIELD :: COMMENT
| @since 0.1.0
|
| @return string The (sanitized) content on success, FALSE on failure.
*/
public function comment(){
$content = $this->getValue("comment");
if(sn_config("comment_markup_html")){
$content = Sanitize::htmlDecode($content);
}
if(sn_config("comment_markup_markdown")){
$parsedown = new Parsedown();
$content = $parsedown->text($content);
}
return $content;
}
/*
| FIELD :: GET UID
| @since 0.1.0
*/
public function uid(){
return $this->getValue("uid");
}
public function key(){
return $this->getValue("uid");
}
/*
| FIELD :: GET (COMMENT FILE) PATH
| @since 0.1.0
*/
public function path(){
return PATH_PAGES . $this->getValue("page_key") . DS . "comments";
}
/*
| FIELD :: GET (COMMENT FILE) PATH / FILE
| @since 0.1.0
*/
public function file(){
return $this->path() . DS . "c_" . $this->getValue("uid") . ".php";
}
/*
| FIELD :: GET TYPE
| @since 0.1.0
*/
public function type(){
return $this->getValue("type");
}
public function isComment(){
return $this->getValue("type") === "comment";
}
public function isReply(){
return $this->getValue("type") === "reply";
}
public function isPingback(){
return $this->getValue("type") === "pingback";
}
/*
| FIELD :: GET DEPTH
| @since 0.1.0
*/
public function depth(){
return (int) $this->getValue("depth");
}
/*
| FIELD :: TITLE
| @since 0.1.0
|
| @param bool TRUE to sanitize the content, FALSE to return it plain.
|
| @return string The respective comment title as STRING.
*/
public function title($sanitize = true){
if($sanitize){
return Sanitize::html($this->getValue("title"));
}
return $this->getValue("title");
}
/*
| FIELD :: GET STATUS
| @since 0.1.0
*/
public function status(){
return $this->getValue("status");
}
public function isPending(){
return $this->getValue("status") === "pending";
}
public function isPublic(){
return $this->getValue("status") === "approved";
}
public function isApproved(){
return $this->getValue("status") === "approved";
}
public function isRejected(){
return $this->getValue("status") === "rejected";
}
public function isSpam(){
return $this->getValue("status") === "spam";
}
/*
| FIELD :: GET RATING
| @since 0.1.0
*/
public function rating(){
return $this->getValue("rating");
}
/*
| FIELD :: GET LIKE
| @since 0.1.0
*/
public function like(){
$rating = $this->getValue("rating");
if(is_array($rating) && count($rating) >= 1){
return $rating[0];
}
return 0;
}
/*
| FIELD :: GET DISLIKE
| @since 0.1.0
*/
public function dislike(){
$rating = $this->getValue("rating");
if(is_array($rating) && count($rating) >= 2){
return $rating[1];
}
return 0;
}
/*
| FIELD :: GET PAGE KEY
| @since 0.1.0
*/
public function page_key(){
return $this->getValue("page_key");
}
/*
| FIELD :: GET PAGE UUID
| @since 0.1.0
*/
public function page_uuid(){
return $this->getValue("page_uuid");
}
/*
| FIELD :: GET PARENT UID
| @since 0.1.0
*/
public function parent_uid(){
return $this->getValue("parent_uid");
}
/*
| FIELD :: GET PARENT
| @since 0.1.0
*/
public function parent(){
global $comments;
if($comments->exists($this->getValue("parent_uid"))){
return new Comment($this->getValue("parent_uid"));
}
return false;
}
/*
| FIELD :: GET CHILDREN
| @since 0.1.0
|
| @param multi The single comment status which should return, multiple as ARRAY.
| Use `null` to return each children comment.
| @param string The return type, which allows the following strings:
| "uids" Return just the respective UID / keys
| "keys" Return just the respective UID / keys
| "objects" Return single Comment instances
| "arrays" Return the unformatted DB arrays
|
| @return multi FALSE on error, the respective array on succes.
*/
public function children($status = "approved", $return = "objects"){
global $comments;
// Check Parameter
if(is_string($status)){
$status = array($status);
}
if(!is_array($status) && $status !== null){
return false;
}
// Get Children
$return = array();
foreach($this->getDB(false) AS $uid => $value){
if($value["parent"] !== $this->getValue("uid")){
continue;
}
if(is_array($status) && !in_array($value["status"], $status)){
continue;
}
if($return === "uids" || $return == "keys"){
$return[] = $uid;
} else if($return === "objects"){
$return[$uid] = new Comment($uid);
} else {
$return[$uid] = $value;
}
}
return $return;
}
/*
| FIELD :: GET UUID
| @since 0.1.0
*/
public function uuid(){
return $this->getValue("uuid");
}
/*
| FIELD :: GET USERNAME
| @since 0.1.0
*/
public function username(){
global $L, $users, $SnickerUsers;
list($type, $id) = array_pad(explode("::", $this->getValue("author"), 2), 2, null);
switch($type){
case "bludit":
if(!$users->exists($id)){
break;
}
$user = new User($id);
return $user->nickname();
case "guest":
if(!$SnickerUsers->exists($id)){
break;
}
return $SnickerUsers->db[$id]["username"];
case "unknown":
return $L->g("Unknown User");
}
return false;
}
/*
| FIELD :: GET EMAIL
| @since 0.1.0
*/
public function email(){
global $L, $users, $SnickerUsers;
list($type, $id) = array_pad(explode("::", $this->getValue("author"), 2), 2, null);
switch($type){
case "bludit":
if(!$users->exists($id)){
break;
}
$user = new User($id);
return $user->email();
case "guest":
if(!$SnickerUsers->exists($id)){
break;
}
return $SnickerUsers->db[$id]["email"];
case "unknown":
return "unknown@" . $_SERVER["SERVER_NAME"];
}
return false;
}
/*
| FIELD :: SUBSCRIBE
| @since 0.1.0
*/
public function subscribe(){
return $this->getValue("subscribe");
}
public function hasSubscribed(){
return $this->getValue("subscribe") === true;
}
/*
| FIELD :: GET AVATAR
| @since 0.1.0
*/
public function avatar($size = "64"){
$user = $this->username();
$email = md5(strtolower(trim($this->email())));
$avatar = $this->avatar_url($size);
// Force Profile Picture
$force = false;
if(sn_config("frontend_avatar_users")){
$force = (strpos($avatar, DOMAIN_UPLOADS_PROFILES) !== false);
}
// Return IMG Tag
if(sn_config("frontend_avatar") === "identicon" && !$force){
return '<img src="'.$avatar.'" width="'.$size.'px" height="'.$size.'px" data-identicon="'.$email.'" alt="'.$user.'" />';
}
return '<img src="'.$avatar.'" width="'.$size.'px" height="'.$size.'px" alt="'.$user.'" />';
}
/*
| FIELD :: GET AVATAR URL
| @since 0.1.0
*/
public function avatar_url($size = "64"){
global $users;
// Return Profile Picture
if(sn_config("frontend_avatar_users") && strpos($this->getValue("author"), "bludit") === 0){
$username = substr($this->getValue("author"), strlen("bludit::"));
if($users->exists($username)){
$user = new User($username);
if(($avatar = $user->profilePicture()) !== false){
return $avatar;
}
}
}
// Return Gravatar
if(sn_config("frontend_avatar") === "gravatar"){
$hash = md5(strtolower(trim($this->email())));
return "https://www.gravatar.com/avatar/{$hash}?s={$size}&d=" . sn_config("frontend_gravatar");
}
// Return Identicon
if(sn_config("frontend_avatar") === "identicon"){
$hash = md5(strtolower(trim($this->email())));
$ident = new Identicon\Identicon();
return $ident->getImageDataUri($hash, $size);
}
// Return Mystery Man
return SNICKER_DOMAIN . "includes/img/default-avatar.jpg";
}
/*
| FIELD :: GET / FORMAT DATE
| @since 0.1.0
|
| @param string The respective format, which should be used for the output.
|
| @return string The formatted Date Output.
*/
public function date($format = false, $type = "date"){
global $site;
$date = $this->getValue("{$type}Raw");
return Date::format($date, DB_DATE_FORMAT, ($format? $format: $site->dateFormat()));
}
public function dateModified($format = false){
return $this->date($format, "dateModified");
}
public function dateAudit($format = false){
return $this->date($format, "dateAudit");
}
/*
| FIELD :: GET CUSTOM
| @since 0.1.0
|
| @param string The respective custom key, to get the value.
| Use `null` to get all custom values.
|
| @return multi The custom value, all customs as ARRAY or FALSE on failure.
*/
public function custom($key = NULL){
$custom = $this->getValue("custom");
if($key !== null){
if(array_key_exists($key, $custom)){
return $custom[$key];
}
return false;
}
return $custom;
}
}

476
system/class.comments-index.php

@ -0,0 +1,476 @@
<?php
/*
| Snicker The first native FlatFile Comment Plugin 4 Bludit
| @file ./system/class.comments-index.php
| @author SamBrishes <sam@pytes.net>
| @version 0.1.2 [0.1.0] - Alpha
|
| @website https://github.com/pytesNET/snicker
| @license X11 / MIT License
| @copyright Copyright © 2019 SamBrishes, pytesNET <info@pytes.net>
*/
if(!defined("BLUDIT")){ die("Go directly to Jail. Do not pass Go. Do not collect 200 Cookies!"); }
class CommentsIndex extends dbJSON{
/*
| DATABASE FIELDS
*/
protected $dbFields = array(
"title" => "", // Comment Title
"excerpt" => "", // Comment Excerpt (142)
"status" => "", // Comment Status
"page_uuid" => "", // Comment Page UUID
"parent_uid" => "", // Comment Parent UID
"author" => "", // Comment Author (bludt::username or guest::uuid)
"date" => "" // Comment Date
);
/*
| CONSTRUCTOR
| @since 0.1.0
*/
public function __construct(){
parent::__construct(DB_SNICKER_INDEX);
if(!file_exists(DB_SNICKER_INDEX)){
$this->db = array();
$this->save();
}
}
/*
| OVERWRITE :: EXISTS
| @since 0.1.0
*/
public function exists($uid){
return array_key_exists($uid, $this->db);
}
/*
| HELPER :: LIMIT LIST
| @since 0.1.0
*/
private function limitList($list, $page, $limit){
if($limit == -1){
return $list;
}
$offset = $limit * (max($page, 1) - 1);
$count = min(($offset + $limit - 1), count($list));
if($offset < 0 || $offset > $count){
return false;
}
return array_slice($list, $offset, $limit, true);
}
/*
| DATA :: GET PENDING INDEX
| @since 0.1.0
|
| @param bool TRUE to just return the keys, FALSE to return the complete array.
|
| @return array All pending comments with basic comment data as ARRAY.
*/
public function getPending($keys = false){
$db = array();
foreach($this->db AS $key => $value){
if(!isset($value["status"]) || empty($value["status"])){
continue;
}
if($value["status"] === "pending"){
$db[$key] = $value;
}
}
if($keys){
return array_keys($db);
}
return $db;
}
/*
| DATA :: GET APPROVED INDEX
| @since 0.1.0
|
| @param bool TRUE to just return the keys, FALSE to return the complete array.
|
| @return array All approved comments with basic comment data as ARRAY.
*/
public function getApproved($keys = false){
$db = array();
foreach($this->db AS $key => $value){
if(!isset($value["status"]) || empty($value["status"])){
continue;
}
if($value["status"] === "approved"){
$db[$key] = $value;
}
}
if($keys){
return array_keys($db);
}
return $db;
}
/*
| DATA :: GET REJECTED INDEX
| @since 0.1.0
|
| @param bool TRUE to just return the keys, FALSE to return the complete array.
|
| @return array All rejected comments with basic comment data as ARRAY.
*/
public function getRejected($keys = false){
$db = array();
foreach($this->db AS $key => $value){
if(!isset($value["status"]) || empty($value["status"])){
continue;
}
if($value["status"] === "rejected"){
$db[$key] = $value;
}
}
if($keys){
return array_keys($db);
}
return $db;
}
/*
| DATA :: GET SPAM INDEX
| @since 0.1.0
|
| @param bool TRUE to just return the keys, FALSE to return the complete array.
|
| @return array All spam comments with basic comment data as ARRAY.
*/
public function getSpam($keys = false){
$db = array();
foreach($this->db AS $key => $value){
if(!isset($value["status"]) || empty($value["status"])){
continue;
}
if($value["status"] === "spam"){
$db[$key] = $value;
}
}
if($keys){
return array_keys($db);
}
return $db;
}
/*
| DATA :: COUNT COMMENTS
| @since 0.1.0
|
| @param multi A single comment status as STRING, multiple as ARRAY.
| Use `null` to count all comments.
|
| @return int The number of comments of the respective index.
*/
public function count($status = array("approved")){
if($status === null){
return count($this->db);
}
if(!is_array($status)){
$status = array($status);
}
$count = 0;
foreach($this->db AS $key => $value){
if(!isset($value["status"]) || empty($value["status"])){
continue;
}
if(in_array($value["status"], $status)){
$count++;
}
}
return $count;
}
/*
| DATA :: GET COMMENT
| @since 0.1.0
|
| @param string The desired comment UID.
|
| @return multi The comment index array on success, FALSE on failure
*/
public function getComment($uid){
return array_key_exists($uid, $this->db)? $this->db[$uid]: false;
}
/*
| DATA :: LIST COMMENTS
| @since 0.1.0
|
| @param multi A single comment status as STRING, multiple as ARRAY.
| @param int The current comment page number, starting with 1.
| @param int The number of comments to be shown per page.
|
| @return array The respective unique comment IDs as ARRAY, FALSE on failure.
*/
public function getList($status = array("approved"), $page = 1, $limit = -1){
if($status === null){
return count($this->db);
}
if(!is_array($status)){
$status = array($status);
}
// Get List
$list = array();
foreach($this->db AS $key => $value){
if(!isset($value["status"]) || empty($value["status"])){
continue;
}
if(in_array($value["status"], $status)){
$list[] = $key;
}
}
return $this->limitList($list, $page, $limit);
}
/*
| DATA :: LIST COMMENTS BY UUID
| @since 0.1.0
|
| @param multi A single page UUID as STRING, multiple as ARRAY.
| @param int The current comment page number, starting with 1.
| @param int The number of comments to be shown per page.
|
| @return array The respective unique comment IDs as ARRAY, FALSE on failure.
*/
public function getListByUUID($uuid, $page = 1, $limit = -1){
if(!is_array($uuid)){
$uuid = array($uuid);
}
// Get List
$list = array();
foreach($this->db AS $key => $value){
if(!isset($value["page_uuid"]) || empty($value["page_uuid"])){
continue;
}
if(in_array($value["page_uuid"], $uuid)){
$list[] = $key;
}
}
return $this->limitList($list, $page, $limit);
}
/*
| DATA :: LIST COMMENTS BY PARENT
| @since 0.1.0
|
| @param multi A single comment UID as STRING.
| @param int The current comment page number, starting with 1.
| @param int The number of comments to be shown per page.
|
| @return array The respective unique comment IDs as ARRAY, FALSE on failure.
*/
public function getListByParent($uid, $page = 1, $limit = -1){
if(!is_string($uid) || !$this->exists($uid)){
return array();
}
// Get List
$list = array($uid);
foreach($this->db AS $key => $value){
if(!isset($value["parent_uid"]) || empty($value["parent_uid"])){
continue;
}
if($value["parent_uid"] === $uid){
$list[] = $key;
}
}
return $this->limitList($list, $page, $limit);
}
/*
| DATA :: LIST COMMENTS BY USER
| @since 0.1.0
|
| @param string A single username, unique user id or eMail address.
| @param int The current comment page number, starting with 1.
| @param int The number of comments to be shown per page.
|
| @return array The respective unique comment IDs as ARRAY, FALSE on failure.
*/
public function getListByUser($string, $page = 1, $limit = -1){
global $users, $SnickerUsers;
// Get Member / Guest
$guest = false;
$member = false;
if(Valid::email($string)){
if(($user = $users->getByEmail($string)) !== false){
$member = "bludit::{$user}";
}
} else {
if($users->exists($string)){
$member = "bludit::{$string}";
}
}
if(($user = $SnickerUsers->get($string)) !== false){
$guest = "guest::{$user["uuid"]}";
}
if(!$member && !$guest){
return array();
}
// Get List
$list = array();
foreach($this->db AS $key => $value){
if(!isset($value["author"]) || empty($value["author"])){
continue;
}
if($value["author"] == $member || $value["author"] == $guest){
$list[] = $key;
}
}
return $this->limitList($list, $page, $limit);
}
/*
| DATA :: SEARCH COMMENTS BY TITLE & EXCERPT
| @since 0.1.0
|
| @param string The string to be searched.
| @param int The current comment page number, starting with 1.
| @param int The number of comments to be shown per page.
|
| @return array The respective unique comment IDs as ARRAY, FALSE on failure.
*/
public function searchComments($search, $page = 1, $limit = -1){
$list = array();
foreach($this->db AS $key => $value){
if(isset($value["title"]) && stripos($value["title"], $search) !== false){
$list[] = $key;
} else if(isset($value["excerpt"]) && stripos($value["excerpt"], $search) !== false){
$list[] = $key;
}
}
return $this->limitList($list, $page, $limit);
}
/*
| HANDLE :: ADD COMMENT
| @since 0.1.0
|
| @param string The unique comment ID.
| @param array The comment array.
|
| @return bool TRUE if everything is fluffy, FALSE if not.
*/
public function add($uid, $comment){
$row = array();
foreach($this->dbFields AS $field => $value){
if(isset($comment[$field])){
$final = is_string($comment[$field])? Sanitize::html($comment[$field]): $comment[$field];
} else {
$final = $value;
}
settype($final, gettype($value));
$row[$field] = $final;
}
// Format Excerpt
$row["excerpt"] = strip_tags($comment["comment"]);
if(strlen($row["excerpt"]) > 142){
$row["excerpt"] = substr($row["excerpt"], 0, 139) . "...";
}
// Insert and Return
$this->db[$uid] = $row;
$this->sortBy();
if($this->save() !== true){
Log::set(__METHOD__, "error-update-db");
return false;
}
return true;
}
/*
| HANDLE :: UPDATE COMMENT
| @since 0.1.0
|
| @param string The unique comment ID.
| @param array The comment array.
|
| @return bool TRUE if everything is fluffy, FALSE if not.
*/
public function edit($uid, $comment){
if(!$this->exists($uid)){
$this->log(__METHOD__, "error-comment-uid", array($uid));
return false;
}
$data = $this->db[$uid];
// Loop Fields
$row = array();
foreach($this->dbFields AS $field => $value){
if(isset($comment[$field])){
$final = is_string($comment[$field])? Sanitize::html($comment[$field]): $comments[$field];
} else {
$final = $data[$field];
}
settype($final, gettype($value));
$row[$field] = $final;
}
// Format Excerpt
$row["excerpt"] = strip_tags($comment["comment"]);
if(strlen($row["excerpt"]) > 142){
$row["excerpt"] = substr($row["excerpt"], 0, 139) . "...";
}
// Update and Return
$this->db[$uid] = $row;
if($this->save() !== true){
Log::set(__METHOD__, "error-update-db");
return false;
}
return true;
}
/*
| HANDLE :: DELETE COMMENT
| @since 0.1.0
|
| @param string The unique comment ID.
|
| @return bool TRUE if everything is fluffy, FALSE if not.
*/
public function delete($uid){
if(!$this->exists($uid)){
return false;
}
unset($this->db[$uid]);
if($this->save() !== true){
Log::set(__METHOD__, "error-update-db");
return false;
}
return true;
}
/*
| INTERNAL :: SORT COMMENTS
| @since 0.1.0
|
| @return bool TRUE
*/
public function sortBy(){
global $SnickerPlugin;
if($SnickerPlugin->getValue("frontend_order") === "date_asc"){
uasort($this->db, function($a, $b){
return $a["date"] > $b["date"];
});
} else if($SnickerPlugin->getValue("frontend_order") === "date_desc"){
uasort($this->db, function($a, $b){
return $a["date"] < $b["date"];
});
}
return true;
}
}

392
system/class.comments-users.php

@ -0,0 +1,392 @@
<?php
/*
| Snicker The first native FlatFile Comment Plugin 4 Bludit
| @file ./system/class.comments-users.php
| @author SamBrishes <sam@pytes.net>
| @version 0.1.2 [0.1.0] - Alpha
|
| @website https://github.com/pytesNET/snicker
| @license X11 / MIT License
| @copyright Copyright © 2019 SamBrishes, pytesNET <info@pytes.net>
*/
if(!defined("BLUDIT")){ die("Go directly to Jail. Do not pass Go. Do not collect 200 Cookies!"); }
class CommentsUsers extends dbJSON{
/*
| DATABASE FIELDS
*/
protected $dbFields = array(
"username" => "", // Username
"email" => "", // User eMail Address
"hash" => "", // Hashed IP + User Agent
"blocked" => false, // Blocked?
"comments" => array() // Page UIDs => array(CommentUIDs)
);
/*
| CONSTRUCTOR
| @since 0.1.0
*/
public function __construct(){
parent::__construct(DB_SNICKER_USERS);
if(!file_exists(DB_SNICKER_USERS)){
$this->db = array();
$this->save();
}
}
/*
| GET COMMENTS BY UNIQUE USER ID
| @since 0.1.0
|
| @param string The unique user ID as string (or the user eMail address).
| @param bool TRUE to just return the keys, FALSE to return it as Comment objects.
|
| @return multi The comment keys / objects as ARRAY, FALSE on failure.
*/
public function getComments($uuid, $keys = true){
global $Snicker;
// Validate Data
if(Valid::email($uuid) !== false){
$uuid = md5(strtolower(Sanitize::email($uuid)));
}
if(!array_key_exists($uuid, $this->db)){
return false;
}
// Return Keys
$data = $this->db[$uuid]["comments"];
if($keys === true){
return $data;
}
// Return Objects
foreach($data AS &$key){
$key = $Snicker->getComment($key);
}
return $key;
}
/*
| EXISTS
| @since 0.1.0
*/
public function exists($uid){
return isset($this->db[$uid]);
}
/*
| GET USER BY UUID
| @since 0.1.0
|
| @param string The unique user ID as string (or the user eMail address).
|
| @return multi The user database array on success, FALSE on failure.
*/
public function get($uuid){
if(Valid::email($uuid) !== false){
$uuid = md5(strtolower(Sanitize::email($uuid)));
}
if(!array_key_exists($uuid, $this->db)){
return false;
}
$data = $this->db[$uuid];
$data["uuid"] = $uuid;
return $data;
}
/*
| GET CURRENT USER ID
| @since 0.1.0
|
| @return multi The user UUID on success, FALSE on failure.
*/
public function getCurrent(){
global $security;
$hash = md5($security->getUserIp() . $_SERVER["HTTP_USER_AGENT"]);
foreach($this->db AS $uuid => $fields){
if($fields["hash"] === $hash){
return $uuid;
}
}
return false;
}
/*
| GET USER
| @since 0.1.0
|
| @param string Get the user by Comment Author STRING.
|
| @return multi The user data array on success, FALSE on failure.
*/
public function getByString($string){
global $users;
// Check User Instance
if(strpos($string, "bludit::") === 0){
$username = substr($string, strlen("bludit::"));
if($users->exists($username)){
$user = $users->getUserDB($username);
$user["username"] = $user["nickname"];
return $user;
}
return false;
}
// Check Guest Instance
if(strpos($string, "guest::") === 0){
$uuid = substr($string, strlen("guest::"));
if($this->exists($uuid)){
return $this->db[$uuid];
}
return false;
}
// Return as Anonymous
return array(
"username" => "Anonymous",
"email" => "anonymous@" . $_SERVER["SERVER_NAME"]
);
}
/*
| GET LIST
| @since 0.1.0
|
| @param string The string to be searched or NULL.
| @param int The current comment page number, starting with 1.
| @param int The number of comments to be shown per page.
|
| @return array The respective user keys with an ARRAY or FALSE on failure.
*/
public function getList($search = null, $page = 1, $limit = -1){
if($search !== null){
$list = array();
foreach($this->db AS $uuid => $fields){
if(stripos($fields["username"], $search) === false){
continue;
}
if(stripos($fields["email"], $search) === false){
continue;
}
$list[$uuid] = $fields;
}
} else {
$list = $this->db;
}
// Limit
if($limit == -1){
return $list;
}
// Offset
$offset = $limit * ($page - 1);
$count = min(($offset + $limit - 1), count($list));
if($offset < 0 || $offset > $count){
return false;
}
return array_slice($list, $offset, $limit, true);
}
/*
| MAIN USER HANDLER
| @since 0.1.0
|
| @param string The username as STRING.
| @param string The email address as STRING.
|
| @return multi The (new) UUID on success, FALSE on failure.
*/
public function user($username, $email){
global $security;
// Validate Username
$username = Sanitize::html(strip_tags(trim($username)));
if(empty($username) || strlen($username) > 42){
return false;
}
// Validate eMail Address
$email = strtolower(Sanitize::email($email));
if(empty($email) || Valid::email($email) === false){
return false;
}
// Check User
$uuid = md5($email);
if(array_key_exists($uuid, $this->db)){
return $uuid;
}
// Add User
$this->db[$uuid] = array(
"username" => $username,
"email" => $email,
"hash" => md5($security->getUserIp() . $_SERVER["HTTP_USER_AGENT"]),
"blocked" => false,
"comments" => array()
);
if(!$this->save()){
return false;
}
return $uuid;
}
public function add($username, $email, $meta = array()){
return $this->user($username, $email, $meta);
}
/*
| EDIT USER DATA
| @since 0.1.0
|
| @param string The unique user ID as string (or the user eMail address).
| @param multi The new username (or NULL to keep the existing one).
| @param multi The new eMail address (or NULL to keep the existing one).
| ATTENTION: The new eMail address CANNOT be used already!
| ATTENTION: The new eMail address CHANGES the unique user id (UUID)!
| @param multi TRUE to block the user, FALSE to unblock, null to keep the current.
|
| @return multi The (new) UUID on success, FALSE on failure.
*/
public function edit($uuid, $username = null, $email = null, $blocked = null){
if(Valid::email($uuid) !== false){
$uuid = md5(strtolower(Sanitize::email($uuid)));
}
if(!array_key_exists($uuid, $this->db)){
return false;
}
$data = $this->db[$uuid];
// Change Username
if($username !== null){
$username = Sanitize::html(strip_tags(trim($username)));
if(empty($username) || strlen($username) > 42){
return false;
}
$data["username"] = $username;
}
// Change eMail
if($email !== null){
$email = strtolower(Sanitize::email($uuid));
if(Valid::email($email) === false){
return false;
}
$data["email"] = $email;
$newuuid = md5($email);
}
// Change Blocked
if(is_bool($blocked)){
$data["blocked"] = $blocked;
}
// Update UUID
if(isset($newuuid) && $uuid !== $newuuid){
unset($this->db[$uuid]);
$uuid = $newuuid;
}
// Store new Data
$this->db[$uuid] = $data;
if(!$this->save()){
return false;
}
return $uuid;
}
/*
| ADD COMMENT ID TO USER
| @since 0.1.0
|
| @param string The unique user ID as string (or the user eMail address).
| @param string The unique comment ID as STRING.
|
| @return bool TRUE on success, FALSE on failure.
*/
public function addComment($uuid, $uid){
if(Valid::email($uuid) !== false){
$uuid = md5(strtolower(Sanitize::email($uuid)));
}
if(!array_key_exists($uuid, $this->db)){
return false;
}
// Add Comment UID
$user = $this->db[$uuid];
if(!isset($user["comments"]) || !is_array($user["comments"])){
$user["comments"] = array();
}
if(!in_array($uid, $user["comments"])){
$user["comments"][] = $uid;
}
// Save & Return
$this->db[$uuid] = $user;
if(!$this->save()){
return false;
}
return true;
}
/*
| DELETE COMMENT ID TO USER
| @since 0.1.0
|
| @param string The unique user ID as string (or the user eMail address).
| @param string The unique comment ID as STRING.
|
| @return bool TRUE on success, FALSE on failure.
*/
public function deleteComment($uuid, $uid){
if(Valid::email($uuid) !== false){
$uuid = md5(strtolower(Sanitize::email($uuid)));
}
if(!array_key_exists($uuid, $this->db)){
return false;
}
// Delete Comment UID
$user = $this->db[$uuid];
if(!isset($user["comments"])){
$user["comments"] = array();
}
if(in_array($uid, $user["comments"])){
unset($user["comments"][array_search($uid, $user["comments"])]);
}
// Save & Return
$this->db[$uuid] = $user;
if(!$this->save()){
return false;
}
return true;
}
/*
| DELETE USER
| @since 0.1.0
|
| @param string The unique user ID as string (or the user eMail address).
|
| @return bool TRUE on success, FALSE on failure.
*/
public function delete($uuid){
if(Valid::email($uuid) !== false){
$uuid = md5(strtolower(Sanitize::email($uuid)));
}
if(!array_key_exists($uuid, $this->db)){
return false;
}
// Delete & Return
unset($this->db[$uuid]);
if(!$this->save()){
return false;
}
return true;
}
}

226
system/class.comments-votes.php

@ -0,0 +1,226 @@
<?php
/*
| Snicker The first native FlatFile Comment Plugin 4 Bludit
| @file ./system/class.comments-votes.php
| @author SamBrishes <sam@pytes.net>
| @version 0.1.2 [0.1.0] - Alpha
|
| @website https://github.com/pytesNET/snicker
| @license X11 / MIT License
| @copyright Copyright © 2019 SamBrishes, pytesNET <info@pytes.net>
*/
if(!defined("BLUDIT")){ die("Go directly to Jail. Do not pass Go. Do not collect 200 Cookies!"); }
class CommentsVotes extends dbJSON{
const KEY = "snicker-ratings";
/*
| DATABASE FIELDS
*/
protected $dbFields = array( );
/*
| CONSTRUCTOR
| @since 0.1.0
*/
public function __construct(){
parent::__construct(DB_SNICKER_VOTES);
if(!file_exists(DB_SNICKER_VOTES)){
$this->db = array();
$this->save();
}
}
/*
| HANDLE :: CURRENT USER
| @since 0.1.0
*/
public function currentUser(){
global $login, $security;
if(!is_a($login, "Login")){
$login = new Login();
}
// Get Current User
if($login->isLogged()){
return "bludit::" . $login->username();
}
return "guest::" . md5($security->getUserIp() . $_SERVER["HTTP_USER_AGENT"]);
}
/*
| HANDLE :: HAS VOTED
| @since 0.1.0
*/
public function hasVoted($uid, $vote = null){
$user = $this->currentUser();
$config = sn_config("comment_vote_storage");
// Database Storage
$db = strpos($user, "bludit::") === 0 || $config === "database";
if($db){
if(!array_key_exists($user, $this->db)){
return false;
}
$data = $this->db[$user];
} else {
$store = ($config == "cookie")? "Cookie": "Session";
$data = $store::get(self::KEY);
$data = !empty($data)? @unserialize($data): false;
if(!is_array($data)){
return false;
}
}
// Check Data
if(!array_key_exists($uid, $data)){
return false;
}
return ($vote === null || $data[$uid] === $vote);
}
public function hasLiked($uid){
return $this->hasVoted($uid, "like");
}
public function hasDisliked($uid){
return $this->hasVoted($uid, "dislike");
}
/*
| HANDLE :: ADD NEW COMMENT VOTING
| @since 0.1.0
*/
public function add($uid, $vote = "like"){
$user = $this->currentUser();
$config = sn_config("comment_vote_storage");
// Database Storage
$db = strpos($user, "bludit::") === 0 || $config === "database";
if($db){
if(!array_key_exists($user, $this->db)){
$this->db[$user] = array();
}
if(array_key_exists($uid, $this->db[$user])){
return false;
}
$this->db[$user][$uid] = $vote;
return $this->save() !== false;
}
// Cookie | Session Storage
$store = ($config == "cookie")? "Cookie": "Session";
$data = $store::get(self::KEY);
$data = !empty($data)? @unserialize($data): false;
if(is_array($data)){
if(array_key_exists($uid, $data)){
return false;
}
} else {
$data = array();
}
$data[$uid] = $vote;
$store::set(self::KEY, serialize($data));
return true;
}
/*
| HANDLE :: EDIT COMMENT VOTING
| @since 0.1.0
*/
public function edit($uid, $vote = "like"){
$user = $this->currentUser();
$config = sn_config("comment_vote_storage");
// Database Storage
$db = strpos($user, "bludit::") === 0 || $config === "database";
if($db){
if(!array_key_exists($user, $this->db)){
$this->db[$user] = array();
}
if(array_key_exists($uid, $this->db[$user]) && $this->db[$user][$uid] === $vote){
return false;
}
$this->db[$user][$uid] = $vote;
return $this->save() !== false;
}
// Cookie | Session Storage
$store = ($config == "cookie")? "Cookie": "Session";
$data = $store::get(self::KEY);
$data = !empty($data)? @unserialize($data): false;
if(is_array($data)){
if(array_key_exists($uid, $data) && $data[$uid] === $vote){
return false;
}
} else {
$data = array();
}
$data[$uid] = $vote;
$store::set(self::KEY, serialize($data));
return true;
}
/*
| HANDLE :: DELETE COMMENT VOTING
| @since 0.1.0
*/
public function delete($uid){
$user = $this->currentUser();
$config = sn_config("comment_vote_storage");
// Database Storage
$db = strpos($user, "bludit::") === 0 || $config === "database";
if($db){
if(!array_key_exists($user, $this->db)){
return true;
}
if(!array_key_exists($uid, $this->db[$user])){
return true;
}
unset($this->db[$user][$uid]);
return $this->save() !== false;
}
// Cookie | Session Storage
$store = ($config == "cookie")? "Cookie": "Session";
$data = $store::get(self::KEY);
$data = !empty($data)? @unserialize($data): false;
if(!is_array($data)){
return true;
}
if(!array_key_exists($uid, $data)){
return true;
}
unset($data[$uid]);
$store::set(self::KEY, serialize($data));
return true;
}
/*
| HANDLE :: DELETE BY USER
| @since 0.1.0
*/
public function deleteByUser($user){
$config = sn_config("comment_vote_storage");
// Database Storage
$db = strpos($user, "bludit::") === 0 || $config === "database";
if($db){
if(!array_key_exists($user, $this->db)){
return true;
}
unset($this->db[$user]);
return $this->save() !== false;
}
// Cookie | Session Storage
$store = ($config == "cookie")? "Cookie": "Session";
$data = $store::get(self::KEY);
$data = !empty($data)? @unserialize($data): false;
if(!is_array($data)){
return true;
}
$store::set(self::KEY, serialize(array()));
return true;
}
}

564
system/class.comments.php

@ -0,0 +1,564 @@
<?php
/*
| Snicker The first native FlatFile Comment Plugin 4 Bludit
| @file ./system/class.comments.php
| @author SamBrishes <sam@pytes.net>
| @version 0.1.2 [0.1.0] - Alpha
|
| @website https://github.com/pytesNET/snicker
| @license X11 / MIT License
| @copyright Copyright © 2019 SamBrishes, pytesNET <info@pytes.net>
*/
if(!defined("BLUDIT")){ die("Go directly to Jail. Do not pass Go. Do not collect 200 Cookies!"); }
class Comments extends dbJSON{
/*
| PAGE UUID
*/
protected $uuid;
/*
| DATABASE FIELDS
*/
protected $dbFields = array(
"type" => "comment", // Comment Type ("comment", "reply", "pingback")
"depth" => 1, // Comment Depth (starting with 1)
"title" => "", // Comment Title
"status" => "", // Comment Status ("pending", "approved", "rejected", "spam")
"comment" => "", // Comment Content
"rating" => [0, 0], // Comment Rating
"page_uuid" => "", // Comment Page UUID
"parent_uid" => "", // Comment Parent UID
"author" => "", // Comment Author (bludt::username or guest::uuid)
"subscribe" => false, // eMail Subscription
"date" => "", // Date Comment Written
"dateModified" => "", // Date Comment Modified
"dateAudit" => "", // Date Comment Audit
"custom" => array(), // Custom Data
);
/*
| CONSTRUCTOR
| @since 0.1.0
|
| @param string The UUID of the respective page.
*/
public function __construct($uuid){
global $pages;
// Get Page
if($pages->getByUUID($uuid) === false){
$error = "The Page UUID couldn't be found in the database [{$uuid}]";
Log::set(__METHOD__ . LOG_SEP . $error);
throw new Exception($error);
}
$this->uuid = $uuid;
parent::__construct(DB_SNICKER_COMMENTS . "comments-{$uuid}.php");
}
/*
| HELPER :: FILL LOG FILE
| @since 0.1.0
|
| @param string The respective method for the log (Use __METHOD__)
| @param string The respective error message to be logged.
| @param array Additional values AS array for the `vsprintf` function.
*/
private function log($method, $string, $args){
$strings = array(
"error-comment-uid" => "The comment UID is invalid or does not exist [%s]",
"error-page-uuid" => "The page uuid is invalid or does not exist [%s]",
"error-create-dir" => "The comment directory could not be created [%s]",
"error-create-file" => "The comment file could not be created [%s]",
"error-comment-file" => "The comment file does not exist [%s]",
"error-comment-update" => "The comment file could not be updated [%s]",
"error-comment-remove" => "The comment file could not be deleted [%s]",
"error-update-db" => "The comment database could not be updated"
);
if(array_key_exists($string, $strings)){
$string = $strings[$string];
}
Log::set($method . LOG_SEP . vsprintf("Error occured: {$string}", $args), LOG_TYPE_ERROR);
}
/*
| HELPER :: GENERATE UNIQUE COMMENT ID
| @since 0.1.0
*/
private function generateUID(){
if(function_exists("random_bytes")){
return md5(bin2hex(random_bytes(16)) . time());
} else if(function_exists("openssl_random_pseudo_bytes")){
return md5(bin2hex(openssl_random_pseudo_bytes(16)) . time());
}
return md5(uniqid() . time());
}
/*
| PUBLIC :: GET DEFAULT FIELDS
| @since 0.1.0
|
| @return array An array with all default fields and values per entry.
*/
public function getDefaultFields(){
return $this->dbFields;
}
/*
| DATA :: GET DATABASE
| @since 0.1.0
|
| @param bool TRUE to just return the keys, FALSE to return the complete DB.
|
| @return array The complete database entries (or keys) within an ARRAY.
*/
public function getDB($keys = true){
return ($keys)? array_keys($this->db): $this->db;
}
/*
| DATA :: CHECK IF COMMENT ITEM EXISTS
| @since 0.1.0
|
| @param string The unique comment ID.
|
| @return bool TRUE if the comment ID exists, FALSE if not.
*/
public function exists($uid){
return isset($this->db[$uid]);
}
/*
| DATA :: GET COMMENT ITEM
| @since 0.1.0
|
| @param string The unique comment ID.
|
| @return array The comment data array on success, FALSE on failure.
*/
public function getCommentDB($uid){
return ($this->exists($uid))? $this->db[$uid]: false;
}
/*
| DATA :: LIST COMMENTS
| @since 0.1.0
|
| @param int The current comment page number, starting with 1.
| @param int The number of comments to be shown per page.
| @param multi The desired comment type as STRING, multiple as ARRAY.
| Pass `null` to get each comment type.
| @param multi The desired comment status as STRING, multiple as ARRAY.
| Pass `null` to get each comment status.
|
| @return array The respective database keys with an ARRAY or FALSE on failure.
*/
public function getList($page, $limit, $type = array("comment", "reply"), $status = array("approved")){
$type = is_string($type)? array($type): $type;
if(!is_array($type)){
$type = null;
}
$status = is_string($status)? array($status): $status;
if(!is_array($status)){
$type = null;
}
// Format List
$list = array();
foreach($this->db AS $key => $fields){
if($type !== null && !in_array($fields["type"], $type)){
continue;
}
if($status !== null && !in_array($fields["status"], $status)){
continue;
}
array_push($list, $key);
}
// Limit
if($limit == -1){
return $list;
}
// Offset
$offset = $limit * ($page - 1);
$count = min(($offset + $limit - 1), count($list));
if($offset < 0 || $offset > $count){
return false;
}
return array_slice($list, $offset, $limit, true);
}
/*
| DATA :: GENERATE A DEPTH LIST
| @since 0.1.0
|
| @param int The current comment page number, starting with 1.
| @param int The number of comments to be shown per page.
| @param multi The desired comment type as STRING, multiple as ARRAY.
| Pass `null` to get each comment type.
| @param multi The desired comment status as STRING, multiple as ARRAY.
| Pass `null` to get each comment status.
|
| @return array The respective database keys with an ARRAY or FALSE on failure.
*/
public function getDepthList($page, $limit, $type = array("comment", "reply"), $status = array("approved")){
global $login, $SnickerUsers;
$this->sortBy();
// Validate Parameters
$type = is_string($type)? array($type): $type;
if(!is_array($type)){
$type = null;
}
$status = is_string($status)? array($status): $status;
if(!is_array($status)){
$type = null;
}
// Get User Pending
if(in_array("pending", $status)){
if(!is_a($login, "Login")){
$login = new Login();
}
if($login->isLogged()){
$user = "bludit::" . $login->username();
} else {
if(($user = $SnickerUsers->getCurrent()) !== false){
$user = "guest::" . $user;
}
}
}
// Format List
$list = array();
$children = array();
foreach($this->db AS $key => $fields){
if($type !== null && !in_array($fields["type"], $type)){
continue;
}
if($status !== null && !in_array($fields["status"], $status)){
continue;
}
if($fields["status"] === "pending" && $fields["author"] !== $user){
continue;
}
if(!empty($fields["parent_uid"])){
if(!array_key_exists($fields["parent_uid"], $children)){
$children[$fields["parent_uid"]] = array();
}
array_push($children[$fields["parent_uid"]], $key);
} else {
array_push($list, $key);
}
}
// Offset
$count = 0;
$offset = $limit * ($page - 1);
for(; $count < $offset ;){
$key = array_shift($list);
$count++;
if(array_key_exists($key, $children)){
$count += count($children[$key]);
unset($children[$key]);
}
}
// Generator
$count = 0;
foreach($list AS $key){
if($count >= $limit){
break;
}
$count++;
yield $key;
if(!array_key_exists($key, $children)){
continue;
}
$loop = $key;
$depth = array();
while(true){
if(empty($depth) && empty($children[$key])){
break;
}
if(array_key_exists($loop, $children) && count($children[$loop]) > 0){
array_push($depth, $loop);
$loop = array_shift($children[$loop]);
$count++;
yield $loop;
} else {
$loop = array_pop($depth);
continue;
}
}
}
}
/*
| DATA :: COUNT COMMENTS
| @since 0.1.0
|
| @param multi The desired comment type as STRING, multiple as ARRAY.
| Pass `null` to get each comment type.
|
| @return int The total number of comments.
*/
public function count($type = array("comment", "reply")){
$type = is_string($type)? array($type): $type;
if(!is_array($type)){
$type = null;
}
// Count All
if($type === null){
return count($this->db);
}
// Count
$count = 0;
foreach($this->db AS $key => $fields){
if(!in_array($fields["type"], $type)){
continue;
}
$count++;
}
return $count;
}
/*
| HANDLE :: ADD A NEW COMMENT
| @since 0.1.0
|
| @param array The respective comment array.
|
| @return multi The comment UID on success, FALSE on failure.
*/
public function add($args){
global $SnickerIndex, $SnickerUsers;
// Loop Default Fields
$row = array();
foreach($this->dbFields AS $field => $value){
if(isset($args[$field])){
$final = $args[$field];
} else {
$final = $value;
}
settype($final, gettype($value));
$row[$field] = $final;
}
// Create (U)UIDs
$uid = $this->generateUID();
$row["page_uuid"] = $this->uuid;
// Validate Parent UID
if(!empty($row["parent_uid"]) && !$this->exists($row["parent_uid"])){
$row["parent_uid"] = null;
}
// Validate Type and Depth
if(!empty($row["parent_uid"])){
$row["type"] = "reply";
$row["depth"] = $this->db[$row["parent_uid"]]["depth"] + 1;
} else {
$row["type"] = "comment";
$row["depth"] = 1;
}
// Validata Status
if(!in_array($row["status"], array("pending", "approved", "rejected", "spam"))){
$row["status"] = "pending";
}
// Sanitize Strings
$row["title"] = Sanitize::html(strip_tags($row["title"]));
$row["author"] = Sanitize::html($row["author"]);
// Sanitize Comment
$allowed = "<a><b><strong><i><em><u><del><ins><s><strike><p><br><br/><br />";
$allowed .= "<mark><abbr><acronym><dfn><ul><ol><li><dl><dt><dd><hr><hr/><hr />";
if(sn_config("comment_markup_html")){
$row["comment"] = strip_tags($row["comment"], $allowed);
} else {
$row["comment"] = strip_tags($row["comment"]);
}
$row["comment"] = Sanitize::html($row["comment"]);
// Validate Comment
$limit = sn_config("comment_limit");
if($limit > 0 && strlen($row["comment"]) > $limit){
$row["comment"] = substr($row["comment"], 0, $limit);
}
// Set Static Data
$row["rating"] = array(0, 0);
$row["subscribe"] = $row["subscribe"] === true;
$row["date"] = Date::current(DB_DATE_FORMAT);
$row["dateModified"] = "";
if($row["status"] !== "pending"){
$row["dateAudit"] = Date::current(DB_DATE_FORMAT);
}
// Add Index
if(!is_a($SnickerIndex, "CommentsIndex")){
$SnickerIndex = new CommentsIndex();
}
if(!$SnickerIndex->add($uid, $row)){
return false;
}
if(strpos($row["author"], "guest::") === 0){
$SnickerUsers->addComment(substr($row["author"], strlen("guest::")), $uid);
}
// Insert Comment
$this->db[$uid] = $row;
$this->sortBy();
if($this->save() !== true){
Log::set(__METHOD__, "error-update-db");
return false;
}
return $uid;
}
/*
| HANDLE :: EDIT AN EXISTING COMMENT
| @since 0.1.0
|
| @param string The unique comment ID as STRING.
| @param array The respective comment data, whcih you want to update.
|
| @return multi The comment UID on success, FALSE on failure.
*/
public function edit($uid, $args){
global $SnickerIndex;
// Loop Default Fields
$row = array();
foreach($this->dbFields AS $field => $value){
if(isset($args[$field])){
$final = is_string($args[$field])? Sanitize::html($args[$field]): $args[$field];
} else {
$final = $this->db[$uid][$field];
}
settype($final, gettype($value));
$row[$field] = $final;
}
// Create / Check (U)UIDs
if(!$this->exists($uid)){
$this->log(__METHOD__, "error-comment-uid", array($uid));
return false;
}
$row["page_uuid"] = $this->uuid;
// Validate Parent UID
if(!empty($row["parent_uid"]) && !$this->exists($row["parent_uid"])){
$row["parent_uid"] = $data["parent_uid"];
}
// Validate Type and Depth
if(!empty($row["parent_uid"])){
$row["type"] = "reply";
$row["depth"] = $this->db[$row["parent_uid"]]["depth"] + 1;
} else {
$row["type"] = "comment";
$row["depth"] = 1;
}
// Validata Status
if(!in_array($row["status"], array("pending", "approved", "rejected", "spam"))){
$row["status"] = $this->db[$uid]["status"];
}
// Sanitize Strings
$row["title"] = Sanitize::html($row["title"]);
$row["comment"] = Sanitize::html($row["comment"]);
$row["author"] = Sanitize::html($row["author"]);
// Set Static Data
$row["subscribe"] = $row["subscribe"] === true;
$row["dateModified"] = Date::current(DB_DATE_FORMAT);
if($row["status"] !== $this->db[$uid]["status"]){
$row["dateAudit"] = Date::current(DB_DATE_FORMAT);
}
// Update Index
if(!$SnickerIndex->edit($uid, $row)){
return false;
}
// Update and Return
$this->db[$uid] = $row;
$this->sortBy();
if($this->save() !== true){
Log::set(__METHOD__, "error-update-db");
return false;
}
return $uid;
}
/*
| HANDLE :: DELETE AN EXISTING COMMENT
| @since 0.1.0
|
| @param array The respective comment UID to delete.
|
| @return bool TRUE on success, FALSE on failure.
*/
public function delete($uid){
global $SnickerIndex, $SnickerUsers;
if(!isset($this->db[$uid])){
return false;
}
$row = $this->db[$uid];
// Remove Index
if(!$SnickerIndex->delete($uid)){
return false;
}
if(strpos($row["author"], "guest::") === 0){
$SnickerUsers->deleteComment(substr($row["author"], strlen("guest::")), $uid);
}
// Remove Database Item
unset($this->db[$uid]);
if($this->save() !== true){
Log::set(__METHOD__, "error-update-db");
return false;
}
return true;
}
/*
| INTERNAL :: SORT COMMENTS
| @since 0.1.0
|
| @return bool TRUE
*/
public function sortBy(){
global $SnickerPlugin;
if($SnickerPlugin->getValue("frontend_order") === "date_asc"){
uasort($this->db, function($a, $b){
return $a["date"] > $b["date"];
});
} else if($SnickerPlugin->getValue("frontend_order") === "date_desc"){
uasort($this->db, function($a, $b){
return $a["date"] < $b["date"];
});
}
return true;
}
}

1045
system/class.snicker.php

File diff suppressed because it is too large

90
system/functions.php

@ -0,0 +1,90 @@
<?php
/*
| Snicker The first native FlatFile Comment Plugin 4 Bludit
| @file ./system/functions.php
| @author SamBrishes <sam@pytes.net>
| @version 0.1.2 [0.1.0] - Alpha
|
| @website https://github.com/pytesNET/snicker
| @license X11 / MIT License
| @copyright Copyright © 2019 SamBrishes, pytesNET <info@pytes.net>
*/
if(!defined("BLUDIT")){ die("Go directly to Jail. Do not pass Go. Do not collect 200 Cookies!"); }
/*
| S18N :: FORMAT AND GET STRING
| @since 0.1.0
|
| @param string The respective string to translate.
| @param array Some additional array for `printf()`.
|
| @return string The translated and formated string.
*/
function sn__($string, $args = array()){
global $L;
$hash = "s18n-" . md5(strtolower($string));
$value = $L->g($hash);
if($hash === $value){
$value = $string;
}
return (count($args) > 0)? vsprintf($value, $args): $value;
}
/*
| S18N :: FORMAT AND PRINT STRING
| @since 0.1.0
|
| @param string The respective string to translate.
| @param array Some additional array for `printf()`.
|
| @return <print>
*/
function sn_e($string, $args = array()){
print(sn__($string, $args));
}
/*
| SHORTFUNC :: GET VALUE
| @since 0.1.0
|
| @param string The respective Snicker configuration key.
|
| @return multi The respective value or FALSE if the option doens't exist.
*/
function sn_config($key){
global $SnickerPlugin;
return $SnickerPlugin->getValue($key);
}
/*
| SHORTFUNC :: RESPONSE
| @since 0.1.0
|
| @return die();
*/
function sn_response($data, $key = null){
global $SnickerPlugin;
return $SnickerPlugin->response($data, $key);
}
/*
| SHORTFUNC :: SELECTED
| @since 0.1.0
|
| @return die();
*/
function sn_selected($field, $value = true, $print = true){
global $SnickerPlugin;
return $SnickerPlugin->selected($field, $value, $print);
}
/*
| SHORTFUNC :: CHECKED
| @since 0.1.0
|
| @return die();
*/
function sn_checked($field, $value = true, $print = true){
global $SnickerPlugin;
return $SnickerPlugin->checked($field, $value, $print);
}

745
themes/default/snicker.css

@ -0,0 +1,745 @@
@charset "UTF-8";
/*
| Snicker The first native FlatFile Comment Plugin 4 Bludit
| @file ./system/themes/default/snicker.css
| @author SamBrishes <sam@pytes.net>
| @version 0.1.2 [0.1.0] - Alpha
|
| @website https://github.com/pytesNET/snicker
| @license X11 / MIT License
| @copyright Copyright © 2019 SamBrishes, pytesNET <info@pytes.net>
*/
/* @start ANIMATIONs */
@keyframes spin{
0% {
transform: rotate(0deg);
-o-transform: rotate(0deg);
-ms-transform: rotate(0deg);
-moz-transform: rotate(0deg);
-webkit-transform: rotate(0deg);
}
0% {
transform: rotate(-359deg);
-o-transform: rotate(-359deg);
-ms-transform: rotate(-359deg);
-moz-transform: rotate(-359deg);
-webkit-transform: rotate(-359deg);
}
}
@-moz-keyframes spin{
0% {
transform: rotate(0deg);
-o-transform: rotate(0deg);
-ms-transform: rotate(0deg);
-moz-transform: rotate(0deg);
-webkit-transform: rotate(0deg);
}
0% {
transform: rotate(-359deg);
-o-transform: rotate(-359deg);
-ms-transform: rotate(-359deg);
-moz-transform: rotate(-359deg);
-webkit-transform: rotate(-359deg);
}
}
@-webkit-keyframes spin{
0% {
transform: rotate(0deg);
-o-transform: rotate(0deg);
-ms-transform: rotate(0deg);
-moz-transform: rotate(0deg);
-webkit-transform: rotate(0deg);
}
0% {
transform: rotate(-359deg);
-o-transform: rotate(-359deg);
-ms-transform: rotate(-359deg);
-moz-transform: rotate(-359deg);
-webkit-transform: rotate(-359deg);
}
}
/* @end ANIMATIONs */
/* @start GENERAL */
.snicker-comments,
.snicker-comments *,
.snicker-comments *::before,
.snicker-comments *::after{
box-sizing: border-box;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
}
.snicker-comments{
width: 100%;
margin: 15px 0 30px 0;
padding: 0;
display: block;
font-size: 14px;
line-height: 1.5em;
}
.snicker-comments input,
.snicker-comments textarea,
.snicker-comments button{
font-family: "Segoe UI", "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.snicker-comments input[type="text"],
.snicker-comments input[type="email"],
.snicker-comments textarea{
color: #404448;
width: 100%;
max-width: none;
height: auto;
margin: 0;
padding: 7px 14px;
z-index: 10;
display: block;
font-size: 14px;
text-align: left;
line-height: 1.5em;
border-width: 1px;
border-style: solid;
border-color: #a0a4a8;
background-color: #ffffff;
border-radius: 3px;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
}
.snicker-comments textarea{
height: 150px;
min-height: 150px;
padding: 15px;
resize: vertical;
}
.snicker-comments input[type="text"]:hover,
.snicker-comments input[type="email"]:hover,
.snicker-comments textarea:hover{
color: #343A40;
border-color: #343A40;
background-color: #ffffff;
}
.snicker-comments input[type="text"]:focus,
.snicker-comments input[type="email"]:focus,
.snicker-comments textarea:focus{
color: #343A40;
border-color: #343A40;
background-color: #ffffff;
box-shadow: 0 0 0 3px rgba(52,58,64, 0.35);
-moz-box-shadow: 0 0 0 3px rgba(52,58,64, 0.35);
-webkit-box-shadow: 0 0 0 3px rgba(52,58,64, 0.35);
}
.snicker-comments input[type="checkbox"]{
display: none;
}
.snicker-comments input[type="checkbox"]+label{
color: #606468;
cursor: pointer;
height: auto;
margin: 10px 5px;
padding: 0;
display: inline-block;
font-size: 12px;
line-height: 22px;
vertical-align: top;
}
.snicker-comments input[type="checkbox"]+label:hover,
.snicker-comments input[type="checkbox"]:checked+label{
color: #404448;
}
.snicker-comments input[type="checkbox"]+label:before{
width: 20px;
height: 20px;
content: "";
margin: 0 9px 0 0;
display: inline-block;
position: relative;
vertical-align: top;
background-color: #ffffff;
border: 1px solid #a0a4a8;
background-repeat: no-repeat;
background-position: center center;
border-radius: 3px;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
transition: all 142ms linear;
-moz-transition: all 142ms linear;
-webkit-transition: all 142ms linear;
}
.snicker-comments input[type="checkbox"]+label:hover:before{
border-color: #343A40;
}
.snicker-comments input[type="checkbox"]:checked+label:before{
background-color: #343A40;
border-color: #343A40;
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC\
9zdmciIHdpZHRoPSIxMiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDEyIDE2Ij48cGF0aCBmaWxsPSIjZmZmIiBkPSJNM\
TIgNWwtOCA4LTQtNCAxLjUtMS41TDQgMTBsNi41LTYuNUwxMiA1eiIvPjwvc3ZnPg==");
}
.snicker-comments button{
color: rgba(255, 255, 255, 0.9);
cursor: pointer;
width: auto;
margin: 0;
padding: 10px 15px;
display: inline-block;
position: relative;
font-size: 14px;
text-align: left;
line-height: 1.5em;
vertical-align: top;
background-color: #343A40;
border: 1px solid #343A40;
border-radius: 3px;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
transition: background 142ms linear;
-moz-transition: background 142ms linear;
-webkit-transition: background 142ms linear;
}
.snicker-comments button:hover{
background-color: rgb(14, 16, 19);
}
.snicker-comments button:active{
background-color: #242A30;
}
.snicker-comments button:disabled,
.snicker-comments button.disabled{
color: rgba(255, 255, 255, 0.35);
cursor: not-allowed;
background-color: #040A10;
}
.snicker-comments button.loading:before{
top: 8px;
left: -36px;
width: 28px;
height: 28px;
margin: 0 auto;
padding: 0;
content: "";
display: inline-block;
position: absolute;
background-size: 18px;
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC\
9zdmciIHdpZHRoPSIxMiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDEyIDE2Ij48cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZ\
GQiIGQ9Ik0xMC4yNCA3LjRhNC4xNSA0LjE1IDAgMCAxLTEuMiAzLjYgNC4zNDYgNC4zNDYgMCAwIDEtNS40MS41NEw0Ljgg\
MTAuNC41IDkuOGwuNiA0LjIgMS4zMS0xLjI2YzIuMzYgMS43NCA1LjcgMS41NyA3Ljg0LS41NGE1Ljg3NiA1Ljg3NiAwIDA\
gMCAxLjc0LTQuNDZsLTEuNzUtLjM0ek0yLjk2IDVhNC4zNDYgNC4zNDYgMCAwIDEgNS40MS0uNTRMNy4yIDUuNmw0LjMuNi\
0uNi00LjItMS4zMSAxLjI2Yy0yLjM2LTEuNzQtNS43LTEuNTctNy44NS41NEMuNSA1LjAzLS4wNiA2LjY1LjAxIDguMjZsM\
S43NS4zNUE0LjE3IDQuMTcgMCAwIDEgMi45NiA1eiIvPjwvc3ZnPg==");
background-repeat: no-repeat;
background-position: center center;
animation: spin 2s linear 0ms infinite;
-moz-animation: spin 2s linear 0ms infinite;
-webkit-animation: spin 2s linear 0ms infinite;
}
.snicker-comments .table{
width: 100%;
margin: 0;
padding: 0;
display: table;
border-spacing: 0;
border-collapse: collapse;
}
.snicker-comments .table .table-cell{
display: table-cell;
padding: 5px 10px;
vertical-align: top;
}
.snicker-comments .align-left{
text-align: left;
}
.snicker-comments .align-right{
text-align: right;
}
.snicker-comments .align-center{
text-align: center;
}
/* Main Elements */
.snicker-comments .snicker-comments-form,
.snicker-comments .snicker-comments-list{
width: auto;
margin: 0;
padding: 0;
display: block;
border: 1px solid#c0c4c8;
border-radius: 5px;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
}
.snicker-comments .snicker-comments-list{
border-width: 0;
}
.snicker-comments .no-comments,
.snicker-comments .disabled-comments{
width: 100%;
margin: 15px 0;
padding: 15px 0;
display: block;
font-size: 16px;
text-align: center;
font-style: italic;
line-height: 50px;
}
.snicker-comments .no-comments{
background-color: #f4f8fa;
border: 1px solid #c0c4c8;
border-radius: 5px;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
}
.snicker-comments .comment-alert{
width: 100%;
margin: 0 0 15px 0;
padding: 10px 15px;
display: block;
font-size: 85%;
font-family: "Segoe UI", "Helvetica Neue", Helvetica, Arial, sans-serif;
background-color: #f4f8fa;
border-radius: 3px;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
}
.snicker-comments .comment-alert.alert-error{
color: #ffffff;
background-color: #DC3545;
}
.snicker-comments .comment-alert.alert-success{
color: #ffffff;
background-color: #28A745;
}
/* @end GENERAL */
/* @start COMMENT FORM */
form.comment-form{
width: 100%;
margin: 0;
padding: 0;
display: block;
}
form.comment-form .comment-header,
form.comment-form .comment-article,
form.comment-form .comment-footer{
width: 100%;
height: auto;
margin: 0;
padding: 10px;
display: block;
}
form.comment-form .comment-header{
font-size: 16px;
background-color: #f4f6f8;
border-bottom: 1px solid #d0d4d8;
border-radius: 5px 5px 0 0;
-moz-border-radius: 5px 5px 0 0;
-webkit-border-radius: 5px 5px 0 0;
}
form.comment-form .comment-header .inner{
padding: 7px 10px;
}
form.comment-form .comment-article{
padding: 15px 20px;
}
form.comment-form .comment-footer{
background-color: #f4f6f8;
border-top: 1px solid #d0d4d8;
border-radius: 0 0 5px 5px;
-moz-border-radius: 0 0 5px 5px;
-webkit-border-radius: 0 0 5px 5px;
}
form.comment-form .comment-captcha{
display: block;
text-align: right;
}
form.comment-form .comment-captcha input,
form.comment-form .comment-captcha input:hover,
form.comment-form .comment-captcha input:focus{
width: 100px;
height: 40px;
padding: 5px 10px;
display: inline-block;
font-size: 16px;
text-align: center;
line-height: 22px;
vertical-align: top;
border: 0;
background-color: #e0e4e8;
box-shadow: none;
-moz-box-shadow: none;
-webkit-box-shadow: none;
}
form.comment-form .comment-captcha input:hover{
background-color: #f0f4f8;
}
form.comment-form .comment-captcha input:focus{
background-color: #e0e4e8;
}
form.comment-form .comment-captcha a{
width: auto;
height: 40px;
display: inline-block;
position: relative;
}
form.comment-form .comment-captcha a:before{
top: 0;
left: 0;
right: 0;
bottom: 0;
content: "";
z-index: 20;
display: inline-block;
position: absolute;
background-color: rgba(0, 0, 0, 0.0);
border-radius: 3px;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
transition: background 142ms linear;
-moz-transition: background 142ms linear;
-webkit-transition: background 142ms linear;
}
form.comment-form .comment-captcha a.reload:before{
background-color: rgba(0, 0, 0, 0.5);
}
form.comment-form .comment-captcha a:after{
top: 50%;
left: 50%;
width: 24px;
height: 24px;
margin: -12px 0 0 -12px;
padding: 2px;
content: "";
z-index: 25;
opacity: 0;
display: inline-block;
position: absolute;
background-size: 20px;
background-color: #000;
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC\
9zdmciIHdpZHRoPSIxMiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDEyIDE2Ij48cGF0aCBmaWxsPSIjZmZmIiBkPSJNM\
TAuMjQgNy40YTQuMTUgNC4xNSAwIDAgMS0xLjIgMy42IDQuMzQ2IDQuMzQ2IDAgMCAxLTUuNDEuNTRMNC44IDEwLjQuNSA5\
LjhsLjYgNC4yIDEuMzEtMS4yNmMyLjM2IDEuNzQgNS43IDEuNTcgNy44NC0uNTRhNS44NzYgNS44NzYgMCAwIDAgMS43NC0\
0LjQ2bC0xLjc1LS4zNHpNMi45NiA1YTQuMzQ2IDQuMzQ2IDAgMCAxIDUuNDEtLjU0TDcuMiA1LjZsNC4zLjYtLjYtNC4yLT\
EuMzEgMS4yNmMtMi4zNi0xLjc0LTUuNy0xLjU3LTcuODUuNTRDLjUgNS4wMy0uMDYgNi42NS4wMSA4LjI2bDEuNzUuMzVBN\
C4xNyA0LjE3IDAgMCAxIDIuOTYgNXoiLz48L3N2Zz4=");
background-repeat: no-repeat;
background-position: center;
border-radius: 50%;
-moz-border-radius: 50%;
-webkit-border-radius: 50%;
transition: opacity 142ms linear;
-moz-transition: opacity 142ms linear;
-webkit-transition: opacity 142ms linear;
}
form.comment-form .comment-captcha a.reload:after{
opacity: 1;
animation: spin 2s linear 0ms infinite;
-moz-animation: spin 2s linear 0ms infinite;
-webkit-animation: spin 2s linear 0ms infinite;
}
form.comment-form .comment-captcha img{
max-width: none;
display: inline-block;
vertical-align: top;
border-radius: 3px;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
}
form.comment-form .comment-reply{
margin: 20px 0 0 0;
padding: 10px 15px;
display: block;
position: relative;
font-size: 80%;
background-color: #f4f8fa;
border-width: 1px 1px 0 1px;
border-style: solid;
border-color: #c0c4c8;
border-radius: 3px 3px 0 0;
-moz-border-radius: 3px 3px 0 0;
-webkit-border-radius: 3px 3px 0 0;
}
form.comment-form .comment-reply + p textarea{
border-top-left-radius: 0;
border-top-right-radius: 0;
-moz-border-raidus-topleft: 0;
-moz-border-raidus-topright: 0;
-webkit-border-top-left-radius: 0;
-webkit-border-top-right-radius: 0;
}
form.comment-form .comment-reply .reply-cancel{
top: 10px;
right: 10px;
width: 22px;
height: 22px;
margin: 0;
padding: 0;
opacity: 0.5;
display: inline-block;
position: absolute;
background-size: 14px;
background-color: #555;
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC\
9zdmciIHdpZHRoPSIxMiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDEyIDE2Ij48cGF0aCBmaWxsPSIjZmZmZmZmIiBkP\
SJNNy40OCA4bDMuNzUgMy43NS0xLjQ4IDEuNDhMNiA5LjQ4bC0zLjc1IDMuNzUtMS40OC0xLjQ4TDQuNTIgOCAuNzcgNC4y\
NWwxLjQ4LTEuNDhMNiA2LjUybDMuNzUtMy43NSAxLjQ4IDEuNDhMNy40OCA4eiIvPjwvc3ZnPg==");
background-repeat: no-repeat;
background-position: center center;
border-radius: 3px;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
transition: all 142ms linear;
-moz-transition: all 142ms linear;
-webkit-transition: all 142ms linear;
}
form.comment-form .comment-reply .reply-cancel:hover{
opacity: 1;
background-color: #DC3545;
}
form.comment-form .comment-reply .reply-title{
margin: 0 0 5px 0;
display: block;
font-weight: bold;
font-weight: 600;
}
/* @end COMMENT FORM */
/* @start COMMENT LIST */
.snicker-comments-list .comment{
width: auto;
margin: 15px 0;
padding: 0;
display: block;
border: 1px solid #c0c4c8;
border-radius: 5px;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
}
.snicker-comments-list .comment.new-comment{
border-color: #007bff;
}
.snicker-comments-list .comment .comment-avatar{
width: 110px;
padding: 15px 10px;
position: relative;
}
.snicker-comments-list .comment .comment-avatar img{
border-radius: 5px;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
}
.snicker-comments-list .comment .comment-avatar .avatar-role{
top: 15px;
right: 10px;
color: #ffffff;
width: auto;
margin: 0;
padding: 3px 5px;
display: inline-block;
position: absolute;
font-size: 11px;
font-family: "Segoe UI", "Helvetica Neue", Helvetica, Arial, sans-serif;
line-height: 16px;
font-weight: bold;
text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.25);
background-color: #007bff;
border-radius: 0 4px 0 3px;
-moz-border-radius: 0 4px 0 3px;
-webkit-border-radius: 0 4px 0 3px;
}
.snicker-comments-list .comment .comment-content{
padding: 10px;
}
.snicker-comments-list .comment .comment-content .comment-title{
color: #707478;
margin: 0;
padding: 5px 5px 2px 5px;
display: block;
font-size: 16px;
line-height: 24px;
font-weight: bold;
font-weight: 600;
}
.snicker-comments-list .comment .comment-content .comment-moderation{
color: #fff;
margin-left: 10px;
padding: 2px 6px;
display: inline-block;
font-size: 12px;
line-height: 16px;
font-weight: normal;
font-family: "Segoe UI", "Helvetica Neue", Helvetica, Arial, sans-serif;
vertical-align: top;
background-color: #dc3545;
border-radius: 3px;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
}
.snicker-comments-list .comment .comment-content .comment-meta{
color: #707478;
margin: 0;
padding: 1px 6px;
display: inline-block;
font-size: 12px;
line-height: 1.25em;
background-color: #e0e4e8;
}
.snicker-comments-list .comment .comment-content .comment-comment{
color: #303438;
margin: 0;
padding: 10px;
display: block;
}
.snicker-comments-list .comment .comment-action{
margin: 0;
padding: 0;
display: block;
background-color: #f4f6f8;
border-top: 1px solid #d0d4d8;
border-radius: 0 0 4px 4px;
-moz-border-radius: 0 0 4px 4px;
-webkit-border-radius: 0 0 4px 4px;
}
.snicker-comments-list .comment .comment-action a{
color: #606468;
margin: 2px 0;
padding: 2px 7px;
opacity: 0.65;
display: inline-block;
font-size: 12px;
font-family: "Segoe UI", "Helvetica Neue", Helvetica, Arial, sans-serif;
line-height: 18px;
background-color: #ffffff;
border: 1px solid #c0c4c8;
border-radius: 3px;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
transition: color 142ms linear, background 142ms linear, border 142ms linear;
-moz-transition: color 142ms linear, background 142ms linear, border 142ms linear;
-webkit-transition: color 142ms linear, background 142ms linear, border 142ms linear;
}
.snicker-comments-list .comment .comment-action a.action-like{
color: #28a745;
border-color: #28a745;
}
.snicker-comments-list .comment .comment-action a.action-dislike{
color: #dc3545;
border-color: #dc3545;
}
.snicker-comments-list .comment .comment-action a.action-reply{
color: #007bff;
border-color: #007bff;
}
.snicker-comments-list .comment .comment-action a:hover,
.snicker-comments-list .comment .comment-action a.active{
color: #ffffff;
opacity: 0.9;
text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.25);
}
.snicker-comments-list .comment .comment-action a.action-like:hover,
.snicker-comments-list .comment .comment-action a.action-like.active{
background-color: #28a745;
}
.snicker-comments-list .comment .comment-action a.action-dislike:hover,
.snicker-comments-list .comment .comment-action a.action-dislike.active{
background-color: #dc3545;
}
.snicker-comments-list .comment .comment-action a.action-reply:hover,
.snicker-comments-list .comment .comment-action a.action-reply.active{
background-color: #007bff;
}
.snicker-comments-list .comment .comment-action a span{
margin: 0 3px 0 2px;
display: inline-block;
}
/* Pagination */
.snicker-comments-list .pagination{
width: 100%;
margin: 10px 0;
padding: 0;
display: block;
}
.snicker-comments-list .pagination .pagination-button{
color: #ffffff;
width: auto;
min-width: 30px;
height: 32px;
margin: 0 3px;
padding: 5px 10px;
display: inline-block;
position: static;
font-size: 14px;
text-align: center;
line-height: 22px;
vertical-align: top;
background-color: #007bff;
border-radius: 3px;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
transition: background 142ms linear;
-moz-transition: background 142ms linear;
-webkit-transition: background 142ms linear;
}
.snicker-comments-list .pagination .pagination-button:hover,
.snicker-comments-list .pagination .pagination-button.hover{
background-color: #0062cc;
}
.snicker-comments-list .pagination .pagination-button:active,
.snicker-comments-list .pagination .pagination-button.active{
background-color: #004999;
}
.snicker-comments-list .pagination .pagination-button:disabled,
.snicker-comments-list .pagination .pagination-button.disabled{
color: rgba(255, 255, 255, 0.5);
cursor: not-allowed;
opacity: 0.5;
background-color: #555;
}
.snicker-comments-list .pagination.pagination-top .pagination-button.button-next{
float: right;
}
.snicker-comments-list .pagination.pagination-bottom{
text-align: center;
}
.snicker-comments-list .pagination.pagination-bottom:after{
clear: both;
content: "";
display: table;
}
.snicker-comments-list .pagination.pagination-bottom .pagination-inner{
margin: 0 auto;
display: inline-block;
}
.snicker-comments-list .pagination.pagination-bottom .pagination-button{
float: left;
margin: 0;
border-radius: 0;
-moz-border-radius: 0;
-webkit-border-radius: 0;
}
.snicker-comments-list .pagination.pagination-bottom .pagination-button.button-first,
.snicker-comments-list .pagination.pagination-bottom .pagination-button.button-previous,
.snicker-comments-list .pagination.pagination-bottom .pagination-button.button-next,
.snicker-comments-list .pagination.pagination-bottom .pagination-button.button-last{
font-size: 20px;
line-height: 18px;
}
.snicker-comments-list .pagination.pagination-bottom .pagination-button:first-child{
border-top-left-radius: 3px;
border-bottom-left-radius: 3px;
-moz-border-radius-topleft: 3px;
-moz-border-radius-bottomleft: 3px;
-webkit-border-top-left-radius: 3px;
-webkit-border-bottom-left-radius: 3px;
}
.snicker-comments-list .pagination.pagination-bottom .pagination-button:last-child{
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
-moz-border-radius-right: 3px;
-moz-border-radius-right: 3px;
-webkit-border-top-right-radius: 3px;
-webkit-border-bottom-right-radius: 3px;
}
/* @end COMMENT LIST */

281
themes/default/snicker.js

@ -0,0 +1,281 @@
/*
| Snicker The first native FlatFile Comment Plugin 4 Bludit
| @file ./system/themes/default/snicker.js
| @author SamBrishes <sam@pytes.net>
| @version 0.1.2 [0.1.0] - Alpha
|
| @website https://github.com/pytesNET/snicker
| @license X11 / MIT License
| @copyright Copyright © 2019 SamBrishes, pytesNET <info@pytes.net>
*/
;(function(root){
"use strict";
var w = root, d = root.document;
/*
| AJAX HELPER
*/
function ajax(url, type, data, callback, self){
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function(){
if(this.readyState == 4){
callback.call((self? self: this), this.responseText, this);
}
};
xhttp.open(type, url, true);
xhttp.setRequestHeader("Cache-Control", "no-cache");
xhttp.setRequestHeader("X-Requested-With", "XMLHttpRequest");
if(type == "POST"){
if(!(data instanceof FormData)){
xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
}
xhttp.send(data);
} else {
xhttp.send();
}
}
/*
| ALERT HELPER
*/
function showAlert(type, message, parent){
var alert = d.createElement("DIV");
alert.className = "comment-alert alert-" + type;
alert.innerText = message;
// Remove Existing Status
var alerts = parent.querySelectorAll(".comment-alert");
if(alerts.length > 0){
Array.prototype.forEach.call(alerts, function(item){
item.parentElement.removeChild(item);
});
}
// Add Status
if(parent.children.length > 0){
parent.insertBefore(alert, parent.children[0]);
} else {
parent.appendChild(alert);
}
}
// Ready?
d.addEventListener("DOMContentLoaded", function(){
"use strict";
// Main Elements
var form = d.querySelector("form.comment-form"),
list = d.querySelector(".snicker-comments-list"),
captcha = d.querySelector("a[data-captcha='reload']");
/*
| HANDLE COMMENT FORM
*/
if(form){
form.addEventListener("submit", function(event){
event.preventDefault();
if(typeof(FormData) !== "function" || !SNICKER_AJAX){
return true;
}
var data = new FormData(this), self = this;
// Check Button
var btn = this.querySelector("[name='snicker']");
if(btn.disabled){
return true;
}
event.preventDefault();
// AJAX Call
btn.disabled = true;
btn.classList.add("loading");
data.append("snicker", btn.value);
ajax(SNICKER_PATH, "POST", data, function(json){
var data = JSON.parse(json);
// Add Comment
if(list && data.status == "success" && "comment" in data){
if(list.querySelector(".comment")){
list.querySelector(".comment").insertAdjacentHTML("beforebegin", data.comment);
} else {
list.insertAdjacentHTML("afterbegin", data.comment);
}
list.querySelector(".comment").classList.add("new-comment");
list.querySelector(".comment").scrollIntoView({ behavior: "smooth", block: "center" });
// Empty Form
var field = self.querySelectorAll("#comment-title,#comment-text");
for(var i = 0, l = field.length; i < l; i++){
if(field[i].tagName == "SELECT"){
field[i].options[0].selected = true;
} else if(field[i].getAttribute("type") === "checkbox"){
field[i].checked = false;
} else {
field[i].value = "";
}
}
showAlert("success", data.success, form.querySelector(".comment-article"));
} else if(data.status === "error"){
showAlert("error", data.error, form.querySelector(".comment-article"));
if(captcha && "captcha" in data){
d.querySelector("input[name='comment[captcha]']").value = "";
captcha.querySelector("img").src = data.captcha;
}
}
// Re-Enable Button
btn.disabled = false;
btn.classList.remove("loading");
});
});
}
/*
| HANDLE CAPTCHA RELOAD
*/
if(captcha){
captcha.addEventListener("click", function(event){
if(!SNICKER_AJAX){
return false;
}
event.preventDefault();
captcha.classList.add("reload");
var data = "action=snicker&snicker=captcha&tokenCSRF=";
var token = d.querySelector("input[name='tokenCSRF']").value;
ajax(SNICKER_PATH, "POST", data + token, function(json){
var data = JSON.parse(json);
if(data.status !== "success"){
window.location.replace(captcha.getAttribute("href"));
}
captcha.querySelector("img").src = data.captcha;
captcha.classList.remove("reload");
});
})
}
/*
| HANDLE COMMENT REPLY
*/
if(list){
list.addEventListener("click", function(event){
if(event.target.tagName != "A" || !form){
return true;
}
// Check Link
var href = event.target.getAttribute("href");
if(href.indexOf("snicker=reply") < 0){
return true;
}
// Handle Reply
event.preventDefault();
var comment = (function getComment(element){
var parent = element.parentElement;
return (parent.classList.contains("comment"))? parent: getComment(parent);
})(event.target);
// Create Elements
var reply = d.createElement("DIV");
reply.className = "comment-reply";
reply.innerHTML = '<a href="' + window.location.href + '" class="reply-cancel"></a>'
+ '<div class="reply-title">' + comment.querySelector(".author-username").innerText + ' wrotes:</div>'
+ '<div class="reply-content">' + comment.querySelector(".comment-comment").innerHTML + '</div>';
var parent = d.createElement("INPUT");
parent.type = "hidden";
parent.name = "comment[parent_uid]";
parent.value = comment.id.replace("comment-", "");
// Append Cancel
reply.querySelector(".reply-cancel").addEventListener("click", function(event){
event.preventDefault();
// Remove Elements
reply.parentElement.removeChild(reply);
parent.parentElement.removeChild(parent);
// Switch Button Text
var old = form.querySelector("button").innerText;
form.querySelector("button").value = "comment";
form.querySelector("button").innerText = form.querySelector("button").getAttribute("data-string");
form.querySelector("button").setAttribute("data-string", old);
});
// Inject Elements
var art = form.querySelector(".comment-article");
if(art.querySelector(".comment-reply")){
art.replaceChild(reply, art.querySelector(".comment-reply"));
} else {
art.insertBefore(reply, form.querySelector("textarea").parentElement);
}
var foo = form.querySelector(".comment-footer");
if(foo.querySelector("input[name='comment[parent_uid]']")){
foo.replaceChild(parent, foo.querySelector("input[name='comment[parent_uid]']"));
} else {
foo.appendChild(parent);
}
// Switch Button Text
var old = form.querySelector("button").innerText;
form.querySelector("button").value = "reply";
form.querySelector("button").innerText = form.querySelector("button").getAttribute("data-string");
form.querySelector("button").setAttribute("data-string", old);
});
}
/*
| HANDLE COMMENT RATING
*/
if(list){
list.addEventListener("click", function(event){
if(event.target.tagName != "A" || !SNICKER_AJAX){
return true;
}
if(event.target.classList.contains("disabled")){
return true;
}
// Check Link
var href = event.target.getAttribute("href");
if(href.indexOf("&type=like") < 0 && href.indexOf("&type=dislike") < 0){
return true;
}
// Event Handler
event.preventDefault();
event.target.classList.add("disabled");
var comment = (function getComment(element){
var parent = element.parentElement;
return (parent.classList.contains("comment"))? parent: getComment(parent);
})(event.target), self = event.target;
href = href.split("?");
// AJAX REQUEST
ajax(SNICKER_PATH, "POST", href[1], function(json){
var data = JSON.parse(json);
if(data.status === "success" && "rating" in data){
var like = comment.querySelector("[data-snicker='like']");
if(like){
like.innerText = String(data.rating[0]);
}
like.parentElement.classList[(like.parentElement == self? "add": "remove")]("active");
var dislike = comment.querySelector("[data-snicker='dislike']");
if(dislike){
dislike.innerText = String(data.rating[1]);
}
dislike.parentElement.classList[(dislike.parentElement == self? "add": "remove")]("active");
}
// Re-Enable Button
self.classList.remove("disabled");
});
});
}
});
})(window);

311
themes/default/snicker.php

@ -0,0 +1,311 @@
<?php
/*
| Snicker The first native FlatFile Comment Plugin 4 Bludit
| @file ./system/themes/default/snicker.php
| @author SamBrishes <sam@pytes.net>
| @version 0.1.2 [0.1.0] - Alpha
|
| @website https://github.com/pytesNET/snicker
| @license X11 / MIT License
| @copyright Copyright © 2019 SamBrishes, pytesNET <info@pytes.net>
*/
if(!defined("BLUDIT")){ die("Go directly to Jail. Do not pass Go. Do not collect 200 Cookies!"); }
class Default_SnickerTemplate extends CommentsTheme{
const SNICKER_NAME = "Default Theme";
const SNICKER_JS = "snicker.js";
const SNICKER_CSS = "snicker.css";
/*
| RENDER :: COMMENT FORM
| @since 0.1.0
| @update 0.1.1
*/
public function form($username = "", $email = "", $title = "", $message = ""){
global $comments, $login, $page, $security, $Snicker;
// User Logged In
if(!is_a($login, "Login")){
$login = new Login;
}
$user = $login->isLogged();
// Get Data
if(empty($security->getTokenCSRF())){
$security->generateTokenCSRF();
}
$captcha = ($user)? "disabled": sn_config("frontend_captcha");
$terms = ($user)? "disabled": sn_config("frontend_terms");
// Is Reply
$reply = isset($_GET["snicker"]) && $_GET["snicker"] == "reply";
if($reply && isset($_GET["uid"]) && $comments->exists($_GET["uid"])){
$reply = new Comment($_GET["uid"], $page->uuid());
}
?>
<form class="comment-form" method="post" action="<?php echo $page->permalink(); ?>?snicker=comment#snicker">
<?php if(is_array($username)){ ?>
<div class="comment-header">
<input type="hidden" id="comment-user" name="comment[user]" value="<?php echo $username[0]; ?>" />
<input type="hidden" id="comment-token" name="comment[token]" value="<?php echo $username[1]; ?>" />
<div class="inner">
<?php sn_e("Logged in as %s (%s)", array("<b>" . $username[2] . "</b>", $username[0])); ?>
</div>
</div>
<?php } else { ?>
<div class="comment-header">
<div class="table">
<div class="table-cell align-left">
<input type="text" id="comment-user" name="comment[username]" value="<?php echo $username; ?>" placeholder="<?php sn_e("Your Username"); ?>" />
</div>
<div class="table-cell align-right">
<input type="email" id="comment-mail" name="comment[email]" value="<?php echo $email; ?>" placeholder="<?php sn_e("Your eMail address"); ?>" />
</div>
</div>
</div>
<?php } ?>
<div class="comment-article">
<?php if(Alert::get("snicker-alert") !== false){ ?>
<div class="comment-alert alert-error">
<?php Alert::p("snicker-alert"); ?>
</div>
<?php } else if(Alert::get("snicker-success") !== false){ ?>
<div class="comment-alert alert-success">
<?php Alert::p("snicker-success"); ?>
</div>
<?php } ?>
<?php if($title !== false){ ?>
<p>
<input type="text" id="comment-title" name="comment[title]" value="<?php echo $title; ?>" placeholder="<?php sn_e("Comment Title"); ?>" />
</p>
<?php } ?>
<p>
<textarea id="comment-text" name="comment[comment]" placeholder="<?php sn_e("Your Comment..."); ?>"><?php echo $message; ?></textarea>
</p>
<?php if($captcha !== "disabled"){ ?>
<div class="comment-captcha">
<input type="text" name="comment[captcha]" value="" placeholder="<?php sn_e("Answer"); ?>" />
<a href="<?php echo $page->permalink(); ?>#snicker-comment-form" data-captcha="reload">
<?php echo $Snicker->generateCaptcha(); ?>
</a>
</div>
<?php } ?>
<?php if(is_a($reply, "Comment")){ ?>
<div class="comment-reply">
<a href="<?php echo $page->permalink(); ?>" class="reply-cancel"></a>
<div class="reply-title">
<?php echo $reply->username(); ?> <?php sn_e("wrotes"); ?>:
</div>
<div class="reply-content">
<?php echo $reply->comment(); ?>
</div>
</div>
<?php } ?>
</div>
<div class="comment-footer">
<div class="table">
<div class="table-cell align-left">
<?php if($terms === "default"){ ?>
<div class="terms-of-use">
<input type="checkbox" id="comment-terms" name="comment[terms]" value="1" />
<label for="comment-terms">
<?php echo sn_config("string_terms_of_use"); ?>
</label>
</div>
<?php } else if($terms !== "disabled"){ ?>
<div class="terms-of-use">
<input type="checkbox" id="comment-terms" name="comment[terms]" value="1" />
<label for="comment-terms">
<?php sn_e("I agree the %s!", array('<a href="" target="_blank">'.sn__("Terms of Use").'</a>')); ?>
</label>
</div>
<?php } ?>
</div>
<div class="table-cell align-right">
<input type="hidden" name="tokenCSRF" value="<?php echo $security->getTokenCSRF(); ?>" />
<input type="hidden" name="comment[page_uuid]" value="<?php echo $page->uuid(); ?>" />
<input type="hidden" name="action" value="snicker" />
<?php if(is_a($reply, "Comment")){ ?>
<input type="hidden" name="comment[parent_uid]" value="<?php echo $reply->uid(); ?>" />
<button name="snicker" value="reply" data-string="<?php sn_e("Comment"); ?>"><?php sn_e("Answer"); ?></button>
<?php } else { ?>
<button name="snicker" value="comment" data-string="<?php sn_e("Answer"); ?>"><?php sn_e("Comment"); ?></button>
<?php } ?>
</div>
</div>
</div>
</form>
<?php
unset($_SESSION["s_snicker-alert"]); // Remove Snicker Alerts
unset($_SESSION["s_snicker-success"]); // Remove Snicker Success
}
/*
| RENDER :: PAGINATION
| @since 0.1.0
*/
public function pagination($location, $cpage, $limit, $count){
global $url;
// Data
$link = DOMAIN . $url->uri() . "?cpage=%d#snicker-comments-list";
$maxpages = (int) ceil($count / $limit);
$prev = ($cpage === 1)? false: $cpage - 1;
$next = ($cpage === $maxpages)? false: $cpage + 1;
// Top Position
if($location === "top"){
?>
<div class="pagination pagination-top">
<?php if($cpage === 1){ ?>
<span class="pagination-button button-previous disabled"><?php sn_e("Previous Comments"); ?></span>
<?php } else { ?>
<a href="<?php printf($link, $prev); ?>" class="pagination-button button-previous"><?php sn_e("Previous Comments"); ?></a>
<?php } ?>
<?php if($cpage < $maxpages){ ?>
<a href="<?php printf($link, $next); ?>" class="pagination-button button-next"><?php sn_e("Next Comments"); ?></a>
<?php } else { ?>
<span class="pagination-button button-next disabled"><?php sn_e("Next Comments"); ?></span>
<?php } ?>
</div>
<?php
}
// Bottom Position
if($location === "bottom"){
?>
<div class="pagination pagination-bottom">
<div class="pagination-inner">
<?php if($prev === false){ ?>
<span class="pagination-button button-first disabled">&laquo;</span>
<span class="pagination-button button-previous disabled">&lsaquo;</span>
<?php } else { ?>
<a href="<?php printf($link, 1); ?>" class="pagination-button button-first">&laquo;</a>
<a href="<?php printf($link, $prev); ?>" class="pagination-button button-previous">&lsaquo;</a>
<?php } ?>
<?php
if($maxpages < 6){
$start = 1;
$stop = $maxpages;
} else {
$start = ($cpage > 3)? $cpage - 3: $cpage;
$stop = ($cpage + 3 < $maxpages)? $cpage + 3: $maxpages;
}
if($start > 1){
?><span class="pagination-button button-sep disabled">...</span><?php
}
for($i = $start; $i <= $stop; $i++){
$active = ($i == $cpage)? "active": "";
?>
<a href="<?php printf($link, $i); ?>" class="pagination-button button-number <?php echo $active; ?>"><?php echo $i; ?></a>
<?php
}
if($stop < $maxpages){
?><span class="pagination-button button-sep disabled">...</span><?php
}
?>
<?php if($next !== false){ ?>
<a href="<?php printf($link, $next); ?>" class="pagination-button button-next">&rsaquo;</a>
<a href="<?php printf($link, $maxpages); ?>" class="pagination-button button-last">&raquo;</a>
<?php } else { ?>
<span class="pagination-button button-next disabled">&rsaquo;</span>
<span class="pagination-button button-last disabled">&raquo;</span>
<?php } ?>
</div>
</div>
<?php
}
}
/*
| RENDER :: COMMENT
| @since 0.1.0
*/
public function comment($comment, $uid, $depth){
global $users, $security, $Snicker, $SnickerUsers;
// Get Page
$page = new Page($comment->page_key());
$user = $SnickerUsers->getByString($comment->getValue("author"));
// Render
$token = $security->getTokenCSRF();
$maxdepth = (int) sn_config("comment_depth");
$url = $page->permalink() . "?action=snicker&snicker=rate&&uid=%s&tokenCSRF=%s";
$url = sprintf($url, $comment->uid(), $token);
?>
<div id="comment-<?php echo $comment->uid(); ?>" class="comment" style="margin-left: <?php echo (15 * ($depth - 1)); ?>px;">
<div class="table">
<div class="table-cell comment-avatar">
<?php echo $comment->avatar(90); ?>
<?php
if(isset($user["role"]) && $user["username"] === $page->username()){
echo '<span class="avatar-role">Author</span>';
} else if(isset($user["role"]) && $user["role"] === "admin"){
echo '<span class="avatar-role">Admin</span>';
}
?>
</div>
<div class="table-cell comment-content">
<?php if(sn_config("comment_title") !== "disabled" && !empty($comment->title())){ ?>
<div class="comment-title">
<?php echo $comment->title(); ?>
<?php if($comment->status() === "pending"){ ?>
<span class="comment-moderation"><?php sn_e("This comment hasn't been moderated yet!"); ?></span>
<?php } ?>
</div>
<?php } else if($comment->status() === "pending"){ ?>
<div class="comment-moderation"><?php sn_e("This comment hasn't been moderated yet!"); ?></div>
<?php } ?>
<div class="comment-meta">
<span class="meta-author">
<?php sn_e("Written by %s", array('<span class="author-username">'.$user["username"].'</span>')); ?>
</span>
<span class="meta-date">
<?php sn_e("on %s", array($comment->date())); ?>
</span>
</div>
<div class="comment-comment">
<?php echo $comment->comment(); ?>
</div>
</div>
</div>
<div class="comment-action">
<div class="table">
<div class="table-cell align-left">
<?php if(sn_config("comment_enable_like")){ ?>
<a href="<?php echo $url; ?>&type=like" class="action-like <?php echo ($Snicker->hasLiked($comment->uid())? "active": ""); ?>">
<?php sn_e("Like"); ?> <span data-snicker="like"><?php echo $comment->like(); ?></span>
</a>
<?php } ?>
<?php if(sn_config("comment_enable_dislike")){ ?>
<a href="<?php echo $url; ?>&type=dislike" class="action-dislike <?php echo ($Snicker->hasDisliked($comment->uid())? "active": ""); ?>">
<?php sn_e("Dislike"); ?> <span data-snicker="dislike"><?php echo $comment->dislike(); ?></span>
</a>
<?php } ?>
</div>
<div class="table-cell align-right">
<?php if($maxdepth === 0 || $maxdepth > $comment->depth()){ ?>
<a href="<?php echo $page->permalink(); ?>?snicker=reply&uid=<?php echo $comment->key(); ?>#snicker-comments-form" class="action-reply">
<?php sn_e("Reply"); ?>
</a>
<?php } ?>
</div>
</div>
</div>
</div>
<?php
}
}
Loading…
Cancel
Save