options = $options;
$this->dbManager = $dbManager;
$this->wpdiscuzForm = $wpdiscuzForm;
self::$current_time = current_time("timestamp");
add_filter("the_champ_login_interface_filter", [$this, "wpDiscuzSuperSocializerLogin"], 15, 2);
add_filter("pre_comment_user_ip", [$this, "fixLocalhostIp"], 10);
add_filter("get_avatar_url", [$this, "preGetDefaultAvatarUrl"], 99, 3);
if ($this->options->subscription["enableUserMentioning"]) {
add_filter("comment_text", [$this, "userMentioning"], 10, 3);
}
if ($this->options->content["enableShortcodes"]) {
add_filter("comment_text", [$this, "doShortcode"], 10, 3);
}
add_filter("comment_text", [$this, "multipleBlockquotesToOne"], 100);
add_filter("wp_update_comment_data", [$this, "commentDataArr"], 10, 3);
add_action("post_updated", [$this, "checkFeedbackShortcodes"], 10, 3);
add_action("update_postmeta", [$this, "checkMetaFeedbackShortcodes"], 10, 4);
add_action("added_post_meta", [$this, "checkMetaFeedbackShortcodes"], 10, 4);
add_filter("comment_row_actions", [$this, "commentRowStickAction"], 10, 2);
add_filter("admin_comment_types_dropdown", [$this, "addCommentTypes"]);
add_filter("wpdiscuz_after_comment_author", [$this, "userNicename"], 1, 3);
add_action("wp_ajax_wpdGetInfo", [$this, "wpdGetInfo"]);
add_action("wp_ajax_nopriv_wpdGetInfo", [$this, "wpdGetInfo"]);
if ($this->options->login["showActivityTab"]) {
add_action("wp_ajax_wpdGetActivityPage", [$this, "getActivityPage"]);
add_action("wp_ajax_nopriv_wpdGetActivityPage", [$this, "getActivityPage"]);
}
if ($this->options->login["showSubscriptionsTab"]) {
add_action("wp_ajax_wpdGetSubscriptionsPage", [$this, "getSubscriptionsPage"]);
add_action("wp_ajax_nopriv_wpdGetSubscriptionsPage", [$this, "getSubscriptionsPage"]);
}
if ($this->options->login["showFollowsTab"]) {
add_action("wp_ajax_wpdGetFollowsPage", [$this, "getFollowsPage"]);
add_action("wp_ajax_nopriv_wpdGetFollowsPage", [$this, "getFollowsPage"]);
}
add_action("admin_post_disableAddonsDemo", [$this, "disableAddonsDemo"]);
$requestUri = !empty($_SERVER["REQUEST_URI"]) ? $_SERVER["REQUEST_URI"] : "";
if (!get_option(self::OPTION_SLUG_DEACTIVATION) && (strpos($requestUri, "/plugins.php") !== false)) {
add_action("admin_footer", [$this, "wpdDeactivationReasonModal"]);
}
add_filter("wpdiscuz_comment_author", [$this, "umAuthorName"], 10, 2);
add_action("add_meta_boxes", [$this, "addRatingResetButton"], 10, 2);
add_action("save_post", [$this, "updatePostAuthorsTrs"]);
}
public function filterKses() {
$allowedtags = [];
$allowedtags["br"] = [];
$allowedtags["a"] = [
"href" => true,
"title" => true,
"target" => true,
"rel" => true,
"download" => true,
"hreflang" => true,
"media" => true,
"type" => true
];
$allowedtags["i"] = ["class" => true];
$allowedtags["b"] = [];
$allowedtags["u"] = [];
$allowedtags["strong"] = [];
$allowedtags["s"] = [];
$allowedtags["p"] = [];
$allowedtags["blockquote"] = ["cite" => true];
$allowedtags["ul"] = [];
$allowedtags["li"] = [];
$allowedtags["ol"] = [];
$allowedtags["code"] = [];
$allowedtags["em"] = [];
$allowedtags["abbr"] = ["title" => true];
$allowedtags["q"] = ["cite" => true];
$allowedtags["acronym"] = ["title" => true];
$allowedtags["cite"] = [];
$allowedtags["strike"] = [];
$allowedtags["del"] = ["datetime" => true];
$allowedtags["span"] = [
"id" => true,
"class" => true,
"title" => true,
"contenteditable" => true,
"data-name" => true
];
$allowedtags["pre"] = ["class" => true, "spellcheck" => true];
return apply_filters("wpdiscuz_allowedtags", $allowedtags);
}
public function filterCommentText($commentContent) {
if (!current_user_can("unfiltered_html")) {
kses_remove_filters();
if ($this->options->form["richEditor"] === "none" && $this->options->form["enableQuickTags"] === 0) {
$allowedTags = [];
} else {
$allowedTags = $this->filterKses();
}
$commentContent = wp_kses($commentContent, $allowedTags);
}
return $commentContent;
}
public function dateDiff($datetime) {
$text = "";
if ($datetime) {
$search = ["[number]", "[time_unit]", "[adjective]"];
$replace = [];
$now = new DateTime(gmdate('Y-m-d H:i:s'));
$ago = new DateTime($datetime);
$diff = $now->diff($ago);
if ($diff->y) {
$replace[] = $diff->y;
$replace[] = $diff->y > 1 ? esc_html($this->options->getPhrase("wc_year_text_plural")) : esc_html($this->options->getPhrase("wc_year_text"));
} else if ($diff->m) {
$replace[] = $diff->m;
$replace[] = $diff->m > 1 ? esc_html($this->options->getPhrase("wc_month_text_plural")) : esc_html($this->options->getPhrase("wc_month_text"));
} else if ($diff->d) {
$replace[] = $diff->d;
$replace[] = $diff->d > 1 ? esc_html($this->options->getPhrase("wc_day_text_plural")) : esc_html($this->options->getPhrase("wc_day_text"));
} else if ($diff->h) {
$replace[] = $diff->h;
$replace[] = $diff->h > 1 ? esc_html($this->options->getPhrase("wc_hour_text_plural")) : esc_html($this->options->getPhrase("wc_hour_text"));
} else if ($diff->i) {
$replace[] = $diff->i;
$replace[] = $diff->i > 1 ? esc_html($this->options->getPhrase("wc_minute_text_plural")) : esc_html($this->options->getPhrase("wc_minute_text"));
} else if ($diff->s) {
$replace[] = $diff->s;
$replace[] = $diff->s > 1 ? esc_html($this->options->getPhrase("wc_second_text_plural")) : esc_html($this->options->getPhrase("wc_second_text"));
}
if ($replace) {
$replace[] = esc_html($this->options->getPhrase("wc_ago_text"));
$text = str_replace($search, $replace, $this->options->general["dateDiffFormat"]);
} else {
$text = esc_html($this->options->getPhrase("wc_right_now_text"));
}
}
return $text;
}
//================== Nonce==================================================
public function generateNonceKey() {
return self::GLOBAL_NONCE_NAME;
}
public function generateNonce() {
return wp_create_nonce($this->generateNonceKey());
}
public function validateNonce($nonce = '') {
if (empty($nonce)) {
$nonce = !empty($_POST[self::GLOBAL_NONCE_NAME]) ? sanitize_text_field($_POST[self::GLOBAL_NONCE_NAME]) : "";
}
$timeDependent = wp_verify_nonce($nonce, $this->generateNonceKey());
if (!$timeDependent) {
wp_die(__("Nonce is invalid.", "wpdiscuz"));
}
}
/**
* Check rate limit for an action to prevent abuse
*
* @security This method mitigates IDOR (Insecure Direct Object Reference) vulnerabilities
* by implementing server-side rate limiting on sensitive AJAX actions.
* @security-fix CVE-2025-68997 - Rate limiting to prevent mass voting/rating abuse
* @param string $action Action identifier (e.g., 'vote', 'rate', 'subscribe')
* @param int $maxAttempts Maximum attempts allowed within the time window
* @param int $timeWindow Time window in seconds (default: 60 seconds)
* @return true|WP_Error Returns true if within limit, WP_Error if exceeded
* @since 7.6.44
*
*/
public function checkRateLimit($action, $maxAttempts = 20, $timeWindow = 60) {
$identifier = $this->getClientFingerprint();
$rateLimitKey = 'wpd_rate_' . $action . '_' . $identifier;
$attempts = get_transient($rateLimitKey);
if ($attempts !== false && $attempts >= $maxAttempts) {
return new WP_Error('wc_rate_limit_exceeded', __('Too many requests. Please slow down.', 'wpdiscuz'));
}
set_transient($rateLimitKey, ($attempts ? $attempts + 1 : 1), $timeWindow);
return true;
}
public static function validatePostAccess($post) {
if (!self::canCurrentUserAccessPost($post)) {
wp_send_json_error("wc_msg_post_not_found");
}
}
public static function canCurrentUserAccessPost($post) {
if (!$post) {
return false;
}
if (post_password_required($post)) {
return false;
}
if ('publish' === $post->post_status) {
return true;
}
return is_user_logged_in() && current_user_can('read_post', $post->ID);
}
/**
* Get a fingerprint for the current client for rate limiting purposes
* Uses multiple factors to create a more robust identifier than IP alone
*
* @security Enhanced fingerprinting prevents bypass via IP rotation/proxies
* @security-fix CVE-2025-68997 - Improved client identification
* @return string MD5 hash of client fingerprint
* @since 7.6.44
*
*/
public function getClientFingerprint() {
if (is_user_logged_in()) {
return 'user_' . get_current_user_id();
}
// For guests, combine multiple factors for better fingerprinting
$factors = [
$this->getRealIPAddr(),
isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '',
isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) ? $_SERVER['HTTP_ACCEPT_LANGUAGE'] : '',
];
return md5(implode('|', $factors));
}
//==========================================================================
public function getNumber($number) {
if ($this->options->general["humanReadableNumbers"]) {
if (absint($number) >= 1000000) {
$number = sprintf(esc_html__("%sM", "wpdiscuz"), str_replace(".0", "", number_format($number / 1000000, 1)));
} else if (absint($number) >= 1000) {
$number = sprintf(esc_html__("%sK", "wpdiscuz"), str_replace(".0", "", number_format($number / 1000, 1)));
}
}
return $number;
}
public function makeClickable($ret) {
$ret = " " . $ret;
$hook = "?";
if (is_ssl() && $this->options->general["commentLinkFilter"] == 1) {
$hook = "";
}
$ret = preg_replace_callback("#[^\"|\'](https" . $hook . ":\/\/[^\s]+(\.jpe?g|\.png|\.gif|\.bmp))#i", [
$this,
"replaceUrlToImg"
], $ret);
// this one is not in an array because we need it to run last, for cleanup of accidental links within links
$ret = preg_replace("#(]+?>|>))]+?>([^>]+?)#i", "$1$3", $ret);
$ret = trim($ret);
return $ret;
}
public function replaceUrlToImg($matches) {
$url = $matches[1];
if (is_ssl() && $this->options->general["commentLinkFilter"] == 2 && strpos($matches[1], "https://") === false) {
$url = str_replace("http://", "https://", $url);
}
$rel = "noreferrer ugc";
if (strpos($url, get_site_url()) !== 0) {
$rel .= " nofollow";
}
return apply_filters("wpdiscuz_source_to_image_conversion", "", $url);
}
/**
* check if comment has been posted today or not
*
* @param type $comment WP_Comment object or Datetime value
*
* @return type
*/
public static function isPostedToday($comment) {
if (is_object($comment)) {
return date("Ymd", strtotime(current_time("Ymd"))) <= date("Ymd", strtotime($comment->comment_date));
} else {
return date("Ymd", strtotime(current_time("Ymd"))) <= date("Ymd", strtotime($comment));
}
}
public static function getMicrotime() {
list($pfx_usec, $pfx_sec) = explode(" ", microtime());
return ((float)$pfx_usec + (float)$pfx_sec);
}
/**
* check if comment is still editable or not
* return boolean
*/
public function isCommentEditable($comment) {
if (!$comment) {
return false;
}
$commentTimestamp = strtotime($comment->comment_date);
$timeDiff = self::$current_time - $commentTimestamp;
$editableTimeLimit = $this->options->moderation["commentEditableTime"] === "unlimit" ? abs($timeDiff) + 100 : intval($this->options->moderation["commentEditableTime"]);
return apply_filters("wpdiscuz_is_comment_editable", $editableTimeLimit && ($timeDiff < $editableTimeLimit), $comment);
}
/**
* checks if the current comment content is in min/max range defined in options
*/
public function isContentInRange($commentContent, $isReply) {
if ($isReply) {
$commentMinLength = intval($this->options->content["replyTextMinLength"]);
$commentMaxLength = intval($this->options->content["replyTextMaxLength"]);
} else {
$commentMinLength = intval($this->options->content["commentTextMinLength"]);
$commentMaxLength = intval($this->options->content["commentTextMaxLength"]);
}
$commentContent = trim(strip_tags($commentContent));
$contentLength = function_exists("mb_strlen") ? mb_strlen($commentContent) : strlen($commentContent);
return ($contentLength >= $commentMinLength) && ($commentMaxLength == 0 || $contentLength <= $commentMaxLength);
}
/**
* return client real ip
*/
public static function getRealIPAddr() {
return apply_filters("pre_comment_user_ip", ($_SERVER["REMOTE_ADDR"] ?? ""));
}
public function getUIDData($uid) {
$id_strings = explode("_", $uid);
return $id_strings;
}
public function superSocializerFix() {
$output = "";
if (function_exists("the_champ_login_button")) {
$output .= "
" . wp_trim_words($commentContent, $options->content["commentReadMoreLimit"], $readMoreLink) . "
"; } public static function strWordCount($content) { $words = preg_split("/[\n\r\t ]+/", $content, -1, PREG_SPLIT_NO_EMPTY); $words = array_filter($words, function ($w) { return $w !== " "; }); return count($words); } public function isLoadWpdiscuz($post) { if (!$post || !is_object($post) || (is_front_page() && !$this->options->general["isEnableOnHome"])) { return false; } $form = $this->wpdiscuzForm->getForm($post->ID); return apply_filters("is_load_wpdiscuz", $form->getFormID() && (comments_open($post) || $post->comment_count) && is_singular() && post_type_supports($post->post_type, "comments"), $post); } /** * Returns true if wpDiscuz has a form assigned to the post and the post type supports comments. * Unlike isLoadWpdiscuz(), this works in any context (admin, AJAX, non-singular) and is * intended for use by addons that need to check wpDiscuz support outside of page rendering. */ public function isPostSupportedByWpdiscuz($postId) { $post = get_post($postId); if (!$post || !post_type_supports($post->post_type, "comments")) { return false; } return (bool)$this->wpdiscuzForm->getForm($post->ID)->getFormID(); } public function replaceCommentContentCode($content) { if (is_ssl()) { $content = preg_replace_callback("#<\s*?img[^>]*src*=*[\"\']?([^\"\']*)[^>]+>#is", [ $this, "replaceImageToURL" ], $content); } return preg_replace_callback("#`(.*?)`#is", [$this, "replaceCodeContent"], stripslashes($content)); } private function replaceImageToURL($matches) { if (strpos($matches[1], "https://") === false && $this->options->general["commentLinkFilter"] == 1) { return "\r\n" . $matches[1] . "\r\n"; } elseif (strpos($matches[1], "https://") === false && $this->options->general["commentLinkFilter"] == 2) { return str_replace("http://", "https://", $matches[0]); } else { return $matches[0]; } } private function replaceCodeContent($matches) { $codeContent = trim($matches[1]); $codeContent = str_replace(["<", ">"], ["<", ">"], $codeContent); return "" . $codeContent . ""; } public function spoiler($content) { return preg_replace_callback(self::$spoilerPattern, [$this, "_spoiler"], $content); } private function _spoiler($matches) { $html = ""; foreach ($theChampLoginOptions["providers"] as $k => $provider) { $html .= " ";
}
$html .= "
"; if ($this->options->login["showActivityTab"]) { $response .= $this->getActivityTitleHtml(); } if ($this->options->login["showSubscriptionsTab"]) { $response .= $this->getSubscriptionsTitleHtml(); } if ($this->options->login["showFollowsTab"]) { $response .= $this->getFollowsTitleHtml(); } $isFirstTab = true; $response .= apply_filters("wpdiscuz_content_modal_title", "", $currentUser); $response .= "
"; $response .= "