Записи Alex’а

Автор блога:
Все рубрики (4)

Улучшение messages.php для 1.6.* и 1.7.

Файл, который правим: /components/users/messages.php
Что требует улучшения:
SQL-запрос, отвечающий за выгрузку входящих сообщений (примерно 28 строка в 1.7RC и примерно 50 в 1.6.*):
Код PHP:
                
$sql = "SELECT m.*, m.senddate as fpubdate, m.from_id as sender_id, u.nickname as author, u.login as author_login, u.is_deleted, p.imageurl
                FROM cms_user_msg m
                LEFT JOIN cms_users u ON m.from_id = u.id
                LEFT JOIN cms_user_profiles p ON m.from_id = p.user_id
                WHERE m.to_id = $id
                ORDER BY senddate DESC
                LIMIT ".(($page-1)*$perpage).", $perpage";

Исправляем bbcode.lib.php (email_2html)

Файл, который мы правим: /includes/bbcode/bbcode.lib.php

При использование бб-тэгов ’email’ в комменатриях возникает следующая ошибка
Warning: call_user_func(bbcode::email_2html) [function.call-user-func]: First argument is expected to be a valid callback in /путь/includes/bbcode/bbcode.lib.php on line 981

Ошибка в том, что для email используется обработчик email_2html, однако его просто нет в этом файле.
Код PHP:
            'email' => array(
                    'handler' => 'email_2html',
                    'is_close' => false,
                    'lbr' => 0,
                    'rbr' => 0,
                    'ends' => array('*','align','code','video', 'audio', 'hide','h1','h2','h3','hr',
                        'list','php','quote','table','td','th','tr'),
                    'permission_top_level' => true,
                    'children' => array('b','color','email','font','i','img',
                        'nobb','s','size','sub','sup','tt','u')
                ),
Поэтому мы его быстро допишем:
Код PHP:
     function email_2html($elem) {
     return '<a href="mailto:'.$this -> get_html($elem['val']).'">'.$this -> get_html($elem['val']).'</a>';
     }
Вставить это код можно после любого из других обработчиков, например после:
Код PHP:
    // Функция - обработчик тега [u]
    function u_2html($elem) {
        return '<u>'.$this -> get_html($elem['val']).'</u>';
    }

Анти-спам (практическая часть)

Предыдущая теоретическая тут.

В этой статье рассмотрен наиболее простой способ ограждения юзеров от спама, а именно введение captcha (блокировка отправки сообщений) при жалобах от других пользователей. А также введение captcha при слишком частой отправке.

1. Итак, первое с чего нужно начать, добавить в таблицу с юзерам столбец
Spam_detected. Выполняем следующий SQL-запрос

Код PHP:
ALTER TABLE cms_users ADD COLUMN spam_detected INT NOT NULL AFTER is_deleted
Данное поле будет иметь в нашем случае целочисленный тип.
-100 – для администраторов и, например, модераторов, чтобы их случайно не зафильтровать.
0 – означает, что юзер не спаммер.
1 – означает, что есть подозрения, что юзер спаммер (мера воздействия отсутствует)
2 – считаем, что юзер спаммер (мера воздействия: включаем капачи)
4 – считаем, что юзер злостный спаммер (мера воздействия: отключаем возможность писать сообщения)

По дефолту полю spam_detected  можно назначить либо 0, либо 1.

2. Создаем таблицу cms_user_abuse (жалобы одного пользователя на другого)
(id, from_user, to_user, spamdate, msg_id, type, description).
from_user – кто пожаловался
to_user – на кого пожаловались
spamdate – время жалобы
msg_id – id мессаги, которую расценили, как спам.
Type – это тип жалобы, сразу можно предусмотреть например жалобы не только на спам, но и на мат и т.д.
description – в этом поле также можно принимать описание юзера, на что он конкретно жалуется.
Выполняем следующий SQL-запрос:

