working version of new typos admin panel

parent cbe22840
......@@ -30,6 +30,8 @@ class Authorization extends CI_Controller {
}
function check() {
log_message("error", "login action");
if (!$this->check_login_error()) {
$this->data['error_message'] = "Вы превысили число попыток";
......@@ -40,6 +42,8 @@ class Authorization extends CI_Controller {
$username = $this->input->post('username');
$password = $this->input->post('password');
log_message("error", "username = $username");
if ($username == "" || $password == "") {
$this->data['error_message'] = "Логин/пароль пустой";
$this->load->view($this->view_name, $this->data);
......@@ -63,8 +67,6 @@ class Authorization extends CI_Controller {
$user_info = $this->userHelper->getUser($username);
$password = $this->userHelper->hashPassword($password);
echo var_dump($user_info);
if (!$user_info) {
$this->error_login();
......@@ -73,6 +75,9 @@ class Authorization extends CI_Controller {
return;
} else {
if ($password == $user_info->password) {
log_message("info", "hello world");
if (intval($user_info->activity) == 1) {
$loginData = array(
'login' => $username,
......@@ -108,7 +113,7 @@ class Authorization extends CI_Controller {
unset($_SESSION);
redirect ("authorized");
redirect ("authorization");
}
/*Устанавливаем счетчики ошибок входа*/
......
......@@ -75,7 +75,6 @@ class Sites extends CI_Controller {
/*Управление сайтами*/
function panel_sites() {
$oper = $this->input->post('oper');
log_message('error', "oper = $oper");
if ($oper == 'add') { // Добавление пользователя
$data['site'] = $this->input->post('site');
......
......@@ -14,6 +14,7 @@ class Users extends CI_Controller {
$this->login_id = $this->session->login_id;
$this->usertype = $this->session->usertype;
if ($this->usertype != 'admin') {
redirect('users/typos');
}
......@@ -44,8 +45,6 @@ class Users extends CI_Controller {
/*Получить пользователей*/
function get_list_users() {
error_log("get_list_users");
$data['page'] = $this->input->get('page');
$data['limit'] = $this->input->get('rows', 1);
$data['sord'] = $this->input->get('sord');
......@@ -60,8 +59,7 @@ class Users extends CI_Controller {
/*Получить сайты пользователя*/
function get_user_sites() {
log_message('error', 'get_user_states');
$data['page'] = $this->input->get('page');
$data['limit'] = $this->input->get('rows', 1);
$data['sord'] = $this->input->get('sord');
......@@ -80,8 +78,6 @@ class Users extends CI_Controller {
$oper = $this->input->post('oper');
$data = array();
log_message('error', 'panel_users');
log_message('error', "Oper = $oper");
if ($oper == 'add') {
$data['login'] = $this->input->post('login');
if (strlen($data['login']) < 3) {
......@@ -135,7 +131,6 @@ class Users extends CI_Controller {
return;
} else if ($oper == 'del') {
$data['id_user'] = $this->input->post('id');
log_message('error', "DELETE USSER!!!!!");
$this->user->deleteUser($data);
return;
} else if ($oper == 'edit') {
......
......@@ -7,19 +7,35 @@ class Typos extends CI_Controller {
function __construct() {
parent::__construct();
$this->load->model('users/mdl_typos');
$this->load->model('mdl_session');
$this->login_id = $this->mdl_session->get_data('login_id');
$this->load->model('typo');
$this->load->helper('menu');
$this->login_id = $this->session->userdata("login_id");
$this->header_name = "header";
$this->view_name = "users/typos";
$this->menu_name = "menus/menu";
$this->footer_name = "footer";
}
/*Создаем шаблон*/
function index() {
if ($this->mdl_session->get_data('usertype') == 'admin') {$views['menu']['data']['items'] = $this->mdl_menu->admin();}
if ($this->mdl_session->get_data('usertype') == 'user') {$views['menu']['data']['items'] = $this->mdl_menu->user();}
$views['body']['url'] = "users/sites";
$views['menu']['url'] = "menu";
$this->mdl_views->view($views);
$data['base_url'] = $this->config->base_url();
if ($this->session->usertype == 'admin') {
$data['items'] = menu_admin($data['base_url']);
}
if ($this->session->usertype == 'user') {
$data['items'] = menu_user($data['base_url']);
}
$this->load->view($this->header_name, $data);
$this->load->view($this->menu_name, $data);
$this->load->view($this->view_name, $data);
$this->load->view($this->footer_name, $data);
return true;
}
......@@ -30,83 +46,93 @@ class Typos extends CI_Controller {
/*Получить список сайтов для пользователя*/
function get_list_sites() {
$data['page'] = $this->mdl_post->int('page');
$data['limit'] = $this->mdl_post->int('rows', 1);
$data['sord'] = $this->mdl_post->string('sord');
$data['sidx'] = $this->mdl_post->string('sidx');
$data['search'] = $this->mdl_post->string('_search');
$data['searchField'] = $this->mdl_post->string('searchField');
$data['searchOper'] = $this->mdl_post->string('searchOper');
$data['searchString'] = $this->mdl_post->string('searchString');
$data['page'] = $this->input->get('page');
$data['limit'] = $this->input->get('rows', 1);
$data['sord'] = $this->input->get('sord');
$data['sidx'] = $this->input->get('sidx');
$data['search'] = $this->input->get('_search');
$data['searchField'] = $this->input->get('searchField');
$data['searchOper'] = $this->input->get('searchOper');
$data['searchString'] = $this->input->get('searchString');
$data['login_id'] = $this->login_id;
echo json_encode($this->mdl_typos->get_list_sites($data));
log_message("error", $this->login_id);
echo json_encode($this->typo->getSitesList($data));
}
/*Получить список сообщений об опечатках для пользователя*/
function get_list_messages() {
$data['id_site'] = $this->mdl_post->int("id");
$data['page'] = $this->mdl_post->int('page');
$data['limit'] = $this->mdl_post->int('rows', 1);
$data['sord'] = $this->mdl_post->string('sord');
$data['sidx'] = $this->mdl_post->string('sidx');
$data['search'] = $this->mdl_post->string('_search');
$data['searchField'] = $this->mdl_post->string('searchField');
$data['searchOper'] = $this->mdl_post->string('searchOper');
$data['searchString'] = $this->mdl_post->string('searchString');
$data['id_site'] = $this->input->get("id");
$data['page'] = $this->input->get('page');
$data['limit'] = $this->input->get('rows', 1);
$data['sord'] = $this->input->get('sord');
$data['sidx'] = $this->input->get('sidx');
$data['search'] = $this->input->get('_search');
$data['searchField'] = $this->input->get('searchField');
$data['searchOper'] = $this->input->get('searchOper');
$data['searchString'] = $this->input->get('searchString');
$data['login_id'] = $this->login_id;
echo json_encode($this->mdl_typos->get_list_messages($data));
echo json_encode($this->typo->getMessagesList($data));
}
/*Управление сайтами*/
function panel_sites() {
$id_site = $this->mdl_post->int("id");
$oper = $this->mdl_post->string("oper");
$status = $this->mdl_post->int("status");
$id_site = $this->input->get("id");
$oper = $this->input->get("oper");
$status = $this->input->get("status");
$login_id = $this->login_id;
if ($oper == 'edit') {
if ($status != 0 && $status != 1) {
$status = 1;
}
$data['id_site'] = $id_site;
$data['status'] = $status;
$data['login_id'] = $login_id;
$this->mdl_typos->update_status($data);
$this->typo->updateStatus($data);
}
}
/*Управление сообщениями*/
function panel_messages() {
$oper = $this->mdl_post->string('oper');
$oper = $this->input->get('oper');
$data = array();
if ($oper == 'add') {
$data['id_site'] = $this->mdl_post->int('id_site');
$data['link'] = $this->mdl_post->string('link');
$data['error_text'] = $this->mdl_post->string('error_text');
$data['comment'] = $this->mdl_post->string('comment');
$data['status'] = $this->mdl_post->int('status');
$data['id_site'] = $this->input->get('id_site');
$data['link'] = $this->input->get('link');
$data['error_text'] = $this->input->get('error_text');
$data['comment'] = $this->input->get('comment');
$data['status'] = $this->input->get('status');
if ($data['status'] != 0 && $data['status'] != 1) {
$data['status'] = 1;
}
$data['login_id'] = $this->login_id;
$this->mdl_typos->add_message($data);
$this->typo->addMessage($data);
} else if ($oper == 'del') {
$data['id_message'] = $this->mdl_post->int('id');
$data['id_site'] = $this->mdl_post->int('id_site');
$data['id_message'] = $this->input->get('id');
$data['id_site'] = $this->input->get('id_site');
$data['login_id'] = $this->login_id;
$this->mdl_typos->delete_message($data);
$this->typo->deleteMessage($data);
} else if ($oper == 'edit') {
$data['id_message'] = $this->mdl_post->int('id');
$data['id_site'] = $this->mdl_post->int('id_site');
$data['status'] = $this->mdl_post->int('status');
$data['id_message'] = $this->input->get('id');
$data['id_site'] = $this->input->get('id_site');
$data['status'] = $this->input->get('status');
$data['login_id'] = $this->login_id;
if ($data['status'] != 0 && $data['status'] != 1) {
$data['status'] = 0;
}
$this->mdl_typos->edit_message($data);
$this->typo->editMessages($data);
}
}
......
......@@ -14,7 +14,7 @@ function menu_admin($baseUrl) {
$data['sites'] = "<a href='".$baseUrl."index.php/admins/sites'>Сайты</a>";
$data['users'] = "<a href='".$baseUrl."index.php/admins/users'>Пользователи</a>";
$data['typos'] = "<a href='".$baseUrl."index.php/users/typos'>Опечатки</a>";
$data['logout'] = "<a href='".$baseUrl."index.php/authorized/logout'>Выйти</a>";
$data['logout'] = "<a href='".$baseUrl."index.php/authorization/logout'>Выйти</a>";
return $data;
}
......@@ -23,9 +23,10 @@ function menu_admin($baseUrl) {
* Composes and returns a user menu as array of items
* @return string
*/
function menu_user($config) {
$data['logout'] = "<a href='".$baseUrl."index.php/authorized/logout'>Выйти</a>";
function menu_user($baseUrl) {
$data['typos'] = "<a href='".$baseUrl."index.php/users/typos'>Опечатки</a>";
$data['logout'] = "<a href='".$baseUrl."index.php/authorization/logout'>Выйти</a>";
return $data;
}
......@@ -18,11 +18,7 @@
*/
function searchString($field, $operator, $string) {
$s = " $field ";
log_message('debug', $field);
log_message('debug', $operator);
log_message('debug', $string);
if ( $field == "" ) {
return false;
}
......@@ -81,6 +77,5 @@ function searchString($field, $operator, $string) {
break;
}
log_message('debug', 'return ' . $s);
return $s;
}
\ No newline at end of file
<?php
if (!defined('BASEPATH'))
exit('No direct script access allowed');
/* Работа с опечатками */
class Typo extends CI_Model {
function filterResults($table, $data) {
$page = $data['page'];
$limit = $data['limit'];
$sord = $data['sord'];
$sidx = $data['sidx'];
$id_site = isset($data["id_site"]) ? $data["id_site"] : 0;
$login_id = isset($data["login_id"]) ? $data["login_id"] : 0;
if ( $table == "messages") {
if (!$this->get_right_site($data)) {
return array();
}
}
$search = $data['search'];
$searchstring = "";
if ($search == "true") {
$searchField = $data['searchField'];
$searchOper = $data['searchOper'];
$searchString = $data['searchString'];
$search_string = $this->mdl_search->search_string($searchField, $searchOper, $searchString);
if ($search_string != "") {
$searchstring .= " AND " . $search_string . " ";
}
}
$data = array();
$query_count = "";
if ( $table == "sites" ) {
$query_count = "SELECT COUNT(s.id) AS count
FROM sites AS s
JOIN users AS u
JOIN responsible AS r ON r.id_user=u.id
WHERE u.id = '" . $login_id . "'
AND r.id_site = s.id";
} else {
$query_count = "SELECT COUNT(m.id) AS count
FROM `messages` AS m
JOIN users AS u
JOIN responsible AS r ON r.id_user=u.id
WHERE m.id_site = '" . $id_site . "'
AND u.id = '" . $login_id . "'
AND r.id_site = m.id_site
AND r.id_user = u.id";
}
$count = $this->db->query($query_count)->num_rows();
if ($count > 0) {
$total_pages = ceil($count / $limit);
} else {
$total_pages = 0;
}
if ($page > $total_pages) {
$page = $total_pages;
}
$data['page'] = $page;
$data['total'] = $total_pages;
$data['records'] = $count;
/**/
$start = $limit * $page - $limit;
if ($start < 0) {
$start = 0;
}
/* ЗАПРОС */
if ( $table == "messages" ) {
$this->db->select("m.id as id, m.link as link, m.error_text as text "
. "m.comment as comment, m.date as date, m.status as status");
$this->db->from("messages as m");
$this->db->join("users as u");
$this->db->join("responsible as r", "r.id_user = u.id");
$this->db->where("m.id_site", $id_site);
$this->db->where("u.id", $login_id);
$this->db->where("r.id_site", "m.id_site");
$this->db->where("r.id_user", "u.id");
// $query_string = "SELECT m.id AS id,
// m.link AS link,
// m.error_text AS text,
// m.comment AS comment,
// m.date AS date,
// m.status AS status
// FROM messages AS m
// JOIN users AS u
// JOIN responsible AS r ON r.id_user=u.id
// WHERE m.id_site = '" . $id_site . "'
// AND u.id = '" . $login_id . "'
// AND r.id_site = m.id_site
// AND r.id_user = u.id " . $searchstring . "
// ORDER BY $sidx $sord
// LIMIT $start , $limit";
} else {
$this->db->select("s.id as id, s.site as site, s.status as status, u.*");
$this->db->from("sites as s, users as u");
$this->db->join("responsible as r", "r.id_user = u.id AND r.id_site = s.id");
$this->db->where("u.id", $login_id);
// $query_string = "SELECT s.id AS id, s.site AS site, r.status AS status
// FROM sites AS s
// JOIN users AS u
// JOIN responsible AS r ON r.id_user=u.id
// WHERE u.id='" . $login_id . "'
// AND r.id_site = s.id " . $searchstring . "
// ORDER BY $sidx $sord
// LIMIT $start , $limit";
}
if ( $search == "true" ) {
$this->db->where($searchstring);
}
$this->db->limit($limit, $start);
$this->db->order_by($sidx . " " . $sord);
$results = $this->db->get();
if ( $table == 'sites') {
foreach( $results->result() as $id => $row ) {
$data['rows'][$id]['id'] = $row->id;
$data['rows'][$id]['cell'][] = $row->id;
$data['rows'][$id]['cell'][] = $row->site;
$data['rows'][$id]['cell'][] = $row->status;
}
} else if ( $table == 'messages' ) {
foreach( $results->result() as $id => $row ) {
$data['rows'][$id]['id'] = $row->id;
$data['rows'][$id]['cell'][] = $row->id;
$data['rows'][$id]['cell'][] = anchor($row->link, 'ссылка', array('class' => 'typos_link', 'target' => '_blank'));;
$data['rows'][$id]['cell'][] = $row->text;
$data['rows'][$id]['cell'][] = $row->comment;
$data['rows'][$id]['cell'][] = $row->date;
$data['rows'][$id]['cell'][] = $row->status;
}
}
return $data;
}
//Получаем список сайтов, доступных для пользователя
function getSitesList($data) {
return $this->filterResults("sites", $data);
}
/* Получаем список сообщений об опечатках */
function getMessagesList($data) {
return $this->filterResults("messages", $data);
}
/* Обновляем статус для сайта */
function updateStatus($data) {
if ($this->getSiteRights($data)) {
$this->db->set("status", $data['status']);
$this->db->where("id_user", $data['login_id']);
$this->db->where("id_site", $data['id_site']);
$this->db->update("responsible");
} else {
return false;
}
}
/* Добавляем новое сообщение */
function addMessage($data) {
$insertData = [
"id" => NULL,
"id_site" => $data['id_site'],
"link" => $data['link'],
"error_text" => $data['error_text'],
"comment" => $data['comment'],
"date" => $date('Y-m-d H:i:s', time()),
"status" => $data['status'],
];
if ($this->getSiteRights($data)) {
$this->db->insert('messages', $insertData);
}
}
/* Удаляем сообщение */
function deleteMessage($data) {
if ($this->getMessageRights($data)) {
$this->db->where("id", $data['id_message']);
$this->db->delete("messages");
}
}
/* Обновляем статус сообщения */
function editMessage($data) {
if ($this->getMessageRights($data)) {
$this->db->set("status", $data['status']);
$this->db->where("id", $data['id_message']);
$this->db->where("id_site", $data['id_site']);
$this->db->update("messages");
}
}
/* Узнать права на сайт */
function getSiteRights($data) {
$query = "SELECT r.id_site AS id_site
FROM responsible AS r
JOIN users AS u ON u.id = r.id_user
WHERE u.id = '" . $data['login_id'] . "'
AND r.id_site = '" . $data['id_site'] . "' ";
$this->db->select("r.id_site");
$this->db->from("responsible as r");
$this->db->join("users as u", "u.id = r.id_user");
$this->db->where("u.id", $data['login_id']);
$this->db->where("r.id_site", $data['id_site']);
$row = $this->db->count_all_results();
if ($row) {
return true;
} else {
return false;
}
}
/* Узнать права пользователя на сообщение */
function getMessageRights($data) {
$this->db->select("m.id");
$this->db->from("messages as m");
$this->db->join("sites as s", "m.id_site = s.id");
$this->db->join("users as u");
$this->db->join("responsible as r", "r.id_user = u.id");
$this->db->where("m.id", $data['id_message']);
$this->db->where("u.id", $data['login_id']);
$this->db->where("r.id_site", "s.id");
$this->db->where("s.id", $data['id_site']);
$rows = $this->db->count_all_results();
if ($rows) {
return true;
} else {
return false;
}
}
}
/**/
\ No newline at end of file
......@@ -15,7 +15,9 @@ class UserHelper extends CI_Model
*/
public function getUser($username) {
$query = $this->db->query("SELECT * FROM users WHERE login = '".$username."' LIMIT 1");
return $query->row();
$row = $query->row(0);
return $row;
}
/**
......
......@@ -19,7 +19,6 @@ class Site extends CI_Model {
}
private function filterResults($table, $data) {
log_message('error', "data = " . print_r($data, true));
$this->load->helper("search");
$id_site = isset($data['id_site']) ? $data['id_site'] : 0;
......@@ -124,8 +123,6 @@ class Site extends CI_Model {
return array('message' => 'Сайт не уникален');
}
log_message("error", "here");
$data = array(
'site' => $site['site'],
'date' => date("Y-m-d H:i:s", time())
......
......@@ -25,8 +25,6 @@ class User extends CI_Model {
private function filterResults($table, $data) {
$this->load->helper("search");
log_message('error', "data = " . print_r($data, true));
$id_user = isset($data['id_user']) ? $data['id_user'] : 0;
$page = isset($data['page']) ? $data['page'] : 0;
$limit = isset($data['limit']) ? $data['limit'] : 0;
......@@ -102,8 +100,6 @@ class User extends CI_Model {
$results = $this->db->get();
log_message('error', $this->db->last_query());
if ( $table == 'users') {
foreach( $results->result() as $id => $row ) {
$data['rows'][$id]['id'] = $row->id;
......@@ -204,11 +200,9 @@ class User extends CI_Model {
/*Удаляем пользователя*/
function deleteUser($data) {
log_message('error', print_r($data, true));
$this->db->where('id', $data['id_user']);
$this->db->delete('users');
log_message('error', $this->db->last_query());
$this->db->where('id', $data['id_user']);
$this->db->delete('responsible');
......@@ -216,8 +210,6 @@ class User extends CI_Model {
/*Снимаем ответсвенного*/
function deleteResponsible($data) {
log_message('error', 'deleteResponsible with data = ');
log_message('error', print_r($data, true));
$this->db->where('id_site', $data['id_site']);
$this->db->where('id_user', $data['id_user']);
......@@ -233,8 +225,6 @@ class User extends CI_Model {
$this->db->update('responsible');
log_message('error', $this->db->last_query());
}
/*Проверяем логин на уникальность*/
......
<div class="body">
<!-- -->
<link rel="stylesheet" type="text/css" media="screen" href="<?=$base_url?>javascript/jquery_plugins/jqGrid/4.4.0/css/ui.jqgrid.css" />
<script type="text/javascript" src="<?=$base_url?>javascript/jquery_plugins/jqGrid/4.4.0/js/i18n/grid.locale-ru.js"></script>
<script type="text/javascript" src="<?=$base_url?>javascript/jquery_plugins/jqGrid/4.4.0/js/jquery.jqGrid.min.js"></script>
<script type="text/javascript" src="<?=$base_url?>javascript/jquery_plugins/jqGrid/4.4.0/src/grid.subgrid.js"></script>
<!-- -->
<table id="table_sites"></table>
<div id="additional_panel"></div>
<script type="text/javascript">
jQuery("#table_sites").jqGrid({ //Привязка плагина к таблице
url: TYPOS.base_url+'users/typos/get_list_sites', //Получить список сайтов пользователя
editurl: TYPOS.base_url+'users/typos/panel_sites',
datatype: "json", //Формат скрипта-обработчика
colNames:['Номер', 'Сайт', 'Подписан'],
colModel:[
{name:"id", index:'s.id', width:10, searchtype:"integer", align:'center'},
{name:"site", index:'s.site', align:'center', width:20, searchtype:"string"},
{name:"status", index:'r.status', editable: true, align:'center', width:20, edittype:"checkbox", editoptions: {value:"1:0"}, searchtype:"integer", formatter:'checkbox'}
],
//Подтаблица с сообщениями об опечатках
subGrid : true,
subGridRowExpanded: function(subgrid_id, row_id) {
//Дополнительные переменные
var subgrid_table_id, pager_id;
subgrid_table_id = subgrid_id+"_t";
pager_id = "p_"+subgrid_table_id;
$("#"+subgrid_id).html("<table id='"+subgrid_table_id+"' class='scroll'></table><div id='"+pager_id+"' class='scroll'></div>");
jQuery("#"+subgrid_table_id).jqGrid({
url: TYPOS.base_url+'users/typos/get_list_messages?id='+row_id,
editurl: TYPOS.base_url+'users/typos/panel_messages?id_site='+row_id+'&id='+row_id,
datatype: "json",
colNames: ['Номер', 'Ссылка', 'Текст', 'Комментарий', 'Дата добавления', 'Статус сообщения'],
colModel: [
{name:"id",index:"m.id",width:80, searchtype:"integer", align:'center'},
{name:"link",index:"m.link",width:80, sortable:false, searchtype:"string", align:'center', editable:true},
{name:"error_text",index:"m.error_text",width:80, sortable:false, searchtype:"string", align:'center', editable:true},
{name:"comment",index:"m.comment",width:80, sortable:false, searchtype:"string", align:'center', editable:true },
{name:"date",index:"m.date",width:120, searchtype:"date", align:'center'},
{name:"status",index:"m.status",width:80, editable:true, edittype:"checkbox", editoptions: {value:"1:0"}, searchtype:"integer", align:'center', formatter:'checkbox'},
],
rowNum:20,
pager: pager_id,
sortname: 'm.date',
sortorder: 'desc',
height: '100%',
width: 1000
});
jQuery("#"+subgrid_table_id).jqGrid(
'navGrid',"#"+pager_id,
{edit:true,add:true,del:true},
{
//Скрываем ненужные поля при редактировании
afterShowForm:function() {
$('#tr_link').css('display','none');
$('#tr_error_text').css('display','none');
$('#tr_comment').css('display','none');
},
},
{
//При добавлении показываем поля (т.к. редактирование - скрывает их навсегда)
afterShowForm:function() {
$('#tr_link').css('display','table-row');
$('#tr_error_text').css('display','table-row');
$('#tr_comment').css('display','table-row');
}
}
)
},
caption: "Сайты",
rowNum:10,
rowList:[10,20,30],
width:1200,
height: '100%',
pager: '#additional_panel', //Привязка к таблице тулбара
sortname: 's.id',
viewrecords: true,
sortorder: "asc"
});
jQuery("#table_sites").jqGrid(
'navGrid','#additional_panel', //Управление тулбаром таблицы
{edit:true,add:false,del:false} //Отключаем от тулбара редактирование, добавление и удаление записей. На тулбаре останутся только две кнопки: "Поиск" и "Обновить"
);
</script>
</div>
\ No newline at end of file
var TYPOS = {
base_url: 'http://eterfund.ru/api/typos/cp/',
}
//base_url: 'http://eterfund.ru/api/typos/cp/',
base_url: 'http://ambulance.sandbox.eterhost.ru/typos.server/new_cp/',
};
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment