52 changed files with 10720 additions and 0 deletions
@ -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/ |
@ -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 |
@ -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 |
|||
|
|||
?> |
@ -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 */ |
@ -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> |
@ -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">«</span> |
|||
<span class="btn btn-secondary disabled">‹</span> |
|||
<?php } else { ?> |
|||
<a href="<?php printf($link, 1); ?>" class="btn btn-secondary">«</a> |
|||
<a href="<?php printf($link, $page-1); ?>" class="btn btn-secondary">‹</a> |
|||
<?php } ?> |
|||
<?php if(($page * $limit) < $total){ ?> |
|||
<a href="<?php printf($link, $page+1); ?>" class="btn btn-secondary">›</a> |
|||
<a href="<?php printf($link, ceil($total / $limit)); ?>" class="btn btn-secondary">»</a> |
|||
<?php } else { ?> |
|||
<span class="btn btn-secondary disabled">›</span> |
|||
<span class="btn btn-secondary disabled">»</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 |
|||
} |
@ -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> |
@ -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">«</span> |
|||
<span class="btn btn-secondary disabled">‹</span> |
|||
<?php } else { ?> |
|||
<a href="<?php printf($link, 1); ?>" class="btn btn-secondary">«</a> |
|||
<a href="<?php printf($link, $page-1); ?>" class="btn btn-secondary">‹</a> |
|||
<?php } ?> |
|||
<?php if(($page * $limit) < $total){ ?> |
|||
<a href="<?php printf($link, $page+1); ?>" class="btn btn-secondary">›</a> |
|||
<a href="<?php printf($link, ceil($total / $limit)); ?>" class="btn btn-secondary">»</a> |
|||
<?php } else { ?> |
|||
<span class="btn btn-secondary disabled">›</span> |
|||
<span class="btn btn-secondary disabled">»</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> |
@ -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> |
@ -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); |
@ -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; |
|||
} |
|||
} |
@ -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); |
|||
} |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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); |
|||
} |
|||
} |
|||
} |
@ -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'); |
|||
} |
|||
} |
@ -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); |
|||
} |
@ -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; |
|||
} |
|||
} |
@ -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; |
|||
} |
|||
} |
@ -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(); |
|||
} |
@ -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; |
|||
} |
|||
} |
@ -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; |
|||
} |
|||
} |
@ -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] |
|||
]; |
|||
} |
|||
} |
@ -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); |
|||
} |
|||
|
|||
} |
@ -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(); |
|||
} |
|||
} |
|||
} |
@ -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; |
|||
}); |
After Width: | Height: | Size: 5.9 KiB |
@ -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; |
|||
} |
|||
})(); |
@ -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; |
|||
} |
|||
})(); |
@ -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!" |
|||
} |
|||
} |
@ -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" |
|||
} |
@ -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" |
|||
} |
@ -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" |
|||
} |
@ -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": "پاسخ" |
|||
} |
@ -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" |
|||
} |
@ -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": "" |
|||
} |
@ -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">×</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()); |
|||
} |
|||
} |
@ -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); |
|||
} |
@ -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; |
|||
} |
|||
} |
@ -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; |
|||
} |
|||
} |
@ -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; |
|||
} |
|||
} |
@ -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; |
|||
} |
|||
} |
@ -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; |
|||
} |
|||
} |
File diff suppressed because it is too large
@ -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); |
|||
} |
@ -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 */ |
@ -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); |
@ -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">«</span> |
|||
<span class="pagination-button button-previous disabled">‹</span> |
|||
<?php } else { ?> |
|||
<a href="<?php printf($link, 1); ?>" class="pagination-button button-first">«</a> |
|||
<a href="<?php printf($link, $prev); ?>" class="pagination-button button-previous">‹</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">›</a> |
|||
<a href="<?php printf($link, $maxpages); ?>" class="pagination-button button-last">»</a> |
|||
<?php } else { ?> |
|||
<span class="pagination-button button-next disabled">›</span> |
|||
<span class="pagination-button button-last disabled">»</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…
Reference in new issue