Код PHP:
CREATE TABLE IF NOT EXISTS `cms_user_abuse` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `from_user` int(11) NOT NULL,
  `to_user` int (11) NOT NULL,
  `spamdate` datetime NOT NULL,
  `msg_id` int(11) NOT NULL,
  `type` int(11) NOT NULL DEFAULT '1',
  `description` varchar(200) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=cp1251_general_ci AUTO_INCREMENT=1

3. (необязательно, дабы не перегружать статью все операции над таблицей убраны) Создаем таблицу cms_user_spammer (id, id_user, reg_mail, date_spam, ip, description) Тут, думаю, все понятно.
Вообще, стоит добавить, что спаммеры бывает 3-ёх типов.
1) Аккаутн специально созданный для спама
2) Пользователя взломали
3) Пользователь-идиот, который верит в письма счастья и т.д.
Ясно, что первых сразу стоит банить, отсылать письмо провайдеру с указанием IP и удалять из таблицы. 2-ой и 3-ий тип нужно заносить в таблицу, чтобы в случае повтора их банить.

4. Добавляем каждому пользователю кнопку для жалоб и обработчики.
4.1. Кнопка в профиль.
4.2. Кнопку к каждой мессаге.
4.3. Кнопка к комментариям.

В данной статье мы ограничимся только кнопкой к мессагам, так как она самая сложная, имхо. Все остальные делают аналогично.
Остальное делается аналогично.
4.2.1. Начнем с user/frontend.php
Допишем туда:
Код PHP:
 
/////////////////////////////// SMAP /////////////////////////////////////////////////////////////////////// 
if ($do=='spammessage'){
	if (usrCheckAuth()){
		$sql = "SELECT * FROM cms_user_msg WHERE id = '$id' LIMIT 1";
		$result = $inDB->query($sql) ;
		if ($inDB->num_rows($result)){
			$msg = $inDB->fetch_assoc($result);
			if ($msg['to_id']==$inUser->id){			
		//записываем сообщение в таблицу cms_user_abuse
				$message_admin = $msg['message'];
				$sql = "INSERT INTO cms_user_abuse (from_user, to_user, spamdate, msg_id, description) 
								VALUES (".$msg['to_id'].", ".$msg['from_id'].", NOW(), '$id', '$message_admin' )";																
				$inDB->query($sql) ;
		//повышаем уровень spam_detected для отославшего юзера
				$inDB->query("UPDATE cms_users SET spam_detected = spam_detected + 1 WHERE id = ".$msg['from_id']." LIMIT 1");
				$message_admin ="СПАМ!
		[url=/users/".$msg['from_id']."/messages-sent.html]Тут[/url] можно посмотреть исходящие сообщения потенциального спаммера!
		--------------------------------------------
		".$msg['message']."
		--------------------------------------------";
		//отсылаем уведомлению админу
		$sql = "INSERT INTO cms_user_msg (to_id, from_id, senddate, is_new, message)
								VALUES (1, -2, NOW(), 1, '$message_admin')";										
						$inDB->query($sql);
		//удаляем мессагу из входящих
				$inDB->query("DELETE FROM cms_user_msg WHERE id = '$id' LIMIT 1") ;
			}
		}
	}
	header('location:'.$_SERVER['HTTP_REFERER']);
}//do
///////////////////////////END SPAM//////////////////// 
4.2.2. Допишем в user/router.php
Код PHP:
		//antispam
// RewriteRule ^users/([0-9]*)/spammsg.html$ /index.php?view=users&do=spammessage&id=$1
        $routes[] = array(
                            '_uri'  => '/^users\/spammsg([0-9]+).html$/i',
                            'do'    => 'spammessage',
                            1       => 'id'
                         );
		
		// end antispam 
4.2.3 Дополним мессаги кнопкой с жалобой на спам.
В файле User/messages.php изменяем.
Код PHP:
if ($opt=='in'){
								if ($record['sender_id']>0){
									echo '<td class="usr_msg_title" width="80" align="right"><a class="msg_reply" href="/users/'.$record['from_id'].'/reply'.$record['id'].'.html">'.$_LANG['REPLY'].'</a></td>';
									echo '<td class="usr_msg_title" width="80" align="right"><a class="msg_history" href="/users/'.$id.'/messages-history'.$record['from_id'].'.html">'.$_LANG['HISTORY'].'</a></td>';
								////////////////////Дописано АНТИ-СПАМ	
							        echo '<td class="usr_msg_title" width="90" align="right"><a class="msg_spam" href="/users/spammsg'.$record['id'].'.html">Этом спам!</a></td>';
////////////////////Дописано АНТИ-СПАМ (конец)	
								}
							}
