Textpattern | PHP Cross Reference | Content Management Systems |
Description: Comments panel.
1 <?php 2 3 /* 4 * Textpattern Content Management System 5 * https://textpattern.com/ 6 * 7 * Copyright (C) 2020 The Textpattern Development Team 8 * 9 * This file is part of Textpattern. 10 * 11 * Textpattern is free software; you can redistribute it and/or 12 * modify it under the terms of the GNU General Public License 13 * as published by the Free Software Foundation, version 2. 14 * 15 * Textpattern is distributed in the hope that it will be useful, 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 * GNU General Public License for more details. 19 * 20 * You should have received a copy of the GNU General Public License 21 * along with Textpattern. If not, see <https://www.gnu.org/licenses/>. 22 */ 23 24 /** 25 * Comments panel. 26 * 27 * @package Admin\Discuss 28 */ 29 30 use Textpattern\Validator\ChoiceConstraint; 31 use Textpattern\Validator\Validator; 32 use Textpattern\Search\Filter; 33 34 if (!defined('txpinterface')) { 35 die('txpinterface is undefined.'); 36 } 37 38 if ($event == 'discuss') { 39 require_privs('discuss'); 40 41 if (!get_pref('use_comments', 1)) { 42 require_privs(); 43 } 44 45 $available_steps = array( 46 'discuss_save' => true, 47 'discuss_list' => false, 48 'discuss_edit' => false, 49 'discuss_multi_edit' => true, 50 'discuss_change_pageby' => true, 51 ); 52 53 if ($step && bouncer($step, $available_steps)) { 54 $step(); 55 } else { 56 discuss_list(); 57 } 58 } 59 60 //------------------------------------------------------------- 61 62 function discuss_save() 63 { 64 $varray = array_map('assert_string', gpsa(array('email', 'name', 'web', 'message'))); 65 $varray = $varray + array_map('assert_int', gpsa(array('discussid', 'visible', 'parentid'))); 66 extract(doSlash($varray)); 67 68 $message = $varray['message'] = preg_replace('#<(/?txp:.+?)>#', '<$1>', $message); 69 70 $constraints = array( 71 'status' => new ChoiceConstraint($visible, array( 72 'choices' => array(SPAM, MODERATE, VISIBLE), 73 'message' => 'invalid_status', 74 )), 75 ); 76 77 callback_event_ref('discuss_ui', 'validate_save', 0, $varray, $constraints); 78 $validator = new Validator($constraints); 79 80 if ($validator->validate() && safe_update('txp_discuss', 81 "email = '$email', 82 name = '$name', 83 web = '$web', 84 message = '$message', 85 visible = $visible", 86 "discussid = $discussid" 87 )) { 88 update_comments_count($parentid); 89 update_lastmod('discuss_saved', compact('discussid', 'email', 'name', 'web', 'message', 'visible', 'parentid')); 90 $message = gTxt('comment_updated', array('{id}' => $discussid)); 91 } else { 92 $message = array(gTxt('comment_save_failed'), E_ERROR); 93 } 94 95 discuss_list($message); 96 } 97 98 //------------------------------------------------------------- 99 100 function short_preview($message) 101 { 102 $message = strip_tags($message); 103 $offset = min(120, strlen($message)); 104 105 if (strpos($message, ' ', $offset) !== false) { 106 $maxpos = strpos($message, ' ', $offset); 107 $message = substr($message, 0, $maxpos).'…'; 108 } 109 110 return $message; 111 } 112 113 /** 114 * Outputs the main panel listing all comments. 115 * 116 * @param string|array $message The activity message 117 */ 118 119 function discuss_list($message = '') 120 { 121 global $event; 122 123 pagetop(gTxt('tab_comments'), $message); 124 125 extract(gpsa(array( 126 'sort', 127 'dir', 128 'page', 129 'crit', 130 'search_method', 131 ))); 132 133 if ($sort === '') { 134 $sort = get_pref('discuss_sort_column', 'date'); 135 } else { 136 if (!in_array($sort, array('id', 'name', 'email', 'website', 'message', 'status', 'parent'))) { 137 $sort = 'date'; 138 } 139 140 set_pref('discuss_sort_column', $sort, 'discuss', PREF_HIDDEN, '', 0, PREF_PRIVATE); 141 } 142 143 if ($dir === '') { 144 $dir = get_pref('discuss_sort_dir', 'desc'); 145 } else { 146 $dir = ($dir == 'asc') ? "asc" : "desc"; 147 set_pref('discuss_sort_dir', $dir, 'discuss', PREF_HIDDEN, '', 0, PREF_PRIVATE); 148 } 149 150 switch ($sort) { 151 case 'id': 152 $sort_sql = "txp_discuss.discussid $dir"; 153 break; 154 case 'name': 155 $sort_sql = "txp_discuss.name $dir"; 156 break; 157 case 'email': 158 $sort_sql = "txp_discuss.email $dir"; 159 break; 160 case 'website': 161 $sort_sql = "txp_discuss.web $dir"; 162 break; 163 case 'message': 164 $sort_sql = "txp_discuss.message $dir"; 165 break; 166 case 'status': 167 $sort_sql = "txp_discuss.visible $dir"; 168 break; 169 case 'parent': 170 $sort_sql = "txp_discuss.parentid $dir"; 171 break; 172 default: 173 $sort = 'date'; 174 $sort_sql = "txp_discuss.posted $dir"; 175 break; 176 } 177 178 if ($sort != 'date') { 179 $sort_sql .= ", txp_discuss.posted ASC"; 180 } 181 182 $switch_dir = ($dir == 'desc') ? 'asc' : 'desc'; 183 184 $search = new Filter($event, 185 array( 186 'id' => array( 187 'column' => 'txp_discuss.discussid', 188 'label' => gTxt('id'), 189 'type' => 'integer', 190 ), 191 'parent' => array( 192 'column' => array('txp_discuss.parentid', 'textpattern.Title'), 193 'label' => gTxt('parent'), 194 ), 195 'name' => array( 196 'column' => 'txp_discuss.name', 197 'label' => gTxt('name'), 198 ), 199 'message' => array( 200 'column' => 'txp_discuss.message', 201 'label' => gTxt('message'), 202 ), 203 'email' => array( 204 'column' => 'txp_discuss.email', 205 'label' => gTxt('email'), 206 ), 207 'website' => array( 208 'column' => 'txp_discuss.web', 209 'label' => gTxt('website'), 210 ), 211 'visible' => array( 212 'column' => 'txp_discuss.visible', 213 'label' => gTxt('visible'), 214 'type' => 'numeric', 215 ), 216 ) 217 ); 218 219 $alias_yes = VISIBLE.', Yes'; 220 $alias_no = MODERATE.', No, Unmoderated, Pending'; 221 $alias_spam = SPAM.', Spam'; 222 223 $search->setAliases('visible', array( 224 VISIBLE => $alias_yes, 225 MODERATE => $alias_no, 226 SPAM => $alias_spam, 227 )); 228 229 list($criteria, $crit, $search_method) = $search->getFilter(array('id' => array('can_list' => true))); 230 231 $search_render_options = array('placeholder' => 'search_comments'); 232 233 $sql_from = 234 safe_pfx_j('txp_discuss')." 235 left join ".safe_pfx_j('textpattern')." on txp_discuss.parentid = textpattern.ID"; 236 237 $counts = getRows( 238 "SELECT txp_discuss.visible, COUNT(*) AS c 239 FROM ".safe_pfx_j('txp_discuss')." 240 LEFT JOIN ".safe_pfx_j('textpattern')." 241 ON txp_discuss.parentid = textpattern.ID 242 WHERE $criteria GROUP BY txp_discuss.visible" 243 ); 244 245 $count[SPAM] = $count[MODERATE] = $count[VISIBLE] = 0; 246 247 if ($counts) { 248 foreach ($counts as $c) { 249 $count[$c['visible']] = $c['c']; 250 } 251 } 252 253 // Grand total comment count. 254 $total = $count[SPAM] + $count[MODERATE] + $count[VISIBLE]; 255 $paginator = new \Textpattern\Admin\Paginator($event, 'comment'); 256 $limit = $paginator->getLimit(); 257 list($page, $offset, $numPages) = pager($total, $limit, $page); 258 259 $searchBlock = 260 n.tag( 261 $search->renderForm('discuss_list', $search_render_options), 262 'div', array( 263 'class' => 'txp-layout-4col-3span', 264 'id' => $event.'_control', 265 ) 266 ); 267 268 $contentBlock = ''; 269 270 if ($total < 1) { 271 $contentBlock .= graf( 272 span(null, array('class' => 'ui-icon ui-icon-info')).' '. 273 gTxt($crit === '' ? 'no_comments_recorded' : 'no_results_found'), 274 array('class' => 'alert-block information') 275 ); 276 } else { 277 if (!cs('toggle_show_spam')) { 278 $total = $count[MODERATE] + $count[VISIBLE]; 279 $criteria = 'visible != '.intval(SPAM).' and '.$criteria; 280 } 281 282 $rs = safe_query( 283 "SELECT 284 txp_discuss.discussid, 285 txp_discuss.parentid, 286 txp_discuss.name, 287 txp_discuss.email, 288 txp_discuss.web, 289 txp_discuss.message, 290 txp_discuss.visible, 291 UNIX_TIMESTAMP(txp_discuss.posted) AS uPosted, 292 textpattern.ID AS thisid, 293 textpattern.Section AS section, 294 textpattern.url_title, 295 textpattern.Title AS title, 296 textpattern.Category1 AS category1, 297 textpattern.Category2 AS category2, 298 textpattern.Status, 299 UNIX_TIMESTAMP(textpattern.Posted) AS posted 300 FROM ".safe_pfx_j('txp_discuss')." 301 LEFT JOIN ".safe_pfx_j('textpattern')." ON txp_discuss.parentid = textpattern.ID 302 WHERE $criteria ORDER BY $sort_sql LIMIT $offset, $limit" 303 ); 304 305 if ($rs) { 306 $contentBlock .= n.tag_start('form', array( 307 'class' => 'multi_edit_form', 308 'id' => 'discuss_form', 309 'name' => 'longform', 310 'method' => 'post', 311 'action' => 'index.php', 312 )). 313 n.tag_start('div', array( 314 'class' => 'txp-listtables', 315 'tabindex' => 0, 316 'aria-label' => gTxt('list'), 317 )). 318 n.tag_start('table', array('class' => 'txp-list')). 319 n.tag_start('thead'). 320 tr( 321 hCell( 322 fInput('checkbox', 'select_all', 0, '', '', '', '', '', 'select_all'), 323 '', ' class="txp-list-col-multi-edit" scope="col" title="'.gTxt('toggle_all_selected').'"' 324 ). 325 column_head( 326 'ID', 'id', 'discuss', true, $switch_dir, $crit, $search_method, 327 (('id' == $sort) ? "$dir " : '').'txp-list-col-id' 328 ). 329 column_head( 330 'name', 'name', 'discuss', true, $switch_dir, $crit, $search_method, 331 (('name' == $sort) ? "$dir " : '').'txp-list-col-name' 332 ). 333 column_head( 334 'message', 'message', 'discuss', true, $switch_dir, $crit, $search_method, 335 (('message' == $sort) ? "$dir " : 'txp-list-col-message') 336 ). 337 column_head( 338 'date', 'date', 'discuss', true, $switch_dir, $crit, $search_method, 339 (('date' == $sort) ? "$dir " : '').'txp-list-col-created date' 340 ). 341 column_head( 342 'email', 'email', 'discuss', true, $switch_dir, $crit, $search_method, 343 (('email' == $sort) ? "$dir " : '').'txp-list-col-email' 344 ). 345 column_head( 346 'website', 'website', 'discuss', true, $switch_dir, $crit, $search_method, 347 (('website' == $sort) ? "$dir " : '').'txp-list-col-website' 348 ). 349 column_head( 350 'status', 'status', 'discuss', true, $switch_dir, $crit, $search_method, 351 (('status' == $sort) ? "$dir " : '').'txp-list-col-status' 352 ). 353 column_head( 354 'parent', 'parent', 'discuss', true, $switch_dir, $crit, $search_method, 355 (('parent' == $sort) ? "$dir " : '').'txp-list-col-parent' 356 ) 357 ). 358 n.tag_end('thead'); 359 360 include_once txpath.'/publish/taghandlers.php'; 361 362 $contentBlock .= n.tag_start('tbody'); 363 364 while ($a = nextRow($rs)) { 365 extract($a); 366 $parentid = assert_int($parentid); 367 368 $edit_url = array( 369 'event' => 'discuss', 370 'step' => 'discuss_edit', 371 'discussid' => $discussid, 372 'sort' => $sort, 373 'dir' => $dir, 374 'page' => $page, 375 'search_method' => $search_method, 376 'crit' => $crit, 377 ); 378 379 $dmessage = ($visible == SPAM) ? short_preview($message) : $message; 380 381 switch ($visible) { 382 case VISIBLE: 383 $comment_status = gTxt('visible'); 384 $row_class = 'visible'; 385 break; 386 case SPAM: 387 $comment_status = gTxt('spam'); 388 $row_class = 'spam'; 389 break; 390 case MODERATE: 391 $comment_status = gTxt('unmoderated'); 392 $row_class = 'moderate'; 393 break; 394 default: 395 break; 396 } 397 398 if (empty($thisid)) { 399 $parent = gTxt('article_deleted').' ('.$parentid.')'; 400 $view = ''; 401 } else { 402 $parent_title = empty($title) ? '<em>'.gTxt('untitled').'</em>' : escape_title($title); 403 404 $parent = href( 405 span(gTxt('search'), array('class' => 'ui-icon ui-icon-search')), 406 '?event=discuss'.a.'step=discuss_list'.a.'search_method=parent'.a.'crit='.$parentid).sp. 407 href($parent_title, '?event=article'.a.'step=edit'.a.'ID='.$parentid); 408 409 $view = $comment_status; 410 411 if ($visible == VISIBLE and in_array($Status, array(4, 5))) { 412 $view = href($comment_status, permlinkurl($a).'#c'.$discussid, ' title="'.gTxt('view').'"'); 413 } 414 } 415 416 $contentBlock .= tr( 417 td( 418 fInput('checkbox', 'selected[]', $discussid), '', 'txp-list-col-multi-edit' 419 ). 420 hCell( 421 href($discussid, $edit_url, ' title="'.gTxt('edit').'"'), '', ' class="txp-list-col-id" scope="row"' 422 ). 423 td( 424 txpspecialchars(soft_wrap($name, 15)), '', 'txp-list-col-name' 425 ). 426 td( 427 short_preview($dmessage), '', 'txp-list-col-message' 428 ). 429 td( 430 gTime($uPosted), '', 'txp-list-col-created date' 431 ). 432 td( 433 txpspecialchars(soft_wrap($email, 15)), '', 'txp-list-col-email' 434 ). 435 td( 436 txpspecialchars(soft_wrap($web, 15)), '', 'txp-list-col-website' 437 ). 438 td( 439 $view, '', 'txp-list-col-status' 440 ). 441 td( 442 $parent, '', 'txp-list-col-parent' 443 ), ' class="'.$row_class.'"' 444 ); 445 } 446 447 if (empty($message)) { 448 $contentBlock .= n.tr(tda(gTxt('just_spam_results_found'), ' colspan="10"')); 449 } 450 451 $contentBlock .= n.tag_end('tbody'). 452 n.tag_end('table'). 453 n.tag_end('div'). // End of .txp-listtables. 454 discuss_multiedit_form($page, $sort, $dir, $crit, $search_method). 455 tInput(). 456 n.tag_end('form'); 457 } 458 } 459 460 $createBlock = tag( 461 cookie_box('show_spam'), 462 'div', array('class' => 'txp-list-options')); 463 $pageBlock = $paginator->render(). 464 nav_form($event, $page, $numPages, $sort, $dir, $crit, $search_method, $total, $limit); 465 466 $table = new \Textpattern\Admin\Table($event); 467 echo $table->render(compact('total', 'crit') + array('heading' => 'tab_comments'), $searchBlock, $createBlock, $contentBlock, $pageBlock); 468 } 469 470 /** 471 * Renders and outputs the comment editor panel. 472 */ 473 474 function discuss_edit() 475 { 476 global $event; 477 478 pagetop(gTxt('edit_comment')); 479 480 extract(gpsa(array( 481 'discussid', 482 'sort', 483 'dir', 484 'page', 485 'crit', 486 'search_method', 487 ))); 488 489 $discussid = assert_int($discussid); 490 491 $rs = safe_row("*, UNIX_TIMESTAMP(posted) AS uPosted", 'txp_discuss', "discussid = '$discussid'"); 492 493 if ($rs) { 494 extract($rs); 495 496 $message = txpspecialchars($message); 497 498 $status_list = selectInput( 499 'visible', 500 array( 501 VISIBLE => gTxt('visible'), 502 SPAM => gTxt('spam'), 503 MODERATE => gTxt('unmoderated'), 504 ), 505 $visible, 506 false, 507 '', 508 'status'); 509 510 echo form( 511 hed(gTxt('edit_comment'), 2). 512 inputLabel( 513 'status', 514 $status_list, 515 'status', '', array('class' => 'txp-form-field edit-comment-status') 516 ). 517 inputLabel( 518 'name', 519 fInput('text', 'name', $name, '', '', '', INPUT_REGULAR, '', 'name'), 520 'name', '', array('class' => 'txp-form-field edit-comment-name') 521 ). 522 inputLabel( 523 'email', 524 fInput('email', 'email', $email, '', '', '', INPUT_REGULAR, '', 'email'), 525 'email', '', array('class' => 'txp-form-field edit-comment-email') 526 ). 527 inputLabel( 528 'website', 529 fInput('text', 'web', $web, '', '', '', INPUT_REGULAR, '', 'website'), 530 'website', '', array('class' => 'txp-form-field edit-comment-website') 531 ). 532 inputLabel( 533 'date', 534 safe_strftime('%d %b %Y %X', 535 $uPosted), 536 '', '', array('class' => 'txp-form-field edit-comment-date') 537 ). 538 inputLabel( 539 'commentmessage', 540 '<textarea id="commentmessage" name="message" cols="'.INPUT_LARGE.'" rows="'.TEXTAREA_HEIGHT_MEDIUM.'">'.$message.'</textarea>', 541 'message', '', array('class' => 'txp-form-field txp-form-field-textarea edit-comment-message') 542 ). 543 graf( 544 sLink('discuss', '', gTxt('cancel'), 'txp-button'). 545 fInput('submit', 'step', gTxt('save'), 'publish'), 546 array('class' => 'txp-edit-actions') 547 ). 548 hInput('sort', $sort). 549 hInput('dir', $dir). 550 hInput('page', $page). 551 hInput('crit', $crit). 552 hInput('search_method', $search_method). 553 hInput('discussid', $discussid). 554 hInput('parentid', $parentid). 555 eInput('discuss'). 556 sInput('discuss_save'), 557 '', '', 'post', 'txp-edit', '', 'discuss_edit_form'); 558 } else { 559 echo graf( 560 span(null, array('class' => 'ui-icon ui-icon-info')).' '. 561 gTxt('comment_not_found'), 562 array('class' => 'alert-block information') 563 ); 564 } 565 } 566 567 // ------------------------------------------------------------- 568 569 function discuss_change_pageby() 570 { 571 global $event; 572 573 Txp::get('\Textpattern\Admin\Paginator', $event, 'comment')->change(); 574 discuss_list(); 575 } 576 577 // ------------------------------------------------------------- 578 579 function discuss_multiedit_form($page, $sort, $dir, $crit, $search_method) 580 { 581 $methods = array( 582 'visible' => gTxt('show'), 583 'unmoderated' => gTxt('hide_unmoderated'), 584 'spam' => gTxt('hide_spam'), 585 'delete' => gTxt('delete'), 586 ); 587 588 return multi_edit($methods, 'discuss', 'discuss_multi_edit', $page, $sort, $dir, $crit, $search_method); 589 } 590 591 // ------------------------------------------------------------- 592 593 function discuss_multi_edit() 594 { 595 // FIXME: this method needs some refactoring. 596 $selected = ps('selected'); 597 $method = ps('edit_method'); 598 $visStates = array( 599 'delete' => false, 600 'spam' => SPAM, 601 'unmoderated' => MODERATE, 602 'visible' => VISIBLE, 603 ); 604 $visState = isset($visStates[$method]) ? $visStates[$method] : null; 605 606 if ($selected && is_array($selected) && isset($visState)) { 607 // Get all articles for which we have to update the count. 608 $ids = array(); 609 610 foreach ($selected as $id) { 611 $ids[] = assert_int($id); 612 } 613 614 // Remove bogus (false) entries to prevent SQL syntax errors being thrown. 615 $ids = array_filter($ids); 616 $done = array(); 617 618 if ($ids) { 619 $idList = implode(',', $ids); 620 $parentids = array(); 621 622 $rs = safe_rows_start("*", 'txp_discuss', "discussid IN (".$idList.")"); 623 624 while ($row = nextRow($rs)) { 625 extract($row); 626 627 $id = assert_int($discussid); 628 $parentids[] = $parentid; 629 630 if ($method == 'delete') { 631 // Delete and, if successful, update comment count. 632 if (safe_delete('txp_discuss', "discussid = '$id'")) { 633 $done[] = $id; 634 } 635 } elseif (safe_update('txp_discuss', 636 "visible = ".$visState, 637 "discussid = '$id'" 638 )) { 639 $done[] = $id; 640 } 641 } 642 } 643 644 if ($done) { 645 // Might as well clean up all comment counts while we're here. 646 clean_comment_counts($parentids); 647 648 $doneStr = join(', ', $done); 649 650 $messages = array( 651 'delete' => gTxt('comments_deleted', array('{list}' => $doneStr)), 652 'spam' => gTxt('comments_marked_spam', array('{list}' => $doneStr)), 653 'unmoderated' => gTxt('comments_marked_unmoderated', array('{list}' => $doneStr)), 654 'visible' => gTxt('comments_marked_visible', array('{list}' => $doneStr)), 655 ); 656 657 if ($method == 'delete') { 658 callback_event('discuss_deleted', '', 0, $done); 659 } 660 661 update_lastmod($method == 'delete' ? 'discuss_deleted' : 'discuss_updated', $done); 662 663 return discuss_list($messages[$method]); 664 } 665 } 666 667 return discuss_list(); 668 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
title