Textpattern PHP Cross Reference Content Management Systems

Source: /textpattern/include/txp_discuss.php - 668 lines - 23180 bytes - Summary - Text - Print

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:.+?)>#', '&lt;$1&gt;', $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).'&#8230;';
 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

title

Description

title

Description

title

title

Body