Опять же вставить можно в любом месте.
4.2.4 В css добавить стиль для msg_spam. Это уже по вкусу.

5. Производим действия над юзерами, подозрительными на спам.
5.1. Выводим captcha (или блокирует отправку) при отправке личных сообщений.
А также если юзер пишет чаще, чем раз в минуту, будем показывать captcha.
Для этого в файле user/frontend.php ищем процедуру if ($do==&#8217;sendmessage&#8217;){
И правим ее, чтобы она выглядела так.
Код PHP:
 if ($do=='sendmessage'){
	if (usrCheckAuth() && $inUser->id!=$id || isset($_POST['massmail'])){

		$from_id    = $inUser->id;
		$to_id      = $id;
		
		$sql        = "SELECT * FROM cms_users WHERE id = '$id'";
		$result     = $inDB->query($sql) ;
        $usr        = $inDB->fetch_assoc($result);

		if ($usr || isset($_POST['massmail'])){
			if (usrCheckAuth()){
////////////////////Дописано АНТИ-СПАМ
				// если у юзера spam_detected >=2, то будем показывать ему captcha
				$sql        = "SELECT spam_detected FROM cms_users WHERE id = '$from_id'";
				$result     = $inDB->query($sql) ;
				$usr1        = $inDB->fetch_assoc($result);
				$need_captcha = ($usr1['spam_detected'] > 1);
				
				//также проверяем не слишком ли часто мы шлем месаги
				if(!$need_captcha)
				{
					$sql = "SELECT senddate FROM cms_user_msg WHERE from_id = '$from_id' AND senddate >= DATE_SUB(NOW(), INTERVAL 1 MINUTE)
					LIMIT 1";
					// Здесь можно изменить "INTERVAL 2 MINUTE" или "INTERVAL 30 SECOND" и т.д. для любителей можно отделить в конфиг
					$result = $inDB->query($sql);
					$need_captcha = ($inDB->num_rows($result)>0);
				}
				// если у юзера spam_detected <5, то он еще может отсылать сообщения
				
				$can_send = ($usr1['spam_detected'] < 5);
				if($_REQUEST['code']) { $code = $_REQUEST['code']; }
////////////////////Дописано АНТИ-СПАМ (конец)				
				$inPage->setTitle($_LANG['SEND_MESS']);
		
				$inPage->addPathway($usr['nickname'], cmsUser::getProfileURL($usr['login']));
				$inPage->addPathway($_LANG['SEND_MESS'], $_SERVER['REQUEST_URI']);
					
				if(!isset($_POST['gosend'])){		
					if (isset($_REQUEST['replyid'])) { $replyid = (int)$_REQUEST['replyid']; }
					else { $replyid = 0; }
					
					if ($replyid){
						$sql = "SELECT m.*, u.* 
								FROM cms_user_msg m, cms_users u
								WHERE m.id = $replyid AND m.from_id = u.id AND m.to_id = $from_id";
						$result = $inDB->query($sql) ;
					
						if ($inDB->num_rows($result)>0){
							$msg = $inDB->fetch_assoc($result);
							echo '<div>';
								echo '<div class="con_heading">'.$_LANG['ORIGINAL_MESS'].'</div>';
								echo '<div class="usr_msgreply_source">';
									echo '<div class="usr_msgreply_sourcetext">'.$msg['message'].'</div>';
									echo '<div class="usr_msgreply_author"><a href="'.cmsUser::getProfileURL($msg['login']).'">'.$msg['nickname'].'</a>, '.$msg['senddate'].'</div>';
								echo '</div>';
							echo '</div>';
						} else {
							die();
						}
					}
////////////////////Дописано АНТИ-СПАМ					
					if ($can_send)
					{
////////////////////Дописано АНТИ-СПАМ (конец)	
						echo '<div class="con_heading">'.$_LANG['SEND_MESS'].'</div>';

						echo '<table width="100%" cellpadding="0" cellspacing="5"><tr>';
					
						echo '<td width="200" height="200" valign="top">
							<div style="background-color:#FFFFFF;padding:5px;border:solid 1px gray;text-align:center">
								'.usrLink(usrImage($usr['id'], 'big'), $usr['login']).'
							</div>
							<div style="padding:5px;width:100%">
								Кому: '.usrLink($usr['nickname'], $usr['login']).'
							</div>
						 </td>';						 
						echo '<td valign="top">';
						echo '<form action="" method="POST" name="msgform">';
							echo '<div class="usr_msg_bbcodebox">';
								echo cmsPage::getBBCodeToolbar('message');
							echo '</div>';							
							echo cmsPage::getSmilesPanel('message');
							echo '<textarea style="font-size:18px;border:solid 1px gray;width:100%;height:200px;" name="message" id="message"></textarea>';
							if ($inCore->userIsAdmin($inUser->id)){
								echo '<input name="massmail" type="checkbox" value="1" /> '.$_LANG['SEND_TO_ALL'];
							}
////////////////////Дописано АНТИ-СПАМ									
							// вставляем капачу если нужна
							if ($need_captcha) {echo cmsPage::getCaptcha();}
////////////////////Дописано АНТИ-СПАМ (конец)	
							echo '<div style="margin-top:6px;"><input type="submit" name="gosend" value="'.$_LANG['SEND'].'" style="font-size:18px"/> ';
							echo '<input type="button" name="gosend" value="'.$_LANG['CANCEL'].'" style="font-size:18px" onclick="window.history.go(-1)"/></div>';
						echo '</form>';
						echo '</td>';					
						echo '</tr></table>';
////////////////////Дописано АНТИ-СПАМ							
					}
					else
					{
						echo '<span style="color: red;">Возможность отсылать личные сообщения для Вас приостановлена!<br/>
								Возможно, на Вас поступили жалобы от других пользователей! В ближайшее время данный случай рассмотрят модераторы сайта.</span>';
					}
////////////////////Дописано АНТИ-СПАМ (конец)						
				} else {
				
					$message = strip_tags($_POST['message'], '<a><img><b><u><i><table><tr><td>');
					$message = htmlspecialchars($message, ENT_QUOTES, 'cp1251');							
				
					if (!isset($_POST['massmail'])){
						//send private message
////////////////////Дописано АНТИ-СПАМ							
						//(если нужна captcha и она верна, или не нужна) и мы можем слать мессаги тогда отправляем
						if ((($need_captcha && $inCore->checkCaptchaCode($code)) || !$need_captcha) && $can_send)
						{
////////////////////Дописано АНТИ-СПАМ (конец)						
							$sql = "INSERT INTO cms_user_msg (to_id, from_id, senddate, is_new, message)
								VALUES ('$to_id', '$from_id', NOW(), 1, '$message')";																
							$inDB->query($sql) ;
						
							$msg_id = dbLastId('cms_user_msg'); 
						
							//send email notification, if user want it
							$needmail = dbGetField('cms_user_profiles', "user_id='{$to_id}'", 'email_newmsg');
							//Проверяем, если юзер онлайн, то уведомление на почту не отправляем.
							$isonline = dbGetField('cms_online', "user_id='{$to_id}'", 'id');
							if (!$isonline){
								if ($needmail){
								$inConf     = cmsConfig::getInstance();
														
								$postdate   = date('d/m/Y H:i:s');
								$to_email   = dbGetField('cms_users', "id='{$to_id}'", 'email');
								$from_nick  = dbGetField('cms_users', "id='{$from_id}'", 'nickname');
								$answerlink = HOST.'/users/'.$from_id.'/reply'.$msg_id.'.html';
						
								$letter_path    = PATH.'/includes/letters/newmessage.txt';
								$letter         = file_get_contents($letter_path);
								
								$letter= str_replace('{sitename}', $inConf->sitename, $letter);
								$letter= str_replace('{answerlink}', $answerlink, $letter);
								$letter= str_replace('{date}', $postdate, $letter);
								$letter= str_replace('{from}', $from_nick, $letter);	
								$inCore->mailText($to_email, $_LANG['YOU_HAVE_NEW_MESS'].'! - '.$inConf->sitename, $letter);
								}
							}
							$inCore->redirect('/users/'.$inUser->id.'/messages-sent.html');
////////////////////Дописано АНТИ-СПАМ								
						}// конец if с капача captcha
						else 
						{
							//когда captcha в веден неверно						
								echo '<span style="color: red;">Вы не правильно ввели проверочный код!<br/>
								Пожалуйста, вернитесь назад и повторите отправку!</span>';				
						}
////////////////////Дописано АНТИ-СПАМ (конец)							
					} else {
						if ($inUser->is_admin){
							$userlist = dbGetTable('cms_users', ' id > 0 AND is_locked = 0 AND is_deleted = 0');
							foreach ($userlist as $key=>$usr){
								$sql = "INSERT INTO cms_user_msg (to_id, from_id, senddate, is_new, message)
										VALUES ('".$usr['id']."', '-2', NOW(), 1, '$message')";
								$inDB->query($sql) ;
							}
						}
						$inCore->redirect('/users/'.$inUser->id.'/messages-sent.html');
					}
																
				}
			
			}
		}
	} else { 
        usrAccessDenied();
        } //usrCheckAuth
}//do 
5.2. Выводим captcha при добавлении комментариев.
Добавляется аналогично, как и для сообщений, даже проще.

6. Админка
Можно также сделать что-то для админки (но честно говоря, я не делал), может быть позже реализую.
6.1. Вынесение всего добра в конфиг и настройка в админке.
6.2. Вывод в админке списка спам-месаг и проч.

7. Что делать с юзерами.
Собственно, после того как начнут поступать жалобы на спам, spam_detected будет расти, и как видно из кода алгоритма его уменьшения не предусмотрено. Поэтому каждая жалоба обрабатывается в ручную, если она по дело, то баним юзера. Если нет, то возвращаем юзеру его spam_detected = 0, через БД.
Можно написать отдельную кнопку и привязать к ней запрос:
$inDB->query("UPDATE cms_users SET spam_detected = 0 WHERE id = &#8217;$id&#8217;);

Собственно, я надеюсь, что это кому-то будет полезно.
Просто по ходу выяснился существенный (на мой взгляд) недостаток сообщений, когда юзер удаляет входящее, автоматически удаляется исходящее, поэтому пришлось это дело переписывать, но это уже совсем другая история...
В следствии чего был реализован более сложный алгоритм, с буферными таблицами и прочим (см. теор. часть), но соотв. он ориентирован под мою переделку. Поэтому выкладывать просто не вижу смысла.
Если кто-то хочет поучаствовать в дальнейшей разработкой, пишите в личку, буду рад.
Поиск багов и косяков приветствуется. У меня вроде все работает, тестировал долго и упорно.

20.08.10 Обновлено, исправлена небольшая ошибка. В if ($do==&#8217;sendmessage&#8217;).

Анти-спам (теоретическая часть)

Как бороться со спамом – это одна из вечных проблем любой соц. сети. В данной записи постараюсь поделиться своими взглядами и опытом по данной проблеме. Статья носит упрощенный (обзорный) характер, на деле все естественно сложнее.

Рассылка спама бывает 2-ух типов: автоматическая, ручная.
Про ручную отправку. Люди в беднейших странах «руками» отправляют сообщения (комментарии, письма и т.д.) и получают по местным меркам хорошие деньги. Если кому-то интересно могу рассказать подробнее. Аналогично, кстати, работает и anticaptcha.

Итак, Борьба со спамом подразделяется на 2 части:
1) Выявление спаммера
2) Противодействие спаммеру