Simple Machines Forum кросс-пост и авторизация.

18 августа, 2010 · Posted in Без рубрики · 2 комментария 

Захотелось мне добавить на сайт http://maps.ck.ua систему комментариев. Да не простую, а золотую необычную. Т.е. чтобы можно было постить и просматривать комментарии и с сайта и с более удобного для просмотра большого количества тем и текста — с форума.

В качестве форума был выбран любимый SMF, 2 версии. Почему любимый — потому что симпатичный дизайн, очень, очень удобная админка и просто хорошая скорость работы.

Официального API для SMF 2.х еще нету, поэтому пришлось использовать чужие наработки в этом плане, плюс немного фантазии и чтения кода форума.

Приведенные ниже строчки фактически являются хардкодингом. Поэтому если вас воротит от этого слова, этот пост можно пропускать.

Итак, авторизация:

function login($_user,$_password ) {
require_once ("...../forum/SSI.php");
require_once("...../forum/Sources/Subs-Auth.php");
 $db_prefix="ИМЯ_БД.ПРЕФИКС_ТАБЛИЦ;

 $request = mysql_query("
 SELECT ID_MEMBER
 FROM {$db_prefix}members
 WHERE member_name = '$_user'
 LIMIT 1");

 if ($request == false && mysql_num_rows($request) < 1)
 {
 mysql_free_result($request);
 setLoginCookie(-3600, 0);
 return "no user name";
 }

 $get_password_hash = mysql_query ("
 SELECT passwd, ID_MEMBER, ID_GROUP, lngfile, is_activated, email_address, additional_groups, member_name, password_salt
 FROM {$db_prefix}members
 WHERE member_name = '$_user'
 LIMIT 1");

 $user_settings = mysql_fetch_assoc($get_password_hash);
 $hash_input_password = sha1(strtolower($user_settings['member_name']) . un_htmlspecialchars(stripslashes($_password)));

 if ($user_settings['passwd']==$hash_input_password)
 {
 $cookielength = 3153600;
 setLoginCookie( $cookielength, $user_settings['ID_MEMBER'], sha1($user_settings['passwd'] . $user_settings['password_salt']));

 } else{
 setLoginCookie(-3600, 0);
 return "no user";
 }

 return "ok";
}

Аутентификация, загрузка из куки.

function smf_authenticateUser($mode="all"){
require_once ("...../forum/SSI.php");
require_once("...../forum//Sources/Subs-Auth.php");
 global $smf_connection, $smf_settings, $smf_user_info;
 $ID_MEMBER = 0;
 if (isset($_COOKIE[$cookiename]))
 {
 $_COOKIE[$cookiename] = stripslashes($_COOKIE[$cookiename]);
 // Fix a security hole in PHP 4.3.9 and below...
 if (preg_match('~^a:[34]:\{i:0;(i:\d{1,6}|s:[1-8]:"\d{1,8}");i:1;s:(0|40):"([a-fA-F0-9]{40})?";i:2;[id]:\d{1,14};(i:3;i:\d;)?\}$~',$_COOKIE[$cookiename]) == 1)
 {
 list ($ID_MEMBER, $password) = @unserialize($_COOKIE[$cookiename]);
 $ID_MEMBER = !empty($ID_MEMBER) && strlen($password) > 0 ? (int) $ID_MEMBER : 0;
 } else return "no cookie";
 } else return "no cookie2";
 //echo $ID_MEMBER;
 // Don't even bother if they have no authentication data.
 if ($ID_MEMBER != 0){
 $request = mysql_query("
 SELECT *
 FROM {$db_prefix}members
 WHERE ID_MEMBER = $ID_MEMBER
 LIMIT 1");
 // Did we find 'im?  If not, junk it.
 if (mysql_num_rows($request) != 0)
 {
 // The base settings array.
 $smf_user_info = mysql_fetch_assoc($request);
 if (strlen($password) == 40)
 $check = sha1($smf_user_info['passwd'] . $smf_user_info['password_salt']) == $password;
 else
 $check = false;

 // Wrong password or not activated - either way, you're going nowhere.
 $ID_MEMBER = $check && ($smf_user_info['is_activated'] == 1 || $smf_user_info['is_activated'] == 11) ? $smf_user_info['id_member'] : 0;
 } else {
 $ID_MEMBER = 0;
 mysql_free_result($request);
 return "no user in mysql";
 }
 } else {
 return "no cookie";
 }
 if(!empty($ID_MEMBER)){
 $cookielength = 3153600;
 setLoginCookie( $cookielength, $smf_user_info['id_member'], sha1($smf_user_info['passwd'] . $smf_user_info['password_salt']));
 } else return "no member";

 // A few things to make life easier...
 $smf_user_info['id'] = &$smf_user_info['id_member'];
 $smf_user_info['username'] = &$smf_user_info['member_name'];
 $smf_user_info['name'] = &$smf_user_info['real_name'];
 $smf_user_info['email'] = &$smf_user_info['email_address'];
 $smf_user_info['messages'] = &$smf_user_info['instant_messages'];
 $smf_user_info['unread_messages'] = &$smf_user_info['unread_messages'];
 $smf_user_info['is_guest'] = $ID_MEMBER == 0;
 $smf_user_info['is_admin'] = in_array(1, $smf_user_info['id_group']);
 $smf_user_info['status']="ok";
 if (empty($smf_user_info['time_format']))
 $smf_user_info['time_format'] = $smf_settings['time_format'];

 if($mode=="all") return $smf_user_info;
 if($mode=="check") return "ok";
 if($mode=="json") return json_encode($smf_user_info);
 if($mode=="id")  return  $smf_user_info['id'];
}

Постинг в форум. Если topic пустой или равен 0, создается новая тема.

function post_to_smf($board,$topic,$subject,$txt,$oid,$otype,$maptype) {
 $ID_MEMBER = smf_authenticateUser("id");
 if($ID_MEMBER==0 or !is_numeric($ID_MEMBER)) return "wrong poster";
 require_once("......../forum/SSI.php");
 require_once("......../forum/Sources/Subs-Post.php");
 global $smcFunc;
 $TOPIC=$topic;
 $ID_BOARD=$board;

 $excerpt = stripcslashes(trim(strip_tags($txt)));
 $words = preg_split("/(?<=(\.|!|\?)+)\s/", $excerpt, -1, PREG_SPLIT_NO_EMPTY);
 foreach ( $words as $word )
 {
 $new_text = $text . substr($excerpt, 0, strpos($excerpt, $word) + strlen($word));
 $excerpt = substr($excerpt, strpos($excerpt, $word) + strlen($word), strlen($excerpt));

 if ( ( strlen($text) != 0 ) && ( strlen($new_text) > $max_length ) )
 {
 break;
 }

 $text = $new_text;
 }
 $post_text = $text;

 $msgOptions = array(
 'id' =>  0,
 'subject' => stripcslashes($subject),
 'body' => $post_text,
 'smileys_enabled' => true,
 );

 $topicOptions = array(
 'id' => $TOPIC,
 'board' => $ID_BOARD,
 'mark_as_read' => true,
 );

 $posterOptions = array(
 'id' => $ID_MEMBER,
 );

 $out=Array();

 $top=createPost2($msgOptions, $topicOptions, $posterOptions);
 if($top) {
 $out["status"]="ok";
 $out["topicid"]=$top;

 return json_encode($out);
 } else {
 $out["status"]="error create post";
 return json_encode($out);
 }
}

function createPost2(&$msgOptions, &$topicOptions, &$posterOptions)
{
 global $user_info, $txt, $modSettings, $smcFunc, $context;

 // Set optional parameters to the default value.
 $msgOptions['icon'] = empty($msgOptions['icon']) ? 'xx' : $msgOptions['icon'];
 $msgOptions['smileys_enabled'] = !empty($msgOptions['smileys_enabled']);
 $msgOptions['attachments'] = empty($msgOptions['attachments']) ? array() : $msgOptions['attachments'];
 $msgOptions['approved'] = isset($msgOptions['approved']) ? (int) $msgOptions['approved'] : 1;
 $topicOptions['id'] = empty($topicOptions['id']) ? 0 : (int) $topicOptions['id'];
 $topicOptions['poll'] = isset($topicOptions['poll']) ? (int) $topicOptions['poll'] : null;
 $topicOptions['lock_mode'] = isset($topicOptions['lock_mode']) ? $topicOptions['lock_mode'] : null;
 $topicOptions['sticky_mode'] = isset($topicOptions['sticky_mode']) ? $topicOptions['sticky_mode'] : null;
 $posterOptions['id'] = empty($posterOptions['id']) ? 0 : (int) $posterOptions['id'];
 $posterOptions['ip'] = empty($posterOptions['ip']) ? $user_info['ip'] : $posterOptions['ip'];

 $smcFunc['db_query']('', 'SET NAMES UTF8',null);
 // We need to know if the topic is approved. If we're told that's great - if not find out.
 if (!$modSettings['postmod_active'])
 $topicOptions['is_approved'] = true;
 elseif (!empty($topicOptions['id']) && !isset($topicOptions['is_approved']))
 {
 $request = $smcFunc['db_query']('', '
 SELECT approved
 FROM {db_prefix}topics
 WHERE id_topic = {int:id_topic}
 LIMIT 1',
 array(
 'id_topic' => $topicOptions['id'],
 )
 );
 list ($topicOptions['is_approved']) = $smcFunc['db_fetch_row']($request);
 $smcFunc['db_free_result']($request);
 }

 // If nothing was filled in as name/e-mail address, try the member table.
 if (!isset($posterOptions['name']) || $posterOptions['name'] == '' || (empty($posterOptions['email']) && !empty($posterOptions['id'])))
 {
 if (empty($posterOptions['id']))
 {
 $posterOptions['id'] = 0;
 $posterOptions['name'] = $txt['guest_title'];
 $posterOptions['email'] = '';
 }
 elseif ($posterOptions['id'] != $user_info['id'])
 {
 $request = $smcFunc['db_query']('', '
 SELECT member_name, email_address
 FROM {db_prefix}members
 WHERE id_member = {int:id_member}
 LIMIT 1',
 array(
 'id_member' => $posterOptions['id'],
 )
 );
 // Couldn't find the current poster?
 if ($smcFunc['db_num_rows']($request) == 0)
 {
 trigger_error('createPost(): Invalid member id ' . $posterOptions['id'], E_USER_NOTICE);
 $posterOptions['id'] = 0;
 $posterOptions['name'] = $txt['guest_title'];
 $posterOptions['email'] = '';
 }
 else
 list ($posterOptions['name'], $posterOptions['email']) = $smcFunc['db_fetch_row']($request);
 $smcFunc['db_free_result']($request);
 }
 else
 {
 $posterOptions['name'] = $user_info['name'];
 $posterOptions['email'] = $user_info['email'];
 }
 }

 // It's do or die time: forget any user aborts!
 $previous_ignore_user_abort = ignore_user_abort(true);

 $new_topic = empty($topicOptions['id']);

 // Insert the post.
 $smcFunc['db_insert']('',
 '{db_prefix}messages',
 array(
 'id_board' => 'int', 'id_topic' => 'int', 'id_member' => 'int', 'subject' => 'string-255', 'body' => (!empty($modSettings['max_messageLength']) && $modSettings['max_messageLength'] > 65534 ? 'string-' . $modSettings['max_messageLength'] : 'string-65534'),
 'poster_name' => 'string-255', 'poster_email' => 'string-255', 'poster_time' => 'int', 'poster_ip' => 'string-255',
 'smileys_enabled' => 'int', 'modified_name' => 'string', 'icon' => 'string-16', 'approved' => 'int',
 ),
 array(
 $topicOptions['board'], $topicOptions['id'], $posterOptions['id'], $msgOptions['subject'], $msgOptions['body'],
 $posterOptions['name'], $posterOptions['email'], time(), $posterOptions['ip'],
 $msgOptions['smileys_enabled'] ? 1 : 0, '', $msgOptions['icon'], $msgOptions['approved'],
 ),
 array('id_msg')
 );
 $msgOptions['id'] = $smcFunc['db_insert_id']('{db_prefix}messages', 'id_msg');

 // Something went wrong creating the message...
 if (empty($msgOptions['id']))
 return false;

 // Fix the attachments.
 if (!empty($msgOptions['attachments']))
 $smcFunc['db_query']('', '
 UPDATE {db_prefix}attachments
 SET id_msg = {int:id_msg}
 WHERE id_attach IN ({array_int:attachment_list})',
 array(
 'attachment_list' => $msgOptions['attachments'],
 'id_msg' => $msgOptions['id'],
 )
 );

 // Insert a new topic (if the topicID was left empty.
 if ($new_topic)
 {
 $smcFunc['db_insert']('',
 '{db_prefix}topics',
 array(
 'id_board' => 'int', 'id_member_started' => 'int', 'id_member_updated' => 'int', 'id_first_msg' => 'int',
 'id_last_msg' => 'int', 'locked' => 'int', 'is_sticky' => 'int', 'num_views' => 'int',
 'id_poll' => 'int', 'unapproved_posts' => 'int', 'approved' => 'int',
 ),
 array(
 $topicOptions['board'], $posterOptions['id'], $posterOptions['id'], $msgOptions['id'],
 $msgOptions['id'], $topicOptions['lock_mode'] === null ? 0 : $topicOptions['lock_mode'], $topicOptions['sticky_mode'] === null ? 0 : $topicOptions['sticky_mode'], 0,
 $topicOptions['poll'] === null ? 0 : $topicOptions['poll'], $msgOptions['approved'] ? 0 : 1, $msgOptions['approved'],
 ),
 array('id_topic')
 );
 $topicOptions['id'] = $smcFunc['db_insert_id']('{db_prefix}topics', 'id_topic');

 // The topic couldn't be created for some reason.
 if (empty($topicOptions['id']))
 {
 // We should delete the post that did work, though...
 $smcFunc['db_query']('', '
 DELETE FROM {db_prefix}messages
 WHERE id_msg = {int:id_msg}',
 array(
 'id_msg' => $msgOptions['id'],
 )
 );

 return false;
 }

 // Fix the message with the topic.
 $smcFunc['db_query']('', '
 UPDATE {db_prefix}messages
 SET id_topic = {int:id_topic}
 WHERE id_msg = {int:id_msg}',
 array(
 'id_topic' => $topicOptions['id'],
 'id_msg' => $msgOptions['id'],
 )
 );

 // There's been a new topic AND a new post today.
 trackStats(array('topics' => '+', 'posts' => '+'));

 updateStats('topic', true);
 updateStats('subject', $topicOptions['id'], $msgOptions['subject']);
 //What if we want to export new topics out to a CMS?
 if (isset($modSettings['integrate_create_topic']) && is_callable($modSettings['integrate_create_topic']))
 call_user_func(strpos($modSettings['integrate_create_topic'], '::') === false ? $modSettings['integrate_create_topic'] : explode('::', $modSettings['integrate_create_topic']), $msgOptions, $topicOptions, $posterOptions);
 }
 // The topic already exists, it only needs a little updating.
 else
 {
 $countChange = $msgOptions['approved'] ? 'num_replies = num_replies + 1' : 'unapproved_posts = unapproved_posts + 1';

 // Update the number of replies and the lock/sticky status.
 $smcFunc['db_query']('', '
 UPDATE {db_prefix}topics
 SET
 ' . ($msgOptions['approved'] ? 'id_member_updated = {int:poster_id}, id_last_msg = {int:id_msg},' : '') . '
 ' . $countChange . ($topicOptions['lock_mode'] === null ? '' : ',
 locked = {int:locked}') . ($topicOptions['sticky_mode'] === null ? '' : ',
 is_sticky = {int:is_sticky}') . '
 WHERE id_topic = {int:id_topic}',
 array(
 'poster_id' => $posterOptions['id'],
 'id_msg' => $msgOptions['id'],
 'locked' => $topicOptions['lock_mode'],
 'is_sticky' => $topicOptions['sticky_mode'],
 'id_topic' => $topicOptions['id'],
 )
 );

 // One new post has been added today.
 trackStats(array('posts' => '+'));
 }

 // Creating is modifying...in a way.
 //!!! Why not set id_msg_modified on the insert?
 $smcFunc['db_query']('', '
 UPDATE {db_prefix}messages
 SET id_msg_modified = {int:id_msg}
 WHERE id_msg = {int:id_msg}',
 array(
 'id_msg' => $msgOptions['id'],
 )
 );

 // Increase the number of posts and topics on the board.
 if ($msgOptions['approved'])
 $smcFunc['db_query']('', '
 UPDATE {db_prefix}boards
 SET num_posts = num_posts + 1' . ($new_topic ? ', num_topics = num_topics + 1' : '') . '
 WHERE id_board = {int:id_board}',
 array(
 'id_board' => $topicOptions['board'],
 )
 );
 else
 {
 $smcFunc['db_query']('', '
 UPDATE {db_prefix}boards
 SET unapproved_posts = unapproved_posts + 1' . ($new_topic ? ', unapproved_topics = unapproved_topics + 1' : '') . '
 WHERE id_board = {int:id_board}',
 array(
 'id_board' => $topicOptions['board'],
 )
 );

 // Add to the approval queue too.
 $smcFunc['db_insert']('',
 '{db_prefix}approval_queue',
 array(
 'id_msg' => 'int',
 ),
 array(
 $msgOptions['id'],
 ),
 array()
 );
 }

 // Mark inserted topic as read (only for the user calling this function).
 if (!empty($topicOptions['mark_as_read']) && !$user_info['is_guest'])
 {
 // Since it's likely they *read* it before replying, let's try an UPDATE first.
 if (!$new_topic)
 {
 $smcFunc['db_query']('', '
 UPDATE {db_prefix}log_topics
 SET id_msg = {int:id_msg}
 WHERE id_member = {int:current_member}
 AND id_topic = {int:id_topic}',
 array(
 'current_member' => $posterOptions['id'],
 'id_msg' => $msgOptions['id'],
 'id_topic' => $topicOptions['id'],
 )
 );

 $flag = $smcFunc['db_affected_rows']() != 0;
 }

 if (empty($flag))
 {
 $smcFunc['db_insert']('ignore',
 '{db_prefix}log_topics',
 array('id_topic' => 'int', 'id_member' => 'int', 'id_msg' => 'int'),
 array($topicOptions['id'], $posterOptions['id'], $msgOptions['id']),
 array('id_topic', 'id_member')
 );
 }
 }

 // If there's a custom search index, it needs updating...
 if (!empty($modSettings['search_custom_index_config']))
 {
 $customIndexSettings = unserialize($modSettings['search_custom_index_config']);

 $inserts = array();
 foreach (text2words($msgOptions['body'], $customIndexSettings['bytes_per_word'], true) as $word)
 $inserts[] = array($word, $msgOptions['id']);

 if (!empty($inserts))
 $smcFunc['db_insert']('ignore',
 '{db_prefix}log_search_words',
 array('id_word' => 'int', 'id_msg' => 'int'),
 $inserts,
 array('id_word', 'id_msg')
 );
 }

 // Increase the post counter for the user that created the post.
 if (!empty($posterOptions['update_post_count']) && !empty($posterOptions['id']) && $msgOptions['approved'])
 {
 // Are you the one that happened to create this post?
 if ($user_info['id'] == $posterOptions['id'])
 $user_info['posts']++;
 updateMemberData($posterOptions['id'], array('posts' => '+'));
 }

 // They've posted, so they can make the view count go up one if they really want. (this is to keep views >= replies...)
 $_SESSION['last_read_topic'] = 0;

 // Better safe than sorry.
 if (isset($_SESSION['topicseen_cache'][$topicOptions['board']]))
 $_SESSION['topicseen_cache'][$topicOptions['board']]--;

 // Update all the stats so everyone knows about this new topic and message.
 updateStats('message', true, $msgOptions['id']);

 // Update the last message on the board assuming it's approved AND the topic is.
 if ($msgOptions['approved'])
 updateLastMessages($topicOptions['board'], $new_topic || !empty($topicOptions['is_approved']) ? $msgOptions['id'] : 0);

 // Alright, done now... we can abort now, I guess... at least this much is done.
 ignore_user_abort($previous_ignore_user_abort);

 // Success.
 return $topicOptions['id'];
}

тут в строчке

$ID_MEMBER = smf_authenticateUser("id");

ждет сюрпиз. Если постит не администратор, функция возвращает ошибку.
Для исправления придется лезть в файл forum/SSI.php и выше строчки

loadPermissions();

добавлять

loadBoard();

Гнусный хардкодинг :\

В работе использованы куски кода моста Mambo-SMF, куски кода самого SMF, документация к форуму.