| Current Path : /var/www/html/administrator/components/com_komento/includes/comment/ |
| Current File : /var/www/html/administrator/components/com_komento/includes/comment/comment.php |
<?php
/**
* @package Komento
* @copyright Copyright (C) Stack Ideas Sdn Bhd. All rights reserved.
* @license GNU/GPL, see LICENSE.php
* Komento is free software. This version may have been modified pursuant
* to the GNU General Public License, and as distributed it includes or
* is derivative of works licensed under the GNU General Public License or
* other free or open source software licenses.
* See COPYRIGHT.php for copyright notices and details.
*/
defined('_JEXEC') or die('Unauthorized Access');
use Foundry\Helpers\StringHelper;
class KomentoComment extends KomentoBase
{
private $table = null;
public $error = null;
public $rownumber = null;
public $likes = null;
public $dislikes = null;
public $liked = null;
public $disliked = null;
public $reported = null;
public $pid = null;
public $childs = null;
public $_original = null;
public $componenttitle = '';
public $contenttitle = '';
public $extension = null;
public $reports = null;
public $likedUsers = null;
public $dislikedUsers = null;
public $author = null;
public $authorName = null;
public $authorLink = null;
public $itemTitle = null;
public $permalink = null;
public function __construct($comment = null, $options = [])
{
parent::__construct();
$resetCache = false;
$cache = true;
// Determines if the object cache should be cleared.
if (isset($options['resetCache'])) {
$resetCache = (bool) $options['resetCache'];
}
// Determines if the object should be cached
if (isset($options['cache'])) {
$cache = (bool) $options['cache'];
}
// we know we want to 'preload' or cache the posts.
// if ($cache && is_array($comment)) {
// // TODO:: preload comments
// $this->loadBatchComments($comment);
// return;
// }
// The $_comment must always be a table.
$this->table = KT::table('Comments');
// If passed in argument is an integer, we load it
if (is_numeric($comment)) {
$cacheExists = KT::cache()->exists($comment, 'comment');
// When the post object has already been loaded before, just reuse it
if ($cacheExists) {
$cache = KT::cache()->get($comment, 'comment');
$this->table = $cache;
$this->bindProperty($cache);
}
// When cache doesn't exist, try to load the post
if (!$cacheExists && $comment != 0) {
$this->table->load($comment);
$this->childs = $this->table->initRepliesCount();
}
}
// If passed in argument is already a comments jtable, just assign it.
if ($comment instanceof KomentoTableComments) {
$this->table = $comment;
}
if (is_object($comment)) {
if (!$comment instanceof KomentoTableComments) {
$this->table = KT::table('Comments');
$this->table->bind($comment);
}
$this->bindProperty($comment);
}
// keep a copy of original data
$this->_original = clone $this->table;
}
private function bindProperty($source)
{
if (isset($source->rownumber)) { $this->rownumber = $source->rownumber;}
if (isset($source->liked)) { $this->liked = $source->liked;}
if (isset($source->disliked)) { $this->disliked = $source->disliked;}
if (isset($source->reported)) { $this->reported = $source->reported;}
if (isset($source->pid)) { $this->pid = $source->pid;}
if (isset($source->childs)) { $this->childs = $source->childs;}
}
/**
* method to load the comments in batch processing
*
* @since 3.0
* @access private
*/
private function loadBatchComments($comments)
{
$ids = [];
foreach ($comments as $item) {
if (is_numeric($item)) {
$ids[] = $item;
}
}
if ($ids) {
// posts
$model = KT::model('Comments');
$comments = $model->loadBatchComments($ids);
}
KT::cache()->cacheComments($comments);
}
/**
* Expands a comment
*
* @since 3.1.0
* @access public
*/
public function expand()
{
$params = $this->getParams();
$params->set('minimized', 0);
$this->table->params = $params->toString();
return $this->table->store();
}
/**
* Minimizes a comment
*
* @since 3.1.0
* @access public
*/
public function minimize()
{
$params = $this->getParams();
$params->set('minimized', 1);
$this->table->params = $params->toString();
return $this->table->store();
}
/**
* Magic method to get properties which don't exist on this object but on the table
*
* @since 3.0
* @access public
*/
public function __get($key)
{
if (property_exists('KomentoTableComments', $key)) {
return $this->table->$key;
}
if (property_exists($this, $key)) {
return $this->$key;
}
return $this->table->$key;
}
/**
* Magic method to set properties which don't exist on this object but on the table
*
* @since 3.0
* @access public
*/
public function __set($key, $value = '')
{
if (property_exists('KomentoTableComments', $key)) {
$this->table->$key = $value;
} else {
$this->$key = $value;
}
}
/**
* Binds the given data to the table
*
* @since 3.0
* @access public
*/
public function bind($data = array(), $allowBindingId = false, $options = [])
{
// Perhaps an object is passed in
if (!is_array($data)) {
$data = (array) $data;
}
// this is a form post. let clean up the data.
if (isset($data['Itemid']) && $data['Itemid']) {
$this->postDataCleanup($data);
}
// Do not allow caller to bind id as this is a security issue
if (isset($data['id']) && !$allowBindingId) {
unset($data['id']);
}
// before normalize, we need to bind the data
$state = $this->table->bind($data, true);
// lets genereate the preview column
if ($this->table->comment) {
$this->table->preview = KT::parser()->parseComment($this->table->comment);
}
if (!isset($options['normalizeData'])) {
$options['normalizeData'] = true;
}
// After binding the data, we also need to normalize the data
if ($options['normalizeData']) {
$this->normalizeData($options);
}
return $state;
}
private function postDataCleanup($data)
{
if (isset($data['Itemid'])) {
unset($data['Itemid']);
}
if (isset($data['option'])) {
unset($data['option']);
}
if (isset($data['_ts'])) {
unset($data['_ts']);
}
if (isset($data['format'])) {
unset($data['format']);
}
if (isset($data['no_html'])) {
unset($data['no_html']);
}
if (isset($data['task'])) {
unset($data['task']);
}
if (isset($data['namespace'])) {
unset($data['namespace']);
}
}
/**
* Overrides the parent's store behavior
*
* @since 3.0
* @access public
*/
public function save($options = [])
{
// Get any save options if available.
$this->saveOptions = $options;
if (!isset($this->saveOptions['isEdited'])) {
$this->saveOptions['isEdited'] = false;
}
if (!isset($this->saveOptions['processPostSave'])) {
$this->saveOptions['processPostSave'] = true;
}
// This allows us to perform necessary logics before the post is really saved
if (!isset($this->saveOptions['ignorePreSave'])) {
$this->preSave();
}
// Now we can store this in the db
$state = $this->table->store();
// Try to store the post.
if (!$state) {
$this->setError($this->table->getError());
return false;
}
// This allows us to perform necessary logics after the post is really saved.
if ($this->saveOptions['processPostSave']) {
$this->postSave();
}
return $state;
}
/**
* Notify user that has been mentioned in post
*
* @since 4.0
* @access public
*/
public function notifyNames()
{
$names = KT::string()->detectNames($this->table->comment);
if (!$names) {
return;
}
// if the avatar integrated with Easysocial, we notify using ES system instead
if ($this->config->get('layout_avatar_integration') === 'easysocial') {
KT::easysocial()->notifyNames($names, $this);
return;
}
$author = $this->getAuthor();
$data['commentDate'] = $this->getCreatedDate()->format(JText::_('DATE_FORMAT_LC3'));
$data['commentPermalink'] = $this->getPermalink();;
$data['commentContent'] = $this->getContent();
$data['commentAuthorName'] = $author->getName();
$subject = JText::sprintf('COM_KT_NOTIFICATION_MENTIONED_TITLE', $author->getName(), $this->getItemTitle());
$template = 'site/emails/comment.mention';
foreach ($names as $recipient) {
KT::notification()->insertMailQueue($subject, 'site/emails/comment.mention', $data, $recipient, false);
}
}
/**
* Determines if users can reply to this comment
*
* @since 3.0
* @access public
*/
public function canReplyTo()
{
// Replies has been disabled or user doesn't have permissions
if (!$this->config->get('enable_reply') || !$this->profile->allow('reply_comment')) {
return false;
}
// The depth has already reached maximum level, users shouldn't be able to reply to this comment.
$maximumLevel = $this->config->get('max_threaded_level');
if ($maximumLevel != 0 && $this->table->depth > ($maximumLevel - 1)) {
return false;
}
return true;
}
/**
* Determines if users can report this comment
*
* @since 4.0
* @access public
*/
public function canReport()
{
return KT::reports()->isEnabled() && !$this->isMine();
}
/**
* Determines if the current viewer can manage the comment item
*
* @since 3.0
* @access public
*/
public function canManage()
{
if (KT::isSiteAdmin()) {
return true;
}
// Guests are explicitly disallowed
if ($this->my->guest) {
return false;
}
if ($this->canEdit() || $this->canDelete() || $this->canFeature() || $this->canUnpublish() || $this->canPublish() || $this->canMinimize()) {
return true;
}
return false;
}
/**
* Determines if the viewer can edit the comment
*
* @since 3.0
* @access public
*/
public function canEdit()
{
// Guests are explicitly disallowed
if ($this->my->guest) {
return false;
}
if (KT::isSiteAdmin()) {
return true;
}
if ($this->access->allow('edit', $this)) {
return true;
}
return false;
}
/**
* Determines if the viewer can delete the comment
*
* @since 3.0
* @access public
*/
public function canDelete()
{
// Guests are explicitly disallowed
if ($this->my->guest) {
return false;
}
if (KT::isSiteAdmin()) {
return true;
}
if ($this->access->allow('delete', $this)) {
return true;
}
return false;
}
/**
* Determines if the viewer can pin the comment
*
* @since 3.0
* @access public
*/
public function canFeature()
{
// Guests are explicitly disallowed
if ($this->my->guest) {
return false;
}
if ($this->parent_id) {
return false;
}
if (KT::isSiteAdmin()) {
return true;
}
if ($this->access->allow('stick', $this)) {
return true;
}
return false;
}
/**
* Determines if the viewer can minimize a comment
*
* @since 3.1.0
* @access public
*/
public function canMinimize()
{
if (!$this->config->get('enable_minimize')) {
return false;
}
if ($this->my->guest) {
return false;
}
if (KT::isSiteAdmin() || $this->access->allow('minimize', $this)) {
return true;
}
return false;
}
/**
* Determines if the viewer can unpublish the comment
*
* @since 3.0
* @access public
*/
public function canUnpublish()
{
// Guests are explicitly disallowed
if ($this->my->guest) {
return false;
}
if (KT::isSiteAdmin()) {
return true;
}
if ($this->access->allow('unpublish', $this)) {
return true;
}
return false;
}
/**
* Determines if the viewer can submit the comment as spam
*
* @since 3.0
* @access public
*/
public function canSubmitAkismet()
{
if ($this->config->get('antispam_akismet_key') && KT::isSiteAdmin()) {
return true;
}
return false;
}
/**
* Determines if the viewer can submit the comment as spam
*
* @since 3.0
* @access public
*/
public function canSubmitSpam()
{
if (KT::isSiteAdmin()) {
return true;
}
return false;
}
/**
* Determines if the viewer can unpublish the comment
*
* @since 3.0
* @access public
*/
public function canPublish()
{
if (KT::isSiteAdmin()) {
return true;
}
if ($this->access->allow('publish', $this)) {
return true;
}
return false;
}
/**
* Ensures that the user is allowed to post comment
*
* @since 3.0
* @access public
*/
public function canPostComments()
{
// The user needs to be able to have the appropriate rights
if (!$this->profile->allow('add_comment')) {
return false;
}
// Ensure that the user isn't flooding
if ($this->config->get('antispam_flood_control')) {
$session = KT::session();
$lastReplied = $session->getLastReplyTime();
$now = JFactory::getDate();
$difference = $now->toUnix() - $lastReplied;
if ($difference && $difference <= $this->config->get('antispam_flood_interval')) {
$this->setError('COM_KOMENTO_FORM_NOTIFICATION_FLOOD');
return false;
}
}
return true;
}
/**
* Publish / Unpublish a comment
*
* @since 3.0
* @access public
*/
public function publish($type = KOMENTO_STATE_PUBLISHED)
{
$model = KT::model('Comments');
$app = $this->getApplication();
// We know this comment is being published if the current state is KOMENTO_COMMENT_MODERATE
$isBeingPublished = $this->table->published == KOMENTO_COMMENT_MODERATE && $type === KOMENTO_STATE_PUBLISHED;
// Unpublish all the childs first
$childs = $model->getChilds($this->table->id);
$hasErrors = false;
if ($childs) {
foreach ($childs as $childId) {
$child = KT::comment($childId);
$state = $child->publish($type);
if (!$state) {
$this->setError($child->getError());
$hasErrors = true;
}
}
}
// When there are errors unpublish the childs, we should skip this
if ($hasErrors) {
return false;
}
$now = JFactory::getDate()->toSql();
$this->table->published = $type;
// Allow plugins to prevent publish / unpublishing of comments
$triggerResult = true;
$triggerArgs = ['component' => $this->table->component, 'cid' => $this->table->cid, 'comment' => &$this];
if ($type === KOMENTO_STATE_PUBLISHED) {
$this->table->publish_up = $now;
$triggerResult = KT::trigger('onBeforePublishComment', $triggerArgs);
}
if ($type === KOMENTO_STATE_UNPUBLISHED) {
$this->table->publish_down = $now;
$triggerResult = KT::trigger('onBeforeUnpublishComment', $triggerArgs);
}
if ($triggerResult === false) {
$this->setError('Trigger onBeforePublishComment/onBeforeUnpublishComment false');
return false;
}
// Try to save now
if (!$this->table->store()) {
$this->setError($this->table->getError());
return false;
}
// Allow plugins to perform actions after the publish / unpublishing of comments
$triggerArgs = ['component' => $this->table->component, 'cid' => $this->table->cid, 'comment' => &$this];
if ($type === KOMENTO_STATE_PUBLISHED) {
KT::trigger('onAfterPublishComment', $triggerArgs);
}
if ($type === KOMENTO_STATE_UNPUBLISHED) {
KT::trigger('onAfterUnpublishComment', $triggerArgs);
}
if ($isBeingPublished) {
$action = $this->table->parent_id ? 'reply' : 'comment';
// send email
if ($this->config->get('notification_enable')) {
if (($action == 'comment' && $this->config->get('notification_event_new_comment')) || ($action == 'reply' && $this->config->get('notification_event_new_reply'))) {
KT::notification()->push($action, 'subscribers,author,usergroups', ['commentId' => $this->table->id]);
}
}
// Add activity
$activity = KT::activity()->process($action, $this->table->id);
// Manually get the attachment of this comment and process the "upload" activity
$attachments = $this->getAttachments();
foreach ($attachments as $attachment) {
KT::activity()->process('upload', $this);
}
}
return true;
}
/**
* Pins a comment
*
* @since 3.0
* @access public
*/
public function feature()
{
$model = KT::model('Comments');
$state = $model->stick($this->table->id);
if (!$state) {
$this->setError($model->getError());
return false;
}
// need to reload the feature flag
$this->table->sticked = 1;
return true;
}
/**
* spam a comment
*
* @since 3.0
* @access public
*/
public function spam()
{
$this->table->published = KOMENTO_COMMENT_SPAM;
// Try to save now
if (!$this->table->store()) {
$this->setError($this->table->getError());
return false;
}
// need to clear the report action as well
$this->removeReport();
return true;
}
/**
* flag a comment
*
* @since 3.0
* @access public
*/
public function flag($type = 0)
{
$this->table->flag = $type;
// Try to save now
if (!$this->table->store()) {
$this->setError($this->table->getError());
return false;
}
return true;
}
/**
* Unpins a comment
*
* @since 3.0
* @access public
*/
public function unfeature()
{
$model = KT::model('Comments');
$state = $model->unstick($this->table->id);
if (!$state) {
$this->setError($model->getError());
return false;
}
// need to reload the feature flag
$this->table->sticked = 0;
return true;
}
/**
* Normalize the data
*
* @since 3.0
* @access public
*/
public function normalizeData($options = [])
{
$fromMigration = isset($options['fromMigration']) ? $options['fromMigration'] : '';
// Normalize the comment before allowing it to save
if (is_null($this->table->ip) || !$this->table->ip) {
$ip = @$_SERVER['REMOTE_ADDR'];
// Some people might be behind a proxy
if (array_key_exists('HTTP_X_FORWARDED_FOR', $_SERVER)) {
$ip = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
$ip = array_pop($ip);
}
$this->table->ip = $ip;
}
$now = JFactory::getDate()->toSql();
// Normalize the depth of the comment
if (is_null($this->table->depth)) {
$this->table->depth = 0;
}
// If user id isn't supplied, try to get the current user
if (is_null($this->table->created_by) && $this->my->id) {
$this->table->created_by = (int) $this->my->id;
}
// If name isn't specified, use the logged in user's name or Guest if not logged in
if (!$this->table->name) {
$this->table->name = $this->my->id ? $this->profile->getName() : JText::_('COM_KOMENTO_GUEST');
}
// If e-mail isn't specified, use logged in user's email
if (!$this->table->email && $this->my->id) {
$this->table->email = $this->profile->email;
}
// If creation date isn't supplied, we need to set our own
if (is_null($this->table->created)) {
$this->table->created = $now;
}
// skip this if the process from migration, because it should respect the publish state from those comment data
if (!$fromMigration) {
// Default publishing state is enabled
$this->table->published = 1;
// Check if the poster's ip address matches any blacklisted ip addresses so that we can enforce moderation state on it
$blacklist = trim($this->config->get('blacklist_ip', ''));
if (!empty($blacklist) && $this->table->ip) {
$blacklist = explode(',', $blacklist);
if (in_array($this->table->ip, $blacklist)) {
$this->table->published = 2;
}
}
// Determines if the post should be moderated first
if ($this->profile->toModerate()) {
$this->table->published = 2;
}
}
// Honeypot traps
if ($this->isHoneypotTrapped()) {
$this->table->published = KOMENTO_COMMENT_SPAM;
$this->setParam('spam', KT_SPAM_HONEYPOT);
}
// Cleantalk antispam detection
if (KT::cleantalk()->isEnabled() && $this->getCleanTalkSpamLevel() == KOMENTO_CLEANTALK_POSSIBLE_SPAM) {
$this->table->published = KOMENTO_COMMENT_SPAM;
$this->setParam('spam', KT_SPAM_CLEANTALK);
}
// Akismet antispam detection
if (KT::akismet()->isEnabled() && $this->isAkismetSpam()) {
$this->table->published = KOMENTO_COMMENT_SPAM;
$this->setParam('spam', KT_SPAM_AKISMET);
}
// Ensure that the modified date is to the latest time
if (!$this->table->modified && $this->table->published) {
$this->table->modified = $now;
}
// Ensure that the publish_up date is set
if (!$this->table->publish_up && $this->table->published) {
$this->table->publish_up = $now;
}
}
/**
* Pre process the comment before we save it.
*
* @since 3.0
* @access public
*/
public function preSave()
{
KT::trigger('onBeforeSaveComment', ['component' => $this->table->component, 'cid' => $this->table->cid, 'comment' => &$this]);
if ($this->isNew()) {
// Calculate and update comment boundaries
$model = KT::model('Comments');
$model->updateCommentLftRgt($this->table);
}
// updating the depth if needed
if ($this->parent_id) {
$parent = KT::table('Comments');
$parent->load($this->parent_id);
$this->depth = $parent->depth + 1;
}
// Whenever a comment moved from other cid, we reset the parent to 0
if (isset($this->saveOptions['previousCid']) && $this->saveOptions['previousCid'] > 0) {
$this->parent_id = 0;
}
return true;
}
/**
* Post saving method happens after a comment is stored on the table.
*
* @since 3.0
* @access public
*/
public function postSave()
{
// Process attachments
if (isset($this->saveOptions['processAttachments']) && $this->saveOptions['processAttachments']) {
$attachments = $this->input->get('attachments', '', 'default');
if (is_array($attachments) && !empty($attachments)) {
$file = KT::file();
foreach ($attachments as $attachment) {
$state = $file->attach($attachment, $this->table->id);
if ($state) {
KT::activity()->process('upload', $this);
}
}
}
}
// Mentions
if ($this->config->get('enable_mention')) {
$this->notifyNames();
}
// Updating comment lft/rgt for when article id is changed
if (isset($this->saveOptions['previousCid']) && $this->saveOptions['previousCid'] > 0) {
// Move childs over
$this->updateChildsArticle($this->saveOptions['previousCid']);
// fix current cid
$this->fixStructure();
// fix previous cid
$this->fixStructure($this->saveOptions['previousCid']);
}
// notification to article's subscribers.
// now we add the comment subscriber here.
// We need to port the codes from controllers/subscriptions.php into a subscriptions library
$subscribe = false;
// // Determines if we should be subscribing the user to the thread
if ($this->config->get('subscription_auto')) {
$subscribe = true;
} else {
// need to get from form post.
$subscribe = $this->input->get('subscribe', false, 'default');
}
//[KOMENTO PAID START]
// Push notifications
if (KT::push()->isEnabled()) {
KT::push()->notify($this);
}
//[KOMENTO PAID END]
if ($this->config->get('enable_subscription') && $subscribe && $this->table->email) {
$data = [
'userid' => $this->table->created_by,
'fullname' => $this->table->name,
'email' => $this->table->email,
'commentId' => $this->table->id
];
// get user's interval settings. if email digest enabled
if ($this->config->get('email_digest_enabled')) {
$defaultInterval = $this->config->get('email_digest_interval');
$defaultPostCount = 10;
if ($this->table->created_by) {
$model = KT::model('Subscription');
$subscriptions = $model->getUserSubscriptions($this->table->created_by, ['limit' => 1]);
if ($subscriptions) {
$sub = $subscriptions[0];
$defaultInterval = $sub->interval;
$defaultPostCount = $sub->count;
}
}
$data['interval'] = $defaultInterval;
$data['postcount'] = $defaultPostCount;
}
KT::subscription()->add($this->table->component, $this->table->cid, $data);
}
// Set reply datetime in session for flood control
$session = KT::session();
if ($this->config->get('antispam_flood_control')) {
$session->setReplyTime();
}
KT::trigger('onAfterSaveComment', ['component' => $this->table->component, 'cid' => $this->table->cid, 'comment' => &$this]);
// Add activity
$action = $this->table->parent_id ? 'reply' : 'comment';
KT::activity()->process($action, $this);
// Send notifications
if ($this->config->get('notification_enable')) {
$isNew = !$this->saveOptions['isEdited'];
if ($this->isPublished() && $isNew && (($action == 'comment' && $this->config->get('notification_event_new_comment')) || ($action == 'reply' && $this->config->get('notification_event_new_reply')))) {
KT::notification()->push($action, 'author,usergroups,subscribers', ['commentId' => $this->table->id]);
}
if ($this->isPending() && $this->config->get('notification_event_new_pending')) {
$notifyGroups = 'usergroups';
if ($this->config->get('notification_event_new_pending_author')) {
$notifyGroups = 'author,usergroups';
}
KT::notification()->push('pending', $notifyGroups, ['commentId' => $this->table->id]);
}
}
}
public function updateChildsArticle($previousCid)
{
$model = KT::model('Comments');
$model->updateChildsArticle($this->table, $previousCid);
}
public function fixStructure($oldCid = 0)
{
$model = KT::model('comments');
$cid = $oldCid > 0 ? $oldCid : $this->table->cid;
$parents = $model->getRootParents($this->table->component, $cid);
// Set boundary to start from 1
$boundary = 1;
// Fix all the parent first
foreach ($parents as $parent) {
$model->fixItemStructure($parent, $boundary, 0);
}
// Now we start fixing the childrens
foreach($parents as $parent) {
$model->fixChildStructure($parent);
}
}
/**
* Retrieves the application plugin for the unique item
*
* @since 3.0
* @access public
*/
public function getApplication()
{
return KT::loadApplication($this->table->component)->load($this->table->cid);
}
/**
* Retrieve attachments within a comment
*
* @since 4.0.0
* @access public
*/
public function getAttachments()
{
static $cache = [];
if (isset($cache[$this->id])) {
return $cache[$this->id];
}
$items = [];
$cacheExists = KT::cache()->exists($this->id, 'attachment');
if ($cacheExists) {
$items = KT::cache()->get($this->id, 'attachment');
}
if (!$cacheExists) {
$model = KT::model('Uploads');
$items = $model->getAttachments($this->id);
}
$cache[$this->id] = [];
if ($items) {
$acl = KT::acl();
foreach ($items as $item) {
// To satisfy the display from Foundry, we need to standardize these props
$item->name = $item->filename;
$item->icon = $item->getIconType();
$item->title = $item->filename;
$item->image = $item->isImage() ? $item->getLink() : false;
$item->size = $item->getSize();
$item->download = $item->getLink();
$item->canDelete = $acl->allow('delete_attachment', $this);
$cache[$this->id][] = $item;
}
}
return $cache[$this->id];
}
/**
* Retrieves the date object for the comment
*
* @since 3.0
* @access public
*/
public function getCreatedDate()
{
$date = FH::date($this->created, true);
return $date;
}
/**
* method port from jtable. to retrieve comment's author.
*
* @since 3.0
* @access public
*/
public function getAuthor()
{
if (!$this->created_by) {
$author = KT::user(0);
} else {
$author = KT::user($this->created_by);
}
return $author;
}
/**
* Method to retrive author display name based on the config
*
* @since 3.0
* @access public
*/
public function getAuthorName()
{
if (!$this->created_by) {
return $this->name;
}
return $this->getAuthor()->getName();
}
/**
* Method to retrive author display name based on the config
*
* @since 3.0
* @access public
*/
public function getAuthorEmail()
{
if (!$this->created_by) {
return $this->email;
}
return $this->getAuthor()->email;
}
/**
* Method to retrive author display name based on the config
*
* @since 3.0
* @access public
*/
public function getAuthorWebsite()
{
if (!$this->created_by) {
return $this->url;
}
return $this->getAuthor()->email;
}
/**
* Get parent author profile link
*
* @since 3.1
* @access public
*/
public function getParentAuthorLink()
{
static $parentAuthorLinks = [];
if (!isset($parentAuthorLinks[$this->parent_id])) {
$parentComment = $this->getParent();
$author = $parentComment->getAuthor();
$parentAuthorLinks[$this->parent_id] = $author->getProfileLink($parentComment->getAuthorEmail(), $parentComment->getAuthorWebsite());
}
return $parentAuthorLinks[$this->parent_id];
}
/**
* Retrieves the parsed contents of the comment
*
* @since 3.0
* @access public
*/
public function getContent($truncate = false, $insertEllipses = false)
{
static $result = [];
$key = $this->id . (int) $truncate . (int) $insertEllipses;
if (!isset($result[$key])) {
$contents = $this->table->preview;
if (!$contents) {
$contents = KT::parser()->parseComment($this->table->comment);
}
if ($truncate) {
// the content in preview column might have html tags that are being htmlentities, and we need to decode the htmlentities back to it original character
// before calling strip_tags #520
$contents = html_entity_decode($contents);
$insertEllipses = FCJString::strlen($contents) > $truncate;
$contents = FCJString::substr(strip_tags($contents), 0, $truncate);
if ($insertEllipses) {
$contents .= JText::_('COM_KOMENTO_ELLIPSES');
}
}
$result[$key] = $contents;
}
return $result[$key];
}
/**
* Retrieves the address posted with the comment
*
* @since 3.0
* @access public
*/
public function getAddress($charactersLimit = null)
{
if (is_null($charactersLimit)) {
return $this->table->address;
}
return FCJString::substr($this->table->address, 0, $charactersLimit) . JText::_('COM_KOMENTO_ELLIPSES');
}
/**
* Generates the css class for the comment wrapper
*
* @since 3.0
* @access public
*/
public function getCustomCss()
{
$classes = [];
$classes[] = 'kmt-item';
$author = $this->getAuthor();
$app = $this->getApplication();
// Usergroup CSS classes
if ($author->guest) {
$classes[] = $this->config->get('layout_css_public');
} else {
$classes[] = $this->config->get('layout_css_registered');
}
if ($author->isAdmin()) {
$classes[] = $this->config->get('layout_css_admin');
}
if ($this->created_by == $app->getAuthorId()) {
$classes[] = $this->config->get('layout_css_author');
}
$groups = $author->getUsergroups();
if (is_array($groups) && !empty($groups)) {
foreach ($groups as $group) {
$classes[] = 'kt-group-' . $group;
}
}
$custom = implode(' ', $classes);
return $custom;
}
/**
* Retrieves the indentation styling
*
* @since 3.0
* @access public
*/
public function getIndentStyling($spacer = 'margin', $customDepth = null)
{
if (!$this->depth || !$this->config->get('enable_threaded')) {
return;
}
$depth = $this->table->depth;
if (!is_null($customDepth) && $customDepth) {
$depth = $customDepth;
}
$total = $this->config->get('thread_indentation') * $depth;
$margin = $spacer . '-left';
if ($this->doc->getDirection() == 'rtl') {
$margin = $spacer . '-right';
}
$style = $margin . ':' . $total . 'px;';
return $style;
}
/**
* Retrieves the content item's title
*
* @since 3.0
* @access public
*/
public function getItemTitle()
{
$app = $this->getApplication();
$title = $app->getContentTitle();
return $title;
}
/**
* Retrieves the content item's state
*
* @since 3.0
* @access public
*/
public function getItemState()
{
$app = $this->getApplication();
$state = $app->getContentState();
return $state;
}
/**
* Retrieves the component's title
*
* @since 3.0
* @access public
*/
public function getComponentTitle()
{
$app = $this->getApplication();
$title = $app->getComponentName();
return $title;
}
/**
* Retrieves the date object for the comment
*
* @since 3.0
* @access public
*/
public function getModifiedDate()
{
$date = FH::date($this->modified);
return $date;
}
/**
* Retrieves the content item's permalink
*
* @since 3.0
* @access public
*/
public function getItemPermalink()
{
$app = $this->getApplication();
$permalink = $app->getContentPermalink();
return $permalink;
}
/**
* Generates the permalink for the comment.
*
* @since 3.0
* @access public
*/
public function getPermalink()
{
static $permalinks = [];
$idx = $this->id;
if (!isset($permalinks[$idx])) {
$limitperpage = $this->config->get('max_comments_per_page');
$defaultsort = $this->config->get('default_sort', 'oldest');
$sort = $this->input->get('sort', '', 'default');
$model = KT::model('Comments');
$pageInfo = '';
if (! isset($this->rownumber) || !$this->rownumber) {
// we need to manually count the page info.
$rownumber = $model->getRowNumber($this->id, $defaultsort);
if ($rownumber) {
$this->rownumber = $rownumber;
}
}
$pageInfo = '';
if (!is_null($this->rownumber) && $this->rownumber) {
$startpage = $this->rownumber / $limitperpage;
$startpage = ceil($startpage);
if ($startpage > 1) {
$pageInfo = ',' . ($startpage - 1) * $limitperpage;
}
if ($sort && $sort != $defaultsort) {
$pageInfo = ($pageInfo) ? $pageInfo . ',' . $sort : ',0,' . $sort;
}
}
$pid = '';
if (isset($this->pid) && $this->pid) {
$parentId = $this->pid;
$pid = ',' . $parentId;
} else if (is_null($this->pid) && $this->depth) {
// if this condition meet, thats means this comment object did not go through formatter.
// let try to get manually
$parentId = $model->getParents($this->id, true);
if ($parentId && $parentId != $this->id) {
$this->pid = $parentId;
$pid = ',' . $parentId;
} else {
$pid = ',0';
}
} else {
$pid = ',0';
}
// $permalinks[$idx] = $this->getItemPermalink() . '#comment-' . base64_encode($this->id . $pageInfo);
$permalinks[$idx] = $this->getItemPermalink() . '#comment-' . $this->id . $pid . $pageInfo;
}
return $permalinks[$idx];
}
/**
* Retrieves the params
*
* @since 3.0
* @access public
*/
public function getParams()
{
static $data = [];
if (!isset($data[$this->id])) {
$registry = new JRegistry($this->params);
$data[$this->id] = $registry;
}
return $data[$this->id];
}
/**
* Retrieves the parent comment if this comment is a reply
*
* @since 3.0
* @access public
*/
public function getParent()
{
static $parents = [];
if (! isset($parents[$this->parent_id])) {
$parents[$this->parent_id] = KT::comment($this->parent_id);
}
return $parents[$this->parent_id];
}
/**
* Retrieves a list of replies for a particular comment
*
* @since 3.0
* @access public
*/
public function getReplies()
{
static $items = [];
if (!isset($items[$this->id])) {
if (KT::cache()->exists($this->id, 'replies')) {
$replies = KT::cache()->get($this->id, 'replies');
$comments = [];
if ($replies) {
foreach ($replies as $reply) {
$comment = KT::comment($reply->id);
// Get actions likes
if (KT::cache()->exists($comment->id, 'likes')) {
$comment->likes = KT::cache()->get($comment->id, 'likes');
} else {
$comment->likes = $actionsModel->countAction('likes', $comment->id);
}
// Get actions dislikes
if (KT::cache()->exists($comment->id, 'dislikes')) {
$comment->dislikes = KT::cache()->get($comment->id, 'dislikes');
} else {
$comment->dislikes = $actionsModel->countAction('dislikes', $comment->id);
}
$comments[] = $comment;
}
}
$items[$this->id] = $comments;
} else {
$model = KT::model('Comments');
$ids = $model->getChilds($this->id);
$comments = [];
if ($ids) {
// preload
KT::comment($ids);
foreach ($ids as $id) {
$comments[] = KT::comment($id);
}
}
$items[$this->id] = $comments;
}
}
return $items[$this->id];
}
public static function convertOrphanitem($id)
{
$config = KT::getConfig();
$comment = KT::getTable('comments');
$comment->load($id);
$comment->created_by = $config->get('orphanitem_ownership');
$comment->store();
return true;
}
/**
* Sets an error message
*
* @since 3.0
* @access public
*/
public function setError($message = '')
{
$this->error = JText::_($message);
}
/**
* Sets a value in the comment params
*
* @since 4.0.0
* @access public
*/
public function setParam($key, $value)
{
$params = $this->getParams();
$params->set($key, $value);
$this->params = $params->toString();
return $params;
}
/**
* Get an error message
*
* @since 3.0
* @access public
*/
public function getError($message = '')
{
return $this->error;
}
/**
* Validates the current comment object
*
* @since 3.0
* @access public
*/
public function validate($data)
{
// Check for name field
$requireName = ($this->config->get('show_name') == 2 && $this->config->get('require_name') == 2) || ($this->my->guest && $this->config->get('show_name') > 0 && $this->config->get('require_name') == 1);
if (!$this->table->name && $requireName) {
$this->setError('COM_KOMENTO_VALIDATE_NAME_REQUIRED');
return false;
}
// Check for email field
$requireEmail = ($this->config->get('show_email') == 2 && $this->config->get('require_email') == 2) || ($this->my->guest && $this->config->get('show_email') > 0 && $this->config->get('require_email') == 1);
if (!$this->table->email && $requireEmail) {
$this->setError('COM_KOMENTO_VALIDATE_EMAIL_REQUIRED');
return false;
}
// Check for website field
$requireUrl = ($this->config->get('show_website') == 2 && $this->config->get('require_website') == 2) || ($this->my->guest && $this->config->get('show_website') > 0 && $this->config->get('require_website') == 1);
if (!$this->table->url && $requireUrl) {
$this->setError('COM_KOMENTO_VALIDATE_WEBSITE_REQUIRED');
return false;
}
// Check for terms and conditions acceptance
if (KT::form()->requireTerms() && $data['tnc'] == 'false') {
$this->setError('COM_KOMENTO_VALIDATE_TERMS_REQUIRED');
return false;
}
// Ensure that the e-mail is valid against their defined e-mail pattern
if ($this->table->email) {
// $pattern = '/S+@S+/';
$pattern = '/^([.0-9a-z_+-]+)@(([0-9a-z-]+\.)+[0-9a-z]+)$/i';
if (!preg_match($pattern, $this->table->email)) {
$this->setError('COM_KOMENTO_FORM_NOTIFICATION_EMAIL_INVALID');
return false;
}
}
$this->table->comment = KT::parser()->removeEmptyBBcodes($this->table->comment);
$this->table->comment = trim($this->table->comment);
// Ensure that the comment isn't empty
if ($this->table->comment === '') {
$this->setError('COM_KOMENTO_FORM_NOTIFICATION_COMMENT_REQUIRED');
return false;
}
// Minimum characters check
if ($this->config->get('antispam_min_length_enable') && FCJString::strlen($this->table->comment) < $this->config->get('antispam_min_length')) {
$this->setError(JText::sprintf('COM_KOMENTO_FORM_VALIDATION_TOO_SHORT', $this->config->get('antispam_min_length')));
return false;
}
// Maximum characters check
if ($this->config->get('antispam_max_length_enable') && FCJString::strlen($this->table->comment) > $this->config->get('antispam_max_length')) {
$this->setError(JText::sprintf('COM_KOMENTO_FORM_VALIDATION_TOO_LONG', $this->config->get('antispam_max_length')));
return false;
}
// Captcha checks
$captcha = KT::captcha();
if ($this->config->get('antispam_captcha_enable') && KT::form()->requireCaptcha() && $captcha) {
$options = [
'recaptcha_response_field' => isset($data['recaptchaResponse']) ? $data['recaptchaResponse'] : '',
'captcha-response' => isset($data['captchaResponse']) ? $data['captchaResponse'] : '',
'captcha-id' => isset($data['captchaId']) ? $data['captchaId'] : ''
];
if (!$captcha->verify($options)) {
$error = $captcha->getError();
$this->setError($error);
return false;
}
}
$blockedWords = $this->config->get('blocked_words');
$hasBlockedWords = StringHelper::hasBlockedWords($blockedWords, $data['comment']);
if ($hasBlockedWords) {
$this->setError(JText::sprintf('COM_KT_CONTAINS_BLOCKED_WORD_MESSAGE', $hasBlockedWords, $blockedWords));
return false;
}
return true;
}
/**
* Validate the antispams
*
* @since 3.0
* @access public
*/
public function validateSpam()
{
// Honeypot spam detection
if ($this->isHoneypotTrapped() && $this->config->get('antispam_honeypot_rejection_type') === 'high') {
$this->setError('Comment submitted');
return false;
}
// CleanTalk antispam detection
if (KT::cleantalk()->isEnabled() && ($this->getCleanTalkSpamLevel() == KOMENTO_CLEANTALK_SPAM)) {
$this->setError('COM_KOMENTO_FORM_NOTIFICATION_CLEANTALK_SPAM');
return false;
}
$akismetRejection = $this->config->get('antispam_akismet_rejection_type');
// Akismet antispam detection
if (KT::akismet()->isEnabled() && $akismetRejection == 'high' && $this->isAkismetSpam()) {
$this->setError('COM_KOMENTO_FORM_NOTIFICATION_AKISMET_SPAM');
return false;
}
return true;
}
/**
* Check for cleantalk spam
*
* @since 3.0
* @access public
*/
public function getCleanTalkSpamLevel()
{
$cleantalk = KT::cleantalk();
$isSpam = $cleantalk->validate($this);
if ($isSpam) {
return true;
}
return false;
}
/**
* Retrieves the spam type for the comment if it was caught by the antispam filters
*
* @since 4.0.0
* @access public
*/
public function getSpamType()
{
static $cache = [];
if (!isset($cache[$this->id])) {
$params = $this->getParams();
$cache[$this->id] = $params->get('spam', false);
}
return $cache[$this->id];
}
/**
* Check for akismet spam
*
* @since 3.0
* @access public
*/
public function isAkismetSpam()
{
$akismet = KT::akismet();
$isSpam = $akismet->isSpam($this);
if ($isSpam) {
return true;
}
return false;
}
/**
* Check for honeypot traps
*
* @since 4.0.0
* @access public
*/
public function isHoneypotTrapped()
{
$config = KT::config();
if (!$config->get('antispam_honeypot_enabled')) {
return false;
}
$honeypot = KT::honeypot();
$trapped = $honeypot->isTrapped();
return $trapped;
}
/**
* Determines if the comment has childs
*
* @since 3.0
* @access public
*/
public function hasReplies()
{
static $items = [];
if (!isset($items[$this->id])) {
if (isset($this->childs)) {
$items[$this->id] = $this->childs;
} else {
$items[$this->id] = $this->getRepliesCount();
}
}
return $items[$this->id];
}
public function getRepliesCount()
{
$repliesCnt = 0;
if (is_null($this->childs)) {
$boundary = ($this->rgt - $this->lft) - 1;
if (!$this->parent_id && $boundary > 0) {
$repliesCnt = floor($boundary / 2);
}
$this->childs = $repliesCnt;
}
return $this->childs;
}
/**
* Determines if a comment has location
*
* @since 3.0
* @access public
*/
public function hasLocation()
{
if ($this->address || ($this->address && $this->latitude && $this->longitude)) {
return true;
}
return false;
}
/**
* Determines if a comment has geometry location
*
* @since 3.0
* @access public
*/
public function hasGeometryLocation()
{
if ($this->address && $this->latitude && $this->longitude) {
return true;
}
return false;
}
/**
* Get Map url based on setting
*
* @since 4.0
* @access public
*/
public function getMapUrl()
{
if ($this->config->get('location_service_provider') === 'osm') {
return '//www.openstreetmap.org/?mlat=' . $this->latitude . '&mlon=' . $this->longitude . '#map=15/' . $this->latitude . '/' . $this->longitude;
}
return '//maps.google.com/maps?z=15&q=' . $this->latitude . ',' . $this->longitude;
}
/**
* Determines if the comment is unpublished
*
* @since 3.0
* @access public
*/
public function isUnpublished()
{
return $this->table->published == KOMENTO_COMMENT_UNPUBLISHED;
}
/**
* Determines if the comment is pending
*
* @since 3.0
* @access public
*/
public function isPending()
{
return $this->table->published == KOMENTO_COMMENT_MODERATE;
}
/**
* Determines if the comment is minimized
*
* @since 3.1.0
* @access public
*/
public function isMinimized()
{
static $items = [];
if (!isset($items[$this->id])) {
$params = $this->getParams();
$items[$this->id] = $params->get('minimized', false);
}
return $items[$this->id];
}
/**
* Determines if the comment is new
*
* @since 3.0
* @access public
*/
public function isNew()
{
return $this->_original->id ? false : true;
}
/**
* Determines if this is a parent comment
*
* @since 3.0
* @access public
*/
public function isParent()
{
return !$this->parent_id;
}
/**
* Determines if the comment is spam
*
* @since 3.0
* @access public
*/
public function isSpam()
{
return $this->table->published == KOMENTO_COMMENT_SPAM;
}
/**
* Determines if the comment is reported
*
* @since 3.0
* @access public
*/
public function isReport()
{
$model = KT::model('Actions');
$state = $model->countAction('report', $this->table->id);
return $state;
}
/**
* Determines if the comment is published
*
* @since 3.0
* @access public
*/
public function isPublished()
{
return $this->table->published == KOMENTO_COMMENT_PUBLISHED;
}
/**
* Determines if the comment is a pinned comment
*
* @since 3.0
* @access public
*/
public function isFeatured()
{
return $this->table->sticked > 0;
}
/**
* Determines if the comment has been edited before
*
* @since 3.0
* @access public
*/
public function isEdited()
{
return $this->table->modified_by > 0;
}
/**
* Allows caller to delete a comment
*
* @since 3.0
* @access public
*/
public function delete()
{
// @onBeforeDeleteComment
$args = array('component' => $this->table->component, 'cid' => $this->table->cid, 'comment' => &$this);
$result = KT::trigger('onBeforeDeleteComment', $args);
if ($result === false) {
$this->setError('Trigger onBeforeDeleteComment false');
return false;
}
$state = $this->table->delete();
if (!$state) {
$this->setError('Comment delete failed');
return false;
}
// @onAfterDeleteComment
KT::trigger('onAfterDeleteComment', $args);
// Always delete the replies no matter what.
$model = KT::model('Comments');
$model->deleteChilds($this->table->id);
// Clear activities
$activitiesModel = KT::model('Activity');
$activitiesModel->delete($this->table->id);
// Clear actions
$actionsModel = KT::model('Actions');
$actionsModel->removeAction('all', $this->table->id, 'all');
// Process activities
KT::activity()->process('remove', $this);
// Remove attachments
KT::file()->clearAttachments($this->table->id);
return true;
}
/**
* Generates an export-able data that is safe for viewing
*
* @since 3.0
* @access public
*/
public function export()
{
$data = (object) get_object_vars($this->table);
return $data;
}
/**
* Allows caller to remove reports from a particular comment
*
* @since 3.0
* @access public
*/
public function removeReport()
{
$model = KT::model('Actions');
$state = $model->removeAction('report', $this->table->id);
return $state;
}
/**
* Retrieve commment ratings
*
* @since 3.0
* @access public
*/
public function getRatings()
{
return $this->table->ratings / 2;
}
/**
* Determine whether the comment is ownself.
*
* @since 4.0.0
* @access public
*/
public function isMine()
{
if ((int) $this->created_by === (int) $this->my->id) {
return true;
}
return false;
}
}