- Совместимость
- не проверялся на совместимость
- Изменения в БД
- не требуются
- Дополнительные требования
- sqlite3
- Автор
- frost444
- Общественное достояние
- да
Описание: добавляет логирование действий пользователей, их точное месторасположение на трекере.
Возможности:
Возможности:
- Запись лога перемещения пользователя по трекеру
- Живой вывод тех кто смотрит топик или форум
- Использование AJAX и sqlite3
- Инструкция по установке
-
PHP:
Автор мода: frost444 ####[ОТКРЫТЬ]----- config.php -----[найти]----- 'bb_login_err' => array('filecache', array()), -----[добавить ниже]----- 'buf_where' => array('db_sqlite', array('columns' => 'user_ip INT, username TEXT, user_id INT, user_rank INT, user_opt INT, page_id INT, id INT, title TEXT, mode TEXT, url TEXT, time INT')), -----[добавить в конец]----- $bb_cfg['bots_conf'] = array( 'id' => '-777', ); $bb_cfg['bots'] = array ( 'AdsBot-Google' => 'AdsBot [Google]', 'ia_archiver' => 'Alexa [Bot]', 'Scooter/' => 'Alta Vista [Bot]', 'Ask Jeeves' => 'Ask Jeeves [Bot]', 'Baiduspider+(' => 'Baidu [Spider]', 'Exabot/' => 'Exabot [Bot]', 'FAST Enterprise Crawler' => 'FAST Enterprise [Crawler]', 'FAST-WebCrawler/' => 'FAST WebCrawler [Crawler]', 'http://www.neomo.de/' => 'Francis [Bot]', 'Gigabot/' => 'Gigabot [Bot]', 'Mediapartners-Google' => 'Google Adsense [Bot]', 'Google Desktop' => 'Google Desktop', 'Feedfetcher-Google' => 'Google Feedfetcher', 'Googlebot' => 'Google [Bot]', 'heise-IT-Markt-Crawler' => 'Heise IT-Markt [Crawler]', 'heritrix/1.' => 'Heritrix [Crawler]', 'ibm.com/cs/crawler' => 'IBM Research [Bot]', 'ICCrawler - ICjobs' => 'ICCrawler - ICjobs', 'ichiro/' => 'ichiro [Crawler]', 'MJ12bot/' => 'Majestic-12 [Bot]', 'MetagerBot/' => 'Metager [Bot]', 'msnbot-NewsBlogs/' => 'MSN NewsBlogs', 'msnbot/' => 'MSN [Bot]', 'msnbot-media/' => 'MSNbot Media', 'NG-Search/' => 'NG-Search [Bot]', 'http://lucene.apache.org/nutch/' => 'Nutch [Bot]', 'NutchCVS/' => 'Nutch/CVS [Bot]', 'OmniExplorer_Bot/' => 'OmniExplorer [Bot]', 'online link validator' => 'Online link [Validator]', 'psbot/0' => 'psbot [Picsearch]', 'Seekbot/' => 'Seekport [Bot]', 'Sensis Web Crawler' => 'Sensis [Crawler]', 'SEO search Crawler/' => 'SEO Crawler', 'Seoma [SEO Crawler]' => 'Seoma [Crawler]', 'SEOsearch/' => 'SEOSearch [Crawler]', 'Snappy/1.1 ( http://www.urltrends.com/ )' => 'Snappy [Bot]', 'http://www.tkl.iis.u-tokyo.ac.jp/~crawler/' => 'Steeler [Crawler]', 'SynooBot/' => 'Synoo [Bot]', '[email protected]' => 'Telekom [Bot]', 'TurnitinBot/' => 'TurnitinBot [Bot]', 'voyager/1.0' => 'Voyager [Bot]', 'W3 SiteSearch Crawler' => 'W3 [Sitesearch]', 'W3C-checklink/' => 'W3C [Linkcheck]', 'W3C_*Validator' => 'W3C [Validator]', 'http://www.WISEnutbot.com' => 'WiseNut [Bot]', 'yacybot' => 'YaCy [Bot]', 'Yahoo-MMCrawler/' => 'Yahoo MMCrawler [Bot]', 'Yahoo! DE Slurp' => 'Yahoo Slurp [Bot]', 'Yahoo! Slurp' => 'Yahoo [Bot]', 'YahooSeeker/' => 'YahooSeeker [Bot]', 'Yandex/1.01.001 (compatible; Win16; I)' => 'Яндекс БОТ', 'Mozilla/5.0 (compatible; YandexBot/3.0; +http://yandex.com/bots)' => 'Яндекс [Bot]', 'bingbot/' => 'Bing [Bot]', ); $bb_cfg['where_user'] = true; ####[СОХРАНИТЬ_ФАЙЛ]#### ####[ОТКРЫТЬ]----- ajax.php -----[найти]----- 'index_data' => array('guest'), -----[добавить ниже]----- 'UserStats' => array('user'), -----[найти последнюю скобку и перед ней добавить]----- function UserStats() { require(AJAX_DIR .'UserStats.php'); } ####[СОХРАНИТЬ_ФАЙЛ]#### ####[ОТКРЫТЬ]----- lang_main.php -----[в конец]----- // Online/Offline $lang['OFFLINE'] = 'Вне форума'; $lang['ONLINE'] = 'На форуме'; $lang['HIDDEN'] = 'Скрыт'; $lang['ON_OFF_STATUS'] = 'Статус'; // Online/Offline $lang['LAST_TYPE'] = 'Последнее действие'; $lang['ONLINE_USERS_WHERE'] = '<b>%1$d</b> человек просматривают эту страницу: <b>%2$d</b><sup title="С 1-го аккаунта сидят несколько человек"><b>[%3$d]</b></sup> зарегистрированных, <b>%4$d</b> скрытых, <b>%5$d</b> гостей, <b>%6$d</b> ботов'; ####[СОХРАНИТЬ_ФАЙЛ]#### ####[ОТКРЫТЬ]----- index.php -----[найти]----- $template->assign_vars(array( 'SHOW_FORUMS' => $forums_count, -----[добавить выше]----- where_user(array('page_id' => PAGE_FORUM, 'id' => '0', 'mode' => '0', 'url' => str_replace($bb_cfg['script_path'], "", $_SERVER['REQUEST_URI']), 'title' => $lang['FORUM'])); ####[СОХРАНИТЬ_ФАЙЛ]#### ####[ОТКРЫТЬ]----- viewtopic.php -----[найти]----- $template->assign_vars(array( 'PAGE_URL' => $pg_url, -----[добавить выше]----- where_user(array('page_id' => PAGE_VIEWTOPIC, 'id' => $topic_id, 'mode' => '0', 'url' => str_replace($bb_cfg['script_path'], "", $_SERVER['REQUEST_URI']), 'title' => $t_data['topic_title']), true); -----[найти]----- $template->assign_vars(array( 'PAGE_URL' => $pg_url, -----[добавить ниже]----- 'WHO_ONLINE_LIST' => who_online_list(PAGE_VIEWTOPIC, $topic_id), ####[СОХРАНИТЬ_ФАЙЛ]#### ####[ОТКРЫТЬ]----- functions.php -----[найти]----- function profile_url($data) { ... } -----[заменить]----- function profile_url($data, $popupon = false) { global $bb_cfg, $lang, $datastore; if (!$ranks = $datastore->get('ranks')) { $datastore->update('ranks'); $ranks = $datastore->get('ranks'); } $user_rank = !empty($data['user_rank']) ? $data['user_rank'] : 0; if(isset($ranks[$user_rank])) { $title = $ranks[$user_rank]['rank_title']; $style = $ranks[$user_rank]['rank_style']; } if(empty($title)) $title = $lang['USER']; if(empty($style)) $style = 'colorUser'; if(!$bb_cfg['color_nick']) $style = ''; $username = !empty($data['username']) ? $data['username'] : $lang['GUEST']; $user_id = (!empty($data['user_id']) && $username != $lang['GUEST']) ? $data['user_id'] : ANONYMOUS; if($popupon) { $profile = " <span onclick=\"user_stats(".$user_id.", 'popup');\" title=\"Просмотр профиля\" class=\"UserPopupsImg clickable\"><span class=\"pad_6\"></span></span>"; $profile = '<span title="'. $title .'" class="'. $style .'">'. $username .'</span>'.$profile.''; }else{ $profile = '<span title="'. $title .'" class="'. $style .'">'. $username .'</span>'; } if(!in_array($user_id, array('', ANONYMOUS, BOT_UID)) && $username && !$popupon) { $popup = " <span onclick=\"user_stats(".$user_id.", 'popup');\" title=\"Просмотр профиля\" class=\"UserPopupsImg clickable\"><span class=\"pad_6\"></span></span>"; $profile = '<a href="'. make_url(PROFILE_URL . $user_id, true) .'">'. $profile .'</a>'.$popup.''; } return $profile; } -----[добавить в конец]----- function who_online_list ($page_id, $id = false, $mode = false) { global $bb_cfg, $lang, $userdata; $id = ($id) ? "AND id = {$id}" : ''; $mode = ($mode) ? "AND mode = {$mode}" : ''; $where_time = (TIMENOW - 300); $sql = CACHE('buf_where')->fetch_rowset(" SELECT username, user_rank, user_opt, user_id, min(user_ip) as user_ip FROM buf_where WHERE page_id = {$page_id} $id $mode AND time > $where_time GROUP BY user_ip having min(user_ip) = max(user_ip) "); $id_user = $id_user_hidden = $id_anon = $id_bot = $user = $count_all = array(); foreach ($sql as $row) { $count_all[] = $row['user_ip']; if($row['user_id'] != ANONYMOUS && $row['user_id'] != $bb_cfg['bots_conf']['id']) { $id_user[] = $row['user_id']; $user[] = profile_url(array('username' => $row['username'], 'user_id' => $row['user_id'], 'user_rank' => $row['user_rank'])); } else if($row['user_id'] != ANONYMOUS && $row['user_id'] != $bb_cfg['bots_conf']['id'] && bf($row['user_opt'], 'user_opt', 'allow_viewonline')) { $id_user_hidden[] = $row['user_id']; } else if($row['user_id'] == ANONYMOUS) { $id_anon[] = $row['user_id']; } else if($row['user_id'] == $bb_cfg['bots_conf']['id']) { $id_bot[] = $row['user_id']; } } $alls = count($count_all); $users = count(array_unique($id_user)); $hiddens = count(array_unique($id_user_hidden)); $anons = count($id_anon); $bots = count($id_bot); $duble = ($alls - $users - $hiddens - $anons - $bots); $online_count = sprintf($lang['ONLINE_USERS_WHERE'], $alls, $users, $duble, $hiddens, $anons, $bots); $online_list = $online_count . '<br /><br />' . join(",\n", array_unique($user)); return $online_list; } function where_user($data, $quest = false) { global $bb_cfg, $lang, $userdata; if(!$bb_cfg['where_user']) return; if(IS_GUEST && !$quest) return; $where_time = (TIMENOW - 300); $buf = CACHE('buf_where')->fetch_row(" SELECT time FROM buf_where WHERE page_id = {$data['page_id']} AND mode = '{$data['mode']}' AND user_ip = '". USER_IP ."' AND time > $where_time AND id = {$data['id']} ORDER BY time DESC"); $insert = true; if($buf) $insert = false; $sql['user_ip'] = USER_IP; $sql['username'] = $userdata['username']; $sql['user_id'] = $userdata['user_id']; if(IS_GUEST) { $user_browser = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : 'not browser'; foreach ($bb_cfg['bots'] as $bot => $name) { if(strstr($user_browser, $bot)) { $sql['username'] = $name; $sql['user_id'] = $bb_cfg['bots_conf']['id']; } } } $sql['user_rank'] = $userdata['user_rank']; $sql['user_opt'] = $userdata['user_opt']; $sql['time'] = TIMENOW; $sql['page_id'] = @$data['page_id']; $sql['id'] = @$data['id']; $sql['mode'] = @$data['mode']; $sql['url'] = @$data['url']; $sql['title'] = @htmlCHR($data['title']); $sql_insert = DB()->build_array('INSERT', $sql); if($insert) CACHE('buf_where')->query("INSERT INTO buf_where $sql_insert"); } ####[СОХРАНИТЬ_ФАЙЛ]#### ####[ОТКРЫТЬ]----- init_bb.php -----[найти]----- define('POST_USERS_URL', 'u'); -----[добавить ниже]----- //[START] Where user define('PAGE_REGISTER', -1); define('PAGE_LOGIN', 0); define('PAGE_INDEX', 1); define('PAGE_FORUM', 2); define('PAGE_VIEWFORUM', 3); define('PAGE_VIEWTOPIC', 4); define('PAGE_POSTING', 5); define('PAGE_PROFILE', 6); define('PAGE_PRIVMSGS', 7); define('PAGE_SEARCH', 8); define('PAGE_TRACKER', 9); define('PAGE_VIEWONLINE', 10); define('PAGE_VIEWMEMBERS', 11); define('PAGE_GROUPCP', 12); define('PAGE_FAQ', 13); define('PAGE_REPORT', 14); define('PAGE_PORTAL', 15); define('PAGE_ORDER', 16); define('PAGE_POINTSCP', 17); define('PAGE_QUESTBOOK', 18); define('PAGE_RADIO', 19); define('PAGE_PRESENTS', 20); define('PAGE_VIEWCHEATER', 21); define('PAGE_WARNINGS', 22); define('PAGE_MYTOP', 22); ////(BLOG)(START) Where user define('PAGE_BLOG', 100); define('PAGE_BLOG_ALLENTRIES', 101); define('PAGE_BLOG_BLOCKED', 102); define('PAGE_BLOG_CFG', 103); define('PAGE_BLOG_CONTRIBUTORS', 104); define('PAGE_BLOG_ENTRY', 105); define('PAGE_BLOG_FRIENDS', 106); define('PAGE_BLOG_FPOSTING', 107); define('PAGE_BLOG_RSS', 108); define('PAGE_BLOGS', 109); define('PAGE_BLOGS_NEWS', 110); ////(BLOG)(END) Where user ////(GRABBER)(START) Where user define('PAGE_RUTOR', 200); define('PAGE_RUTORREGISTER', 201); define('PAGE_RELEASE', 202); define('PAGE_GRAB_INDEX', 203); define('PAGE_QUOTE', 204); ////(GRABBER)(END) Where user //[END] Where user ####[СОХРАНИТЬ_ФАЙЛ]#### ####[ОТКРЫТЬ]----- init_bb.php -----[найти]----- case 'db_sqlite': if (!isset($this->obj[$cache_name])) { $cache_cfg['pconnect'] = $this->cfg['pconnect']; $cache_cfg['db_file_path'] = $this->get_db_path($cache_name, $cache_cfg, '.sqlite.db'); $cache_cfg['table_name'] = $cache_name; $cache_cfg['table_schema'] = $this->get_table_schema($cache_cfg); $this->obj[$cache_name] = new sqlite_common($cache_cfg); } $this->ref[$cache_name] =& $this->obj[$cache_name]; break; -----[заменить]----- case 'db_sqlite': if (!isset($this->obj[$cache_name])) { $cache_cfg['pconnect'] = $this->cfg['pconnect']; $cache_cfg['db_file_path'] = $this->get_db_path($cache_name, $cache_cfg, '.sqlite.db'); $cache_cfg['table_name'] = $cache_name; $cache_cfg['table_schema'] = $this->get_table_schema($cache_cfg); $cache_cfg['file_name'] = $cache_name.'.sqlite.db'; $cache_cfg['dir'] = $this->cfg['db_dir']; $this->obj[$cache_name] = new sqlite_common($cache_cfg); } $this->ref[$cache_name] =& $this->obj[$cache_name]; break; -----[найти]----- class sqlite_common extends cache_common { var $cfg = array( 'db_file_path' => 'sqlite.db', 'table_name' => 'table_name', 'table_schema' => 'CREATE TABLE table_name (...)', 'pconnect' => true, 'con_required' => true, 'log_name' => 'SQLite', 'shard_type' => 'none', # none, string, int (тип перевичного ключа для шардинга) 'shard_val' => 0, # для string - кол. начальных символов, для int - делитель (будет использован остаток от деления) ); var $engine = 'SQLite'; var $dbh = null; var $connected = false; var $shard_val = false; var $table_create_attempts = 0; function sqlite_common ($cfg) { $this->cfg = array_merge($this->cfg, $cfg); $this->dbg_enabled = sql_dbg_enabled(); } function connect () { $this->cur_query = ($this->dbg_enabled) ? ($this->cfg['pconnect'] ? 'p' : '') .'connect to: '. $this->cfg['db_file_path'] : 'connect'; $this->debug('start'); $connect_type = ($this->cfg['pconnect']) ? 'sqlite_popen' : 'sqlite_open'; if ($this->cfg['shard_type'] != 'none' && $this->shard_val === false) { trigger_error("cannot shard: shard_val not defined for {$this->cfg['db_file_path']}", E_USER_ERROR); } if (@$this->dbh = $connect_type($this->cfg['db_file_path'], 0666, $sqlite_error)) { $this->connected = true; } if (!$this->connected && $this->cfg['con_required']) { trigger_error($sqlite_error, E_USER_ERROR); } $this->debug('stop'); $this->cur_query = null; } function create_table () { $this->table_create_attempts++; return sqlite_query($this->dbh, $this->cfg['table_schema']); } function shard ($name) { $type = $this->cfg['shard_type']; if ($type == 'none') return; if (is_array($name)) trigger_error('cannot shard: $name is array', E_USER_ERROR); // define shard_val if ($type == 'string') { $shard_val = substr($name, 0, $this->cfg['shard_val']); } else { $shard_val = $name % $this->cfg['shard_val']; } // все запросы должны быть к одному и тому же шарду if ($this->shard_val !== false) { if ($shard_val != $this->shard_val) { trigger_error("shard cannot be reassigned. [{$this->shard_val}, $shard_val, $name]", E_USER_ERROR); } else { return; } } $this->shard_val = $shard_val; $this->cfg['db_file_path'] = str_replace('*', $shard_val, $this->cfg['db_file_path']); } function query ($query) { if (!$this->connected) $this->connect(); $this->cur_query = $query; $this->debug('start'); if (!$result = @sqlite_unbuffered_query($this->dbh, $query, SQLITE_ASSOC)) { if (!$this->table_create_attempts && !sqlite_num_rows(sqlite_query($this->dbh, "PRAGMA table_info({$this->cfg['table_name']})"))) { if ($this->create_table()) { $result = sqlite_unbuffered_query($this->dbh, $query, SQLITE_ASSOC); } } if (!$result) { $this->trigger_error($this->get_error_msg()); } } $this->debug('stop'); $this->cur_query = null; $this->num_queries++; return $result; } function fetch_row ($query) { $result = $this->query($query); return is_resource($result) ? sqlite_fetch_array($result, SQLITE_ASSOC) : false; } function fetch_rowset ($query) { $result = $this->query($query); return is_resource($result) ? sqlite_fetch_all($result, SQLITE_ASSOC) : array(); } function changes () { return is_resource($this->dbh) ? sqlite_changes($this->dbh) : 0; } function escape ($str) { return sqlite_escape_string($str); } function get_error_msg () { return 'SQLite error #'. ($err_code = sqlite_last_error($this->dbh)) .': '. sqlite_error_string($err_code); } function rm ($name = '') { if ($name) { $this->db->shard($this->prefix . $name); $result = $this->db->query("DELETE FROM ". $this->cfg['table_name'] ." WHERE cache_name = '". sqlite_escape_string($this->prefix . $name) ."'"); } else { $result = $this->db->query("DELETE FROM ". $this->cfg['table_name']); } return (bool) $result; } function gc ($expire_time = TIMENOW) { $result = $this->db->query("DELETE FROM ". $this->cfg['table_name'] ." WHERE cache_expire_time < $expire_time"); return ($result) ? sqlite_changes($this->db->dbh) : 0; } function trigger_error ($msg = 'DB Error') { if (error_reporting()) trigger_error($msg, E_USER_ERROR); } } -----[заменить]----- class sqlite_common extends cache_common { var $cfg = array( 'db_file_path' => 'sqlite.db', 'table_name' => 'table_name', 'table_schema' => 'CREATE TABLE table_name (...)', 'pconnect' => true, 'con_required' => true, 'log_name' => 'SQLite3', 'shard_type' => 'none', # none, string, int (тип перевичного ключа для шардинга) 'shard_val' => 0, # для string - кол. начальных символов, для int - делитель (будет использован остаток от деления) 'file_name' => 'sqlite.db', 'dir' => null, ); var $engine = 'SQLite3'; var $dbh = null; var $connected = false; var $shard_val = false; var $table_create_attempts = 0; function sqlite_common ($cfg) { $this->cfg = array_merge($this->cfg, $cfg); $this->dbg_enabled = sql_dbg_enabled(); } function connect () { $this->cur_query = ($this->dbg_enabled) ? ($this->cfg['pconnect'] ? 'p' : '') .'connect to: '. $this->cfg['db_file_path'] : 'connect'; $this->debug('start'); if ($this->cfg['shard_type'] != 'none' && $this->shard_val === false) { trigger_error("cannot shard: shard_val not defined for {$this->cfg['db_file_path']}", E_USER_ERROR); } $this->dbh = new SQLite3($this->cfg['db_file_path']); if ($this->dbh) { $this->connected = true; } if (!$this->connected && $this->cfg['con_required']) { trigger_error('Error', E_USER_ERROR); } $this->debug('stop'); $this->cur_query = null; } function create_table () { $this->table_create_attempts++; return $this->dbh->exec($this->cfg['table_schema']); } function shard ($name) { $type = $this->cfg['shard_type']; if ($type == 'none') return; if (is_array($name)) trigger_error('cannot shard: $name is array', E_USER_ERROR); // define shard_val if ($type == 'string') { $shard_val = substr($name, 0, $this->cfg['shard_val']); } else { $shard_val = $name % $this->cfg['shard_val']; } // все запросы должны быть к одному и тому же шарду if ($this->shard_val !== false) { if ($shard_val != $this->shard_val) { trigger_error("shard cannot be reassigned. [{$this->shard_val}, $shard_val, $name]", E_USER_ERROR); } else { return; } } $this->shard_val = $shard_val; $this->cfg['pconnect'] = str_replace('*', $shard_val, $this->cfg['db_file_path']); } function query ($query, $single = false) { if (!$this->connected) $this->connect(); $this->cur_query = $query; $this->debug('start'); $queryIns = ($single) ? 'querySingle':'query'; $mode = ($single) ? @$this->dbh->$queryIns($query,true) : @$this->dbh->$queryIns($query); if (!$result = @$mode) { if (!$this->table_create_attempts && !$this->fetch_column_types($this->cfg['table_name'])) { if ($this->create_table()) { $result = $this->dbh->exec($query); } } } $this->debug('stop'); $this->cur_query = null; $this->num_queries++; return $result; } function fetch_row ($query) { $result = $this->query($query, true); return !empty($result) ? $result : false; } function fetch_rowset ($query) { $rows = array(); $result = $this->query($query); $i = 0; while ($row = $result->fetchArray(SQLITE3_ASSOC)) { $rows[$i]=$row; $i++; } return !empty($result) ? $rows : array(); } function changes () { return ($this->dbh) ? $this->dbh->changes() : 0; } function prepare($row) { return ($this->dbh) ? $this->dbh->prepare($row) : 0; } function escape ($str) { return $this->dbh->escapeString($str); } function get_error_msg () { return 'SQLite error #'. ($err_code = $this->dbh->lastErrorMsg()) .': '. $this->dbh->lastErrorCode(); } function rm ($name = '') { if ($name) { $this->db->shard($this->prefix . $name); $result = $this->db->exec("DELETE FROM ". $this->cfg['table_name'] ." WHERE cache_name = '". $this->dbh->prepare($this->prefix . $name) ."'"); } else { $result = $this->db->exec("DELETE FROM ". $this->cfg['table_name']); } return (bool) $result; } function gc ($expire_time = TIMENOW) { $result = $this->db->exec("DELETE FROM ". $this->cfg['table_name'] ." WHERE cache_expire_time < $expire_time"); return ($result) ? $result->changes() : 0; } function trigger_error ($msg = 'DB Error') { if (error_reporting()) trigger_error($msg, E_USER_ERROR); } function fetch_column_types($table_name) { $col_types = array(); $col_info_res = $this->dbh->query( "PRAGMA table_info('". $table_name . "')"); while ($col_info = $col_info_res->fetchArray(SQLITE3_ASSOC)) { $column_name = $col_info['name']; $column_type = $col_info['type']; $col_types[$column_name] = $column_type; } $col_info_res->finalize(); return $col_types; } } ####[СОХРАНИТЬ_ФАЙЛ]#### ####[ОТКРЫТЬ]----- tpl_config.tpl -----[найти]----- $_lang = $_main . basename('lang_'. $bb_cfg['default_lang']) .'/'; -----[добавить ниже]----- $images['icon_online'] = $_lang .'user-online.png'; $images['icon_offline'] = $_lang .'user-offline.png'; $images['icon_hidden'] = $_lang .'user-hidden.png'; ####[СОХРАНИТЬ_ФАЙЛ]#### ####[ОТКРЫТЬ]----- page_geader.tpl -----[найти]----- <script type="text/javascript" src="{SITE_URL}misc/js/main.js?v={$bb_cfg['js_ver']}"></script> -----[добавить ниже]----- <script type="text/javascript" src="{SITE_URL}misc/js/opt.js?v={$bb_cfg['js_ver']}"></script> <link rel="stylesheet" href="{TPL_DIR}/popup.css?v={$bb_cfg['css_ver']}" type="text/css"> <link rel="stylesheet" href="{TPL_DIR}/popup-user.css?v={$bb_cfg['css_ver']}" type="text/css"> <link rel="stylesheet" href="{TPL_DIR}/AjaxPopup.css?v={$bb_cfg['css_ver']}" type="text/css"> -----[найти]----- <body> -----[добавить ниже]----- <span id="user_popup"></span> ####[СОХРАНИТЬ_ФАЙЛ]#### ####[ОТКРЫТЬ]----- viewtopic.tpl -----[найти]----- </table><!--/pagination--> -----[добавить ниже]----- <div class="category row1 border bw_TRBL mrg_8 pad_4">{WHO_ONLINE_LIST}</div> ####[СОХРАНИТЬ_ФАЙЛ]####