diff --git a/anti-spam-links/SMF-2.1/Sources/AntiSpamLinks.php b/anti-spam-links/SMF-2.1/Sources/AntiSpamLinks.php
new file mode 100644
index 0000000..99ea50a
--- /dev/null
+++ b/anti-spam-links/SMF-2.1/Sources/AntiSpamLinks.php
@@ -0,0 +1,532 @@
+ Posts and Topics > Posts.
+ *
+ * @param array $config_vars
+ * @return void
+ */
+function AntiSpamLinks_ModifyPostSettings(&$config_vars)
+{
+ global $txt;
+
+ antiSpamLinks_load_text();
+
+ $config_vars[] = '';
+ $config_vars[] = $txt['anti_spam_links'];
+ $config_vars[] = array('int', 'anti_spam_links_nolinks', 'subtext' => $txt['anti_spam_links_zero_disable']);
+ $config_vars[] = array('int', 'anti_spam_links_newbielinks', 'subtext' => $txt['anti_spam_links_zero_disable']);
+ $config_vars[] = array('int', 'anti_spam_links_nofollowlinks', 'subtext' => $txt['anti_spam_links_zero_disable']);
+ $config_vars[] = array('select', 'anti_spam_links_guests', array(
+ $txt['anti_spam_links_guests_opt0'],
+ $txt['anti_spam_links_guests_opt1'],
+ $txt['anti_spam_links_guests_opt2'],
+ $txt['anti_spam_links_guests_opt3'],
+ ));
+}
+
+/**
+ * Normalize settings before SMF saves them.
+ *
+ * @return void
+ */
+function AntiSpamLinks_SavePostSettings()
+{
+ if (!isset($_POST['anti_spam_links_newbielinks'], $_POST['anti_spam_links_nofollowlinks']))
+ return;
+
+ $_POST['anti_spam_links_newbielinks'] = max(0, (int) $_POST['anti_spam_links_newbielinks']);
+ $_POST['anti_spam_links_nofollowlinks'] = max(0, (int) $_POST['anti_spam_links_nofollowlinks']);
+ $_POST['anti_spam_links_nolinks'] = isset($_POST['anti_spam_links_nolinks']) ? max(0, (int) $_POST['anti_spam_links_nolinks']) : 0;
+ $_POST['anti_spam_links_guests'] = isset($_POST['anti_spam_links_guests']) ? min(3, max(0, (int) $_POST['anti_spam_links_guests'])) : 0;
+
+ if (!empty($_POST['anti_spam_links_newbielinks']) && !empty($_POST['anti_spam_links_nofollowlinks']) && $_POST['anti_spam_links_nofollowlinks'] <= $_POST['anti_spam_links_newbielinks'])
+ $_POST['anti_spam_links_nofollowlinks'] = $_POST['anti_spam_links_newbielinks'] + 1;
+}
+
+/**
+ * Validate post submission against link rules.
+ *
+ * @param array $post_errors
+ * @param array $minor_errors
+ * @param string $form_message
+ * @param string $form_subject
+ * @return void
+ */
+function AntiSpamLinks_PostErrors(&$post_errors, &$minor_errors, $form_message, $form_subject)
+{
+ $poster = antiSpamLinks_get_target_poster_context();
+
+ if (!antiSpamLinks_should_block_links($poster['id_member'], $poster['posts']))
+ return;
+
+ if (!antiSpamLinks_message_has_external_link($form_message))
+ return;
+
+ antiSpamLinks_load_text();
+
+ $error_key = antiSpamLinks_get_block_error_key($poster['id_member']);
+
+ if (!in_array($error_key, $post_errors, true))
+ $post_errors[] = $error_key;
+}
+
+/**
+ * Adjust preview output for nonactive/nofollow rendering.
+ *
+ * @param string $form_message
+ * @param string $form_subject
+ * @return void
+ */
+function AntiSpamLinks_PreviewPost(&$form_message, &$form_subject)
+{
+ global $context;
+
+ if (empty($context['preview_message']))
+ return;
+
+ $poster = antiSpamLinks_get_target_poster_context();
+ $context['preview_message'] = antiSpamLinks_apply_to_html($context['preview_message'], $poster['id_member'], $poster['posts']);
+}
+
+/**
+ * Validate quick-edit submissions.
+ *
+ * @param array $post_errors
+ * @param array $row
+ * @return void
+ */
+function AntiSpamLinks_JavaScriptModify(&$post_errors, $row)
+{
+ global $context;
+
+ $poster_id = empty($row['id_member']) ? 0 : (int) $row['id_member'];
+ $posts = $poster_id === 0 ? 0 : antiSpamLinks_get_member_posts($poster_id);
+
+ $context['anti_spam_links_jsmodify'] = array(
+ 'poster_id' => $poster_id,
+ 'posts' => $posts,
+ );
+
+ if (!isset($_POST['message']))
+ return;
+
+ if (!antiSpamLinks_should_block_links($poster_id, $posts))
+ return;
+
+ if (!antiSpamLinks_message_has_external_link($_POST['message']))
+ return;
+
+ antiSpamLinks_load_text();
+
+ $error_key = antiSpamLinks_get_block_error_key($poster_id);
+ $context['anti_spam_links_jsmodify_errors'] = array($error_key);
+
+ if (!in_array($error_key, $post_errors, true))
+ $post_errors[] = $error_key;
+}
+
+/**
+ * Mark quick-edit body errors and post-process returned HTML.
+ *
+ * @return void
+ */
+function AntiSpamLinks_JavaScriptModifyXml()
+{
+ global $context;
+
+ if (!empty($context['message']['body']) && !empty($context['anti_spam_links_jsmodify']))
+ {
+ $context['message']['body'] = antiSpamLinks_apply_to_html(
+ $context['message']['body'],
+ $context['anti_spam_links_jsmodify']['poster_id'],
+ $context['anti_spam_links_jsmodify']['posts']
+ );
+ }
+
+ if (!empty($context['message']) && !empty($context['anti_spam_links_jsmodify_errors']))
+ $context['message']['error_in_body'] = true;
+}
+
+/**
+ * Post-process displayed message HTML.
+ *
+ * @param array $output
+ * @param array $message
+ * @param int $counter
+ * @return void
+ */
+function AntiSpamLinks_PrepareDisplayContext(&$output, &$message, $counter)
+{
+ $poster_id = !empty($message['id_member']) ? (int) $message['id_member'] : 0;
+ $posts = !empty($output['member']['real_posts']) ? (int) $output['member']['real_posts'] : 0;
+
+ $output['body'] = antiSpamLinks_apply_to_html($output['body'], $poster_id, $posts);
+}
+
+/**
+ * Post-process previous topic summary posts on the editor screen.
+ *
+ * @param array $row
+ * @return void
+ */
+function AntiSpamLinks_PreviousPost(&$row)
+{
+ $poster_id = empty($row['id_member']) ? 0 : (int) $row['id_member'];
+ $posts = $poster_id === 0 ? 0 : antiSpamLinks_get_member_posts($poster_id);
+
+ $row['body'] = antiSpamLinks_apply_to_html($row['body'], $poster_id, $posts);
+}
+
+/**
+ * Post-process member signatures after SMF has parsed them.
+ *
+ * @param array $member
+ * @param int $user
+ * @param bool $display_custom_fields
+ * @return void
+ */
+function AntiSpamLinks_MemberContext(&$member, $user, $display_custom_fields)
+{
+ if (empty($member['signature']))
+ return;
+
+ $member['signature'] = antiSpamLinks_apply_to_html(
+ $member['signature'],
+ empty($member['id']) ? 0 : (int) $member['id'],
+ isset($member['real_posts']) ? (int) $member['real_posts'] : 0
+ );
+}
+
+/**
+ * Load language strings once.
+ *
+ * @return void
+ */
+function antiSpamLinks_load_text()
+{
+ static $loaded = false;
+
+ if ($loaded)
+ return;
+
+ loadLanguage('AntiSpamLinks');
+ $loaded = true;
+}
+
+/**
+ * Resolve the poster context that should be used for validation/preview.
+ *
+ * @return array
+ */
+function antiSpamLinks_get_target_poster_context()
+{
+ global $user_info;
+
+ if (!empty($_REQUEST['msg']))
+ return antiSpamLinks_get_message_poster_context((int) $_REQUEST['msg']);
+
+ return array(
+ 'id_member' => empty($user_info['is_guest']) ? (int) $user_info['id'] : 0,
+ 'posts' => empty($user_info['is_guest']) ? (int) $user_info['posts'] : 0,
+ );
+}
+
+/**
+ * Load the original poster context for a message.
+ *
+ * @param int $message_id
+ * @return array
+ */
+function antiSpamLinks_get_message_poster_context($message_id)
+{
+ global $smcFunc;
+ static $cache = array();
+
+ $message_id = (int) $message_id;
+ if (isset($cache[$message_id]))
+ return $cache[$message_id];
+
+ $cache[$message_id] = array(
+ 'id_member' => 0,
+ 'posts' => 0,
+ );
+
+ if (empty($message_id))
+ return $cache[$message_id];
+
+ $request = $smcFunc['db_query']('', '
+ SELECT IFNULL(m.id_member, 0) AS id_member, IFNULL(mem.posts, 0) AS posts
+ FROM {db_prefix}messages AS m
+ LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member)
+ WHERE m.id_msg = {int:id_msg}
+ LIMIT 1',
+ array(
+ 'id_msg' => $message_id,
+ )
+ );
+
+ if ($smcFunc['db_num_rows']($request) !== 0)
+ {
+ $row = $smcFunc['db_fetch_assoc']($request);
+ $cache[$message_id] = array(
+ 'id_member' => empty($row['id_member']) ? 0 : (int) $row['id_member'],
+ 'posts' => empty($row['posts']) ? 0 : (int) $row['posts'],
+ );
+ }
+
+ $smcFunc['db_free_result']($request);
+
+ return $cache[$message_id];
+}
+
+/**
+ * Load a member's post count with a small request cache.
+ *
+ * @param int $member_id
+ * @return int
+ */
+function antiSpamLinks_get_member_posts($member_id)
+{
+ global $smcFunc;
+ static $cache = array(0 => 0);
+
+ $member_id = (int) $member_id;
+ if (isset($cache[$member_id]))
+ return $cache[$member_id];
+
+ $cache[$member_id] = 0;
+
+ if (empty($member_id))
+ return 0;
+
+ $request = $smcFunc['db_query']('', '
+ SELECT posts
+ FROM {db_prefix}members
+ WHERE id_member = {int:id_member}
+ LIMIT 1',
+ array(
+ 'id_member' => $member_id,
+ )
+ );
+
+ if ($smcFunc['db_num_rows']($request) !== 0)
+ {
+ list ($cache[$member_id]) = $smcFunc['db_fetch_row']($request);
+ $cache[$member_id] = (int) $cache[$member_id];
+ }
+
+ $smcFunc['db_free_result']($request);
+
+ return $cache[$member_id];
+}
+
+/**
+ * Determine whether posting external links should be blocked.
+ *
+ * @param int $poster_id
+ * @param int $posts
+ * @return bool
+ */
+function antiSpamLinks_should_block_links($poster_id, $posts)
+{
+ global $modSettings;
+
+ $poster_id = (int) $poster_id;
+ $posts = (int) $posts;
+
+ if ($poster_id === 0)
+ return !empty($modSettings['anti_spam_links_guests']) && (int) $modSettings['anti_spam_links_guests'] === 1;
+
+ return !empty($modSettings['anti_spam_links_nolinks']) && $posts < (int) $modSettings['anti_spam_links_nolinks'];
+}
+
+/**
+ * Get the appropriate error key for posting restrictions.
+ *
+ * @param int $poster_id
+ * @return string
+ */
+function antiSpamLinks_get_block_error_key($poster_id)
+{
+ return (int) $poster_id === 0 ? 'anti_spam_links_nolinks_guest' : 'anti_spam_links_nolinks_member';
+}
+
+/**
+ * Check whether a raw message contains at least one external link after BBC parsing.
+ *
+ * @param string $message
+ * @return bool
+ */
+function antiSpamLinks_message_has_external_link($message)
+{
+ $parsed = parse_bbc($message, false);
+
+ if ($parsed === '' || stripos($parsed, ']*\bhref="([^"]+)"[^>]*>~i', $parsed, $matches))
+ return false;
+
+ foreach ($matches[1] as $url)
+ if (!antiSpamLinks_is_internal_url($url))
+ return true;
+
+ return false;
+}
+
+/**
+ * Apply nonactive/nofollow behavior to parsed HTML.
+ *
+ * @param string $html
+ * @param int $poster_id
+ * @param int $posts
+ * @return string
+ */
+function antiSpamLinks_apply_to_html($html, $poster_id, $posts)
+{
+ global $txt;
+
+ $policy = antiSpamLinks_get_render_policy($poster_id, $posts);
+
+ if ($policy['mode'] === 'none' || $html === '' || stripos($html, ']*\bhref="([^"]+)"[^>]*>.*?~i',
+ function ($matches) use ($policy, $txt) {
+ $url = $matches[1];
+ if (antiSpamLinks_is_internal_url($url))
+ return $matches[0];
+
+ return $txt['anti_spam_links_newbielink'] . $url . ' ' . $txt['anti_spam_links_nonactive'] . '';
+ },
+ $html
+ );
+ }
+
+ return preg_replace_callback(
+ '~]*\bhref="([^"]+)"[^>]*>.*?~i',
+ function ($matches) use ($policy, $txt) {
+ $url = $matches[1];
+ $link = $matches[0];
+
+ if (antiSpamLinks_is_internal_url($url))
+ return $link;
+
+ if (preg_match('~\brel="([^"]*)"~i', $link, $rel_match))
+ {
+ $rels = preg_split('~\s+~', trim($rel_match[1]));
+ if (!in_array('nofollow', $rels, true))
+ $rels[] = 'nofollow';
+
+ $link = preg_replace('~\brel="[^"]*"~i', 'rel="' . implode(' ', array_filter($rels)) . '"', $link, 1);
+ }
+ else
+ $link = preg_replace('~' . $txt['anti_spam_links_nofollow'] . '';
+ },
+ $html
+ );
+}
+
+/**
+ * Decide which rendering policy applies to a poster.
+ *
+ * @param int $poster_id
+ * @param int $posts
+ * @return array
+ */
+function antiSpamLinks_get_render_policy($poster_id, $posts)
+{
+ global $modSettings, $txt;
+
+ antiSpamLinks_load_text();
+
+ $poster_id = (int) $poster_id;
+ $posts = (int) $posts;
+
+ if ($poster_id === 0)
+ {
+ $guest_mode = empty($modSettings['anti_spam_links_guests']) ? 0 : (int) $modSettings['anti_spam_links_guests'];
+
+ if ($guest_mode === 2)
+ return array('mode' => 'nonactive', 'title' => $txt['anti_spam_links_guests_nonactive_info']);
+ if ($guest_mode === 3)
+ return array('mode' => 'nofollow', 'title' => $txt['anti_spam_links_guests_nofollow_info']);
+
+ return array('mode' => 'none', 'title' => '');
+ }
+
+ if (!empty($modSettings['anti_spam_links_newbielinks']) && $posts < (int) $modSettings['anti_spam_links_newbielinks'])
+ return array(
+ 'mode' => 'nonactive',
+ 'title' => sprintf($txt['anti_spam_links_newbielinks_info'], (int) $modSettings['anti_spam_links_newbielinks']),
+ );
+
+ if (!empty($modSettings['anti_spam_links_nofollowlinks']) && $posts < (int) $modSettings['anti_spam_links_nofollowlinks'])
+ return array(
+ 'mode' => 'nofollow',
+ 'title' => sprintf($txt['anti_spam_links_nofollowlinks_info'], (int) $modSettings['anti_spam_links_nofollowlinks']),
+ );
+
+ return array('mode' => 'none', 'title' => '');
+}
+
+/**
+ * Check whether a URL should be treated as internal to the forum.
+ *
+ * @param string $url
+ * @return bool
+ */
+function antiSpamLinks_is_internal_url($url)
+{
+ global $boardurl;
+
+ $url = trim(htmlspecialchars_decode((string) $url, ENT_QUOTES));
+
+ if ($url === '')
+ return true;
+
+ if ($url[0] === '#' || $url[0] === '/')
+ return true;
+
+ if (strpos($url, './') === 0 || strpos($url, '../') === 0)
+ return true;
+
+ $normalized_boardurl = rtrim((string) $boardurl, '/');
+ if ($normalized_boardurl !== '' && stripos($url, $normalized_boardurl) === 0)
+ return true;
+
+ $board_parts = @parse_url($boardurl);
+ $url_parts = @parse_url($url);
+
+ if (!empty($board_parts['host']) && !empty($url_parts['host']) && strtolower($board_parts['host']) === strtolower($url_parts['host']))
+ return true;
+
+ return false;
+}
+
diff --git a/anti-spam-links/SMF-2.1/Themes/default/languages/AntiSpamLinks.english.php b/anti-spam-links/SMF-2.1/Themes/default/languages/AntiSpamLinks.english.php
new file mode 100644
index 0000000..eb363e6
--- /dev/null
+++ b/anti-spam-links/SMF-2.1/Themes/default/languages/AntiSpamLinks.english.php
@@ -0,0 +1,24 @@
+(Use 0 to disable)';
+$txt['anti_spam_links_guests'] = 'Guests...';
+$txt['anti_spam_links_guests_opt0'] = '(disable mod for guests)';
+$txt['anti_spam_links_guests_opt1'] = 'cannot post links';
+$txt['anti_spam_links_guests_opt2'] = 'links are shown [nonactive]';
+$txt['anti_spam_links_guests_opt3'] = 'links are set [nofollow]';
+
diff --git a/anti-spam-links/SMF-2.1/Themes/default/languages/AntiSpamLinks.vietnamese.php b/anti-spam-links/SMF-2.1/Themes/default/languages/AntiSpamLinks.vietnamese.php
new file mode 100644
index 0000000..fd0cf95
--- /dev/null
+++ b/anti-spam-links/SMF-2.1/Themes/default/languages/AntiSpamLinks.vietnamese.php
@@ -0,0 +1,24 @@
+(Nhap 0 de tat)';
+$txt['anti_spam_links_guests'] = 'Khach...';
+$txt['anti_spam_links_guests_opt0'] = '(tat mod cho khach)';
+$txt['anti_spam_links_guests_opt1'] = 'khong duoc dang lien ket';
+$txt['anti_spam_links_guests_opt2'] = 'lien ket hien [nonactive]';
+$txt['anti_spam_links_guests_opt3'] = 'lien ket duoc gan [nofollow]';
+
diff --git a/anti-spam-links/install.php b/anti-spam-links/install.php
new file mode 100644
index 0000000..bce4df3
--- /dev/null
+++ b/anti-spam-links/install.php
@@ -0,0 +1,89 @@
+ dirname(__FILE__) . '/SMF-2.1/Sources/AntiSpamLinks.php',
+ 'destination' => $sourcedir . '/AntiSpamLinks.php',
+ ),
+ array(
+ 'source' => dirname(__FILE__) . '/SMF-2.1/Themes/default/languages/AntiSpamLinks.english.php',
+ 'destination' => $boarddir . '/Themes/default/languages/AntiSpamLinks.english.php',
+ ),
+ array(
+ 'source' => dirname(__FILE__) . '/SMF-2.1/Themes/default/languages/AntiSpamLinks.vietnamese.php',
+ 'destination' => $boarddir . '/Themes/default/languages/AntiSpamLinks.vietnamese.php',
+ ),
+);
+
+if ($manual)
+{
+ foreach ($package_files as $file)
+ {
+ if (!file_exists($file['source']))
+ continue;
+
+ @copy($file['source'], $file['destination']);
+ @chmod($file['destination'], 0644);
+ }
+
+ $hooks = array(
+ array('integrate_load_theme', 'AntiSpamLinks_LoadTheme'),
+ array('integrate_modify_post_settings', 'AntiSpamLinks_ModifyPostSettings'),
+ array('integrate_save_post_settings', 'AntiSpamLinks_SavePostSettings'),
+ array('integrate_post_errors', 'AntiSpamLinks_PostErrors'),
+ array('integrate_preview_post', 'AntiSpamLinks_PreviewPost'),
+ array('integrate_post_JavascriptModify', 'AntiSpamLinks_JavaScriptModify'),
+ array('integrate_jsmodify_xml', 'AntiSpamLinks_JavaScriptModifyXml'),
+ array('integrate_prepare_display_context', 'AntiSpamLinks_PrepareDisplayContext'),
+ array('integrate_getTopic_previous_post', 'AntiSpamLinks_PreviousPost'),
+ array('integrate_member_context', 'AntiSpamLinks_MemberContext'),
+ );
+
+ foreach ($hooks as $hook)
+ add_integration_function($hook[0], $hook[1], true, '$sourcedir/AntiSpamLinks.php');
+}
+
+$defaults = array(
+ 'anti_spam_links_nolinks' => 0,
+ 'anti_spam_links_newbielinks' => 0,
+ 'anti_spam_links_nofollowlinks' => 0,
+ 'anti_spam_links_guests' => 0,
+);
+
+$to_update = array();
+foreach ($defaults as $setting => $value)
+{
+ if (!isset($modSettings[$setting]))
+ $to_update[$setting] = $value;
+}
+
+if (!empty($to_update))
+ updateSettings($to_update);
+
+if (isset($cacheAPI) && is_object($cacheAPI) && is_callable(array($cacheAPI, 'cleanCache')))
+ $cacheAPI->cleanCache();
+
+if ($manual)
+{
+ header('Content-Type: text/plain; charset=UTF-8');
+ echo "Anti-Spam Links for SMF 2.1.x installed.\n";
+}
+
diff --git a/anti-spam-links/license.txt b/anti-spam-links/license.txt
index db80f4c..00e4e8b 100644
--- a/anti-spam-links/license.txt
+++ b/anti-spam-links/license.txt
@@ -1,24 +1,20 @@
-Copyright (c) 2018, Simple Machines
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
- * Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above copyright
- notice, this list of conditions and the following disclaimer in the
- documentation and/or other materials provided with the distribution.
- * Neither the name of the nor the
- names of its contributors may be used to endorse or promote products
- derived from this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY
-DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
-ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
+BSD 3-Clause License
+
+Acknowledgements
+
+This package is based on the original Anti-Spam Links modification for SMF:
+https://custom.simplemachines.org/mods/index.php?mod=2404
+
+SMF 2.1.x port and rewrite by:
+Thanh Nguyen (hoidulich.com)
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+
+3. Neither the name of Thanh Nguyen nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED.
+
diff --git a/anti-spam-links/modification.xml b/anti-spam-links/modification.xml
deleted file mode 100644
index 4eeae2a..0000000
--- a/anti-spam-links/modification.xml
+++ /dev/null
@@ -1,374 +0,0 @@
-
-
-
- CustomizeTeam:AntiSpamLinks
- 1.0
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- $txt['anti_spam_links_zero_disable']),
- array('int', 'anti_spam_links_newbielinks', 'subtext' => $txt['anti_spam_links_zero_disable']),
- array('int', 'anti_spam_links_nofollowlinks', 'subtext' => $txt['anti_spam_links_zero_disable']),
- array('select', 'anti_spam_links_guests', array(&$txt['anti_spam_links_guests_opt0'], &$txt['anti_spam_links_guests_opt1'], &$txt['anti_spam_links_guests_opt2'], &$txt['anti_spam_links_guests_opt3'])),
- '',
-]]>
-
-
-
-
- = (int) $save_vars['anti_spam_links_nofollowlinks'])
- (int) $save_vars['anti_spam_links_nofollowlinks'] = (int) $save_vars['anti_spam_links_newbielinks'] + 1;
-
- // End of Anti-Spam-Links mod
-
-]]>
-
-
-
-
-
-
- $_REQUEST['msg'],
- )
- );
- // Error, so treat them as a guest.
- if ($smcFunc['db_num_rows']($request) == 0)
- $poster_id = $posts = 0;
-
- list($poster_id, $posts) = $smcFunc['db_fetch_row']($request);
- $smcFunc['db_free_result']($request);
- }
- else
- {
- $poster_id = $user_info['is_guest'] ? 0 : $user_info['id'];
- $posts = $user_info['is_guest'] ? 0 : $user_info['posts'];
- }
- // Are we editing our own post
- $own_post = $poster_id != $user_info['id'];
-
- global $boardurl;
- // Ugly but necessary.
- // Takes into account moderation when deciding whether to show the post error.
- if((($own_post && $user_info['is_guest']) || (!$own_post && empty($poster_id)) && $modSettings['anti_spam_links_guests'] == 1) || ((($own_post && !$user_info['is_guest']) || !$own_post && !empty($poster_id)) && !empty($modSettings['anti_spam_links_nolinks']) && $posts < $modSettings['anti_spam_links_nolinks']))
- // Your not allowed to use links, so give them an error.
- if (preg_match('~
-
-
-
-
-
-
-
-
-
-
- $poster_id,
- )
- );
- // Error, so treat them as a guest.
- if ($smcFunc['db_num_rows']($request) == 0)
- $poster_id = $posts = 0;
-
- list($posts) = $smcFunc['db_fetch_row']($request);
- $smcFunc['db_free_result']($request);
- }
- else
- $posts = 0;
- }
- else
- // Editing own post
- $posts = $user_info['is_guest'] ? 0 : $user_info['posts'];
-
- global $boardurl;
- // Ugly but necessary.
- // Takes into account moderation when deciding whether to show the post error.
- if((($own_post && $user_info['is_guest']) || (!$own_post && empty($poster_id)) && $modSettings['anti_spam_links_guests'] == 1) || ((($own_post && !$user_info['is_guest']) || !$own_post && !empty($poster_id)) && !empty($modSettings['anti_spam_links_nolinks']) && $posts < $modSettings['anti_spam_links_nolinks']))
- // Your not allowed to use links, so give them an error.
- if (preg_match('~
-
-
-
-
-
-
-
-
-
-
- $poster_id,
- )
- );
- // Error, so treat them as a guest.
- if ($smcFunc['db_num_rows']($request) == 0)
- $poster_id = $posts = 0;
-
- list($posts) = $smcFunc['db_fetch_row']($request);
- $smcFunc['db_free_result']($request);
- }
- else
- $posts = 0;
- }
- else
- // Editing own post
- $posts = $user_info['is_guest'] ? 0 : $user_info['posts'];
-
- global $boardurl;
- // Ugly but necessary.
- // Takes into account moderation when deciding whether to show the post error.
- if((($own_post && $user_info['is_guest']) || (!$own_post && empty($poster_id)) && $modSettings['anti_spam_links_guests'] == 1) || ((($own_post && !$user_info['is_guest']) || !$own_post && !empty($poster_id)) && !empty($modSettings['anti_spam_links_nolinks']) && $posts < $modSettings['anti_spam_links_nolinks']))
- // Your not allowed to use links, so give them an error.
- if (preg_match('~
-
-
-
-
- in_array('no_message', $post_errors) || in_array('long_message', $post_errors),
-]]>
- in_array('no_message', $post_errors) || in_array('long_message', $post_errors) || in_array('anti_spam_links_nolinks_member', $post_errors) || in_array('anti_spam_links_nolinks_guest', $post_errors),
-]]>
-
-
-
-
-
-
-
-
-
-
- ' ', "\r" => '', "\n" => '
', '
' => '
', '
' => "\n"));
-]]>
- ]*?)>(?:.*?)~i', $txt['anti_spam_links_newbielink'] . '$1 ' . $txt['anti_spam_links_nonactive'] . '', $message);
-
- // Links get rel="nofollow" so no page rank for you.
- elseif ((($poster_id === 0 && $modSettings['anti_spam_links_guests'] == 3) || (!empty($poster_id) && !empty($modSettings['anti_spam_links_nofollowlinks']) && $posts != '' && $posts < $modSettings['anti_spam_links_nofollowlinks'])) && strpos($message, ']*?)\>(?:.*?))~i', '$1 rel="nofollow"$2 ' . $txt['anti_spam_links_nofollow'] . '', $message);
-
- // End of Anti-Spam-Links mod
-]]>
-
-
-
-
-
-
- (Use 0 to disable)';
-$txt['anti_spam_links_guests'] = 'Guests... ';
-$txt['anti_spam_links_guests_opt0'] = '(disable mod for guests)';
-$txt['anti_spam_links_guests_opt1'] = 'can not post links';
-$txt['anti_spam_links_guests_opt2'] = 'links are shown [nonactive]';
-$txt['anti_spam_links_guests_opt3'] = 'links are set [nofollow]';
-]]>
-
-
-
-
-
- (Use 0 to disable)';
-$txt['anti_spam_links_guests'] = 'Guests... ';
-$txt['anti_spam_links_guests_opt0'] = '(disable mod for guests)';
-$txt['anti_spam_links_guests_opt1'] = 'can not post links';
-$txt['anti_spam_links_guests_opt2'] = 'links are shown [nonactive]';
-$txt['anti_spam_links_guests_opt3'] = 'links are set [nofollow]';
-]]>
-
-
-
\ No newline at end of file
diff --git a/anti-spam-links/modificationrc4.xml b/anti-spam-links/modificationrc4.xml
deleted file mode 100644
index 2ca925e..0000000
--- a/anti-spam-links/modificationrc4.xml
+++ /dev/null
@@ -1,374 +0,0 @@
-
-
-
- CustomizeTeam:AntiSpamLinks
- 1.0
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- $txt['anti_spam_links_zero_disable']),
- array('int', 'anti_spam_links_newbielinks', 'subtext' => $txt['anti_spam_links_zero_disable']),
- array('int', 'anti_spam_links_nofollowlinks', 'subtext' => $txt['anti_spam_links_zero_disable']),
- array('select', 'anti_spam_links_guests', array(&$txt['anti_spam_links_guests_opt0'], &$txt['anti_spam_links_guests_opt1'], &$txt['anti_spam_links_guests_opt2'], &$txt['anti_spam_links_guests_opt3'])),
- '',
-]]>
-
-
-
-
- = (int) $save_vars['anti_spam_links_nofollowlinks'])
- (int) $save_vars['anti_spam_links_nofollowlinks'] = (int) $save_vars['anti_spam_links_newbielinks'] + 1;
-
- // End of Anti-Spam-Links mod
-
-]]>
-
-
-
-
-
-
- $_REQUEST['msg'],
- )
- );
- // Error, so treat them as a guest.
- if ($smcFunc['db_num_rows']($request) == 0)
- $poster_id = $posts = 0;
-
- list($poster_id, $posts) = $smcFunc['db_fetch_row']($request);
- $smcFunc['db_free_result']($request);
- }
- else
- {
- $poster_id = $user_info['is_guest'] ? 0 : $user_info['id'];
- $posts = $user_info['is_guest'] ? 0 : $user_info['posts'];
- }
- // Are we editing our own post
- $own_post = $poster_id != $user_info['id'];
-
- global $boardurl;
- // Ugly but necessary.
- // Takes into account moderation when deciding whether to show the post error.
- if((($own_post && $user_info['is_guest']) || (!$own_post && empty($poster_id)) && $modSettings['anti_spam_links_guests'] == 1) || ((($own_post && !$user_info['is_guest']) || !$own_post && !empty($poster_id)) && !empty($modSettings['anti_spam_links_nolinks']) && $posts < $modSettings['anti_spam_links_nolinks']))
- // Your not allowed to use links, so give them an error.
- if (preg_match('~
-
-
-
-
-
-
-
-
-
-
- $poster_id,
- )
- );
- // Error, so treat them as a guest.
- if ($smcFunc['db_num_rows']($request) == 0)
- $poster_id = $posts = 0;
-
- list($posts) = $smcFunc['db_fetch_row']($request);
- $smcFunc['db_free_result']($request);
- }
- else
- $posts = 0;
- }
- else
- // Editing own post
- $posts = $user_info['is_guest'] ? 0 : $user_info['posts'];
-
- global $boardurl;
- // Ugly but necessary.
- // Takes into account moderation when deciding whether to show the post error.
- if((($own_post && $user_info['is_guest']) || (!$own_post && empty($poster_id)) && $modSettings['anti_spam_links_guests'] == 1) || ((($own_post && !$user_info['is_guest']) || !$own_post && !empty($poster_id)) && !empty($modSettings['anti_spam_links_nolinks']) && $posts < $modSettings['anti_spam_links_nolinks']))
- // Your not allowed to use links, so give them an error.
- if (preg_match('~
-
-
-
-
-
-
-
-
-
-
- $poster_id,
- )
- );
- // Error, so treat them as a guest.
- if ($smcFunc['db_num_rows']($request) == 0)
- $poster_id = $posts = 0;
-
- list($posts) = $smcFunc['db_fetch_row']($request);
- $smcFunc['db_free_result']($request);
- }
- else
- $posts = 0;
- }
- else
- // Editing own post
- $posts = $user_info['is_guest'] ? 0 : $user_info['posts'];
-
- global $boardurl;
- // Ugly but necessary.
- // Takes into account moderation when deciding whether to show the post error.
- if((($own_post && $user_info['is_guest']) || (!$own_post && empty($poster_id)) && $modSettings['anti_spam_links_guests'] == 1) || ((($own_post && !$user_info['is_guest']) || !$own_post && !empty($poster_id)) && !empty($modSettings['anti_spam_links_nolinks']) && $posts < $modSettings['anti_spam_links_nolinks']))
- // Your not allowed to use links, so give them an error.
- if (preg_match('~
-
-
-
-
- in_array('no_message', $post_errors) || in_array('long_message', $post_errors),
-]]>
- in_array('no_message', $post_errors) || in_array('long_message', $post_errors) || in_array('anti_spam_links_nolinks_member', $post_errors) || in_array('anti_spam_links_nolinks_guest', $post_errors),
-]]>
-
-
-
-
-
-
-
-
-
-
- ' ', "\r" => '', "\n" => '
', '
' => '
', '
' => "\n"));
-]]>
- ]*?)>(?:.*?)~i', $txt['anti_spam_links_newbielink'] . '$1 ' . $txt['anti_spam_links_nonactive'] . '', $message);
-
- // Links get rel="nofollow" so no page rank for you.
- elseif ((($poster_id === 0 && $modSettings['anti_spam_links_guests'] == 3) || (!empty($poster_id) && !empty($modSettings['anti_spam_links_nofollowlinks']) && $posts != '' && $posts < $modSettings['anti_spam_links_nofollowlinks'])) && strpos($message, ']*?)\>(?:.*?))~i', '$1 rel="nofollow"$2 ' . $txt['anti_spam_links_nofollow'] . '', $message);
-
- // End of Anti-Spam-Links mod
-]]>
-
-
-
-
-
-
- (Use 0 to disable)';
-$txt['anti_spam_links_guests'] = 'Guests... ';
-$txt['anti_spam_links_guests_opt0'] = '(disable mod for guests)';
-$txt['anti_spam_links_guests_opt1'] = 'can not post links';
-$txt['anti_spam_links_guests_opt2'] = 'links are shown [nonactive]';
-$txt['anti_spam_links_guests_opt3'] = 'links are set [nofollow]';
-]]>
-
-
-
-
-
- (Use 0 to disable)';
-$txt['anti_spam_links_guests'] = 'Guests... ';
-$txt['anti_spam_links_guests_opt0'] = '(disable mod for guests)';
-$txt['anti_spam_links_guests_opt1'] = 'can not post links';
-$txt['anti_spam_links_guests_opt2'] = 'links are shown [nonactive]';
-$txt['anti_spam_links_guests_opt3'] = 'links are set [nofollow]';
-]]>
-
-
-
\ No newline at end of file
diff --git a/anti-spam-links/package-info.xml b/anti-spam-links/package-info.xml
index 29cc22e..769e9e7 100644
--- a/anti-spam-links/package-info.xml
+++ b/anti-spam-links/package-info.xml
@@ -1,30 +1,48 @@
-
-
-
- Anti-Spam Links
- CustomizeTeam:AntiSpamLinks
- modification
- 1.0.2
-
- modification
-
-
- readme.txt
- modification.xml
- updateDatabase.php
-
-
-
- modification.xml
-
-
-
- readme.txt
- modificationrc4.xml
- updateDatabase.php
-
-
-
- modificationrc4.xml
-
-
\ No newline at end of file
+
+
+
+
+ CustomizeTeam:AntiSpamLinks
+ Anti-Spam Links for SMF 2.1.x
+ 2.1.0
+ modification
+
+
+ readme.txt
+ install.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ uninstall.php
+
+
+
+
+
+
diff --git a/anti-spam-links/readme.txt b/anti-spam-links/readme.txt
index acf998d..c7235ca 100644
--- a/anti-spam-links/readme.txt
+++ b/anti-spam-links/readme.txt
@@ -1,49 +1,36 @@
-[center][size=1.7em][color=purple][b]Anti-Spam Links[/b][/color][/size][/center]
-
-[hr]
-[b]Original Author:[/b] JBlaze | [b]Current Author:[/b] [url=https://www.simplemachines.org/community/index.php?action=profile;u=1][b]SMF Customization Team[/b][/url]
-[b]Supported Languages:[/b] English
-[url=https://custom.simplemachines.org/mods/index.php?mod=2404][b]Link to Mod[/b][/url] | [url=https://www.simplemachines.org/community/index.php?topic=364169.0][b]Mod Discussion[/b][/url] | [url=https://custom.simplemachines.org/mods/index.php?action=profile;u=1][b]Other SMF Customization Team Mods[/b][/url]
-[hr]
-
-[color=purple][b]Compatibility[/b][/color]
-For SMF 2.0.x
-
-[color=purple][b]Introduction[/b][/color]
-Adds some new settings based on post counts to help curb link spammers.
-
-[color=purple][b]Features[/b][/color]
-Adds some new settings based on post counts to help curb link spammers.
-- No links
-- Links set [nonactive] and with newbielink:
-- Links set [nofollow] so no pagerank
-- Guest setting
-EXCLUDES yoursite.com links, so only use on any part of *.yoursite.com ;)
-
-Translations for other languages are welcome. Please post the translated language strings in the Support Topic.
-
-[color=purple][b]Installation[/b][/color]
-Any previous versions of this mod MUST be uninstalled BEFORE installing this version.
-
-Simply install the package to install on the SMF Default Core Theme ONLY.
-
-Manual edits will be required for ALL themes (other than SMF Default Core Theme) which have a custom MessageIndex.template.php and/or Display.template.php
-
-If your theme has its own versions of the language files Modifications.english.php and/or Modifications.english-utf8.php, OR if you're using a language other than those supported (listed above) by the mod then you will need to copy the language strings into each custom version of those files.
-
-[b]Useful Links[/b]
-[url=https://docs.simplemachines.org/index.php?topic=402]Manual Installation Of Mods[/url]
-[url=https://www.simplemachines.org/community/index.php?topic=24110.0]How Do I Modify Files?[/url]
-
-[color=purple][b]Support[/b][/color]
-Please use the modification thread for support with this modification.
-
-[color=purple][b]Changelog[/b][/color]
-[tt]
-[b]Version 1.1 - 6/12/09[/b]
- Initial Release
-[/tt]
-
-
-Copyright (c) 2020, Simple Machines, under BSD 3-Clause License.
-All rights reserved.
\ No newline at end of file
+Anti-Spam Links for SMF 2.1.x
+================================
+
+This package ports the classic Anti-Spam Links mod from SMF 2.0.x to SMF 2.1.x.
+
+What it does
+------------
+- Blocks external links for low-post members if configured.
+- Rewrites external links as non-clickable text for low-post members if configured.
+- Adds rel="nofollow" to external links for low-post members if configured.
+- Supports guest-specific handling.
+- Applies to post preview, quick edit XML responses, displayed posts, topic summaries, and member signatures.
+
+How this port differs from the old 2.0.x package
+------------------------------------------------
+- Uses SMF 2.1 integration hooks instead of editing SMF core files directly.
+- Adds a Vietnamese language file in addition to English.
+- Keeps settings under:
+ Admin > Posts and Topics > Posts
+
+Settings
+--------
+- Post count under which members cannot post external links
+- Post count under which members external links are shown [nonactive]
+- Post count under which members external links are set [nofollow]
+- Guest behavior:
+ - disable mod for guests
+ - cannot post links
+ - links are shown [nonactive]
+ - links are set [nofollow]
+
+Notes
+-----
+- Internal links to your forum are excluded.
+- If both "nonactive" and "nofollow" thresholds are enabled, the package ensures the nofollow threshold stays above the nonactive threshold.
+
diff --git a/anti-spam-links/screenshots/admin-setting.png b/anti-spam-links/screenshots/admin-setting.png
new file mode 100644
index 0000000..6e8c6b8
Binary files /dev/null and b/anti-spam-links/screenshots/admin-setting.png differ
diff --git a/anti-spam-links/screenshots/link-converted.png b/anti-spam-links/screenshots/link-converted.png
new file mode 100644
index 0000000..1bd04cf
Binary files /dev/null and b/anti-spam-links/screenshots/link-converted.png differ
diff --git a/anti-spam-links/screenshots/post-blocked-link.png b/anti-spam-links/screenshots/post-blocked-link.png
new file mode 100644
index 0000000..a745238
Binary files /dev/null and b/anti-spam-links/screenshots/post-blocked-link.png differ
diff --git a/anti-spam-links/uninstall.php b/anti-spam-links/uninstall.php
new file mode 100644
index 0000000..a6e2393
--- /dev/null
+++ b/anti-spam-links/uninstall.php
@@ -0,0 +1,68 @@
+ null,
+ 'anti_spam_links_newbielinks' => null,
+ 'anti_spam_links_nofollowlinks' => null,
+ 'anti_spam_links_guests' => null,
+));
+
+$package_files = array(
+ $sourcedir . '/AntiSpamLinks.php',
+ $boarddir . '/Themes/default/languages/AntiSpamLinks.english.php',
+ $boarddir . '/Themes/default/languages/AntiSpamLinks.vietnamese.php',
+);
+
+if ($manual)
+{
+ foreach ($package_files as $file)
+ if (file_exists($file))
+ @unlink($file);
+}
+
+if (isset($cacheAPI) && is_object($cacheAPI) && is_callable(array($cacheAPI, 'cleanCache')))
+ $cacheAPI->cleanCache();
+
+if ($manual)
+{
+ header('Content-Type: text/plain; charset=UTF-8');
+ echo "Anti-Spam Links for SMF 2.1.x uninstalled.\n";
+}
+
diff --git a/anti-spam-links/updateDatabase.php b/anti-spam-links/updateDatabase.php
deleted file mode 100644
index c750ab2..0000000
--- a/anti-spam-links/updateDatabase.php
+++ /dev/null
@@ -1,20 +0,0 @@
- 0,
- 'anti_spam_links_newbielinks' => 0,
- 'anti_spam_links_nofollowlinks' => 0,
- 'anti_spam_links_guests' => 0,
-));
-
-?>
\ No newline at end of file