Textpattern PHP Cross Reference Content Management Systems

Source: /textpattern/include/txp_list.php - 749 lines - 27723 bytes - Summary - Text - Print

Description: Articles panel.

   1  <?php
   2  
   3  /*
   4   * Textpattern Content Management System
   5   * http://textpattern.com
   6   *
   7   * Copyright (C) 2005 Dean Allen
   8   * Copyright (C) 2016 The Textpattern Development Team
   9   *
  10   * This file is part of Textpattern.
  11   *
  12   * Textpattern is free software; you can redistribute it and/or
  13   * modify it under the terms of the GNU General Public License
  14   * as published by the Free Software Foundation, version 2.
  15   *
  16   * Textpattern is distributed in the hope that it will be useful,
  17   * but WITHOUT ANY WARRANTY; without even the implied warranty of
  18   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  19   * GNU General Public License for more details.
  20   *
  21   * You should have received a copy of the GNU General Public License
  22   * along with Textpattern. If not, see <http://www.gnu.org/licenses/>.
  23   */
  24  
  25  /**
  26   * Articles panel.
  27   *
  28   * @package Admin\List
  29   */
  30  
  31  use Textpattern\Validator\CategoryConstraint;
  32  use Textpattern\Validator\SectionConstraint;
  33  use Textpattern\Validator\Validator;
  34  use Textpattern\Search\Filter;
  35  
  36  if (!defined('txpinterface')) {
  37      die('txpinterface is undefined.');
  38  }
  39  
  40  if ($event == 'list') {
  41      global $statuses, $all_cats, $all_authors, $all_sections;
  42  
  43      require_privs('article');
  44  
  45      $statuses = status_list();
  46  
  47      $all_cats = getTree('root', 'article');
  48      $all_authors = the_privileged('article.edit.own');
  49      $all_sections = safe_column("name", 'txp_section', "name != 'default'");
  50  
  51      $available_steps = array(
  52          'list_list'          => false,
  53          'list_change_pageby' => true,
  54          'list_multi_edit'    => true,
  55      );
  56  
  57      if ($step && bouncer($step, $available_steps)) {
  58          $step();
  59      } else {
  60          list_list();
  61      }
  62  }
  63  
  64  /**
  65   * The main panel listing all articles.
  66   *
  67   * @param  string|array $message The activity message
  68   * @param  string       $post    Not used
  69   */
  70  
  71  function list_list($message = '', $post = '')
  72  {
  73      global $statuses, $use_comments, $comments_disabled_after, $step, $txp_user, $article_list_pageby, $event;
  74  
  75      pagetop(gTxt('tab_list'), $message);
  76  
  77      extract(gpsa(array(
  78          'page',
  79          'sort',
  80          'dir',
  81          'crit',
  82          'search_method',
  83      )));
  84  
  85      if ($sort === '') {
  86          $sort = get_pref('article_sort_column', 'posted');
  87      } else {
  88          if (!in_array($sort, array('id', 'title', 'expires', 'section', 'category1', 'category2', 'status', 'author', 'comments', 'lastmod'))) {
  89              $sort = 'posted';
  90          }
  91  
  92          set_pref('article_sort_column', $sort, 'list', 2, '', 0, PREF_PRIVATE);
  93      }
  94  
  95      if ($dir === '') {
  96          $dir = get_pref('article_sort_dir', 'desc');
  97      } else {
  98          $dir = ($dir == 'asc') ? "asc" : "desc";
  99          set_pref('article_sort_dir', $dir, 'list', 2, '', 0, PREF_PRIVATE);
 100      }
 101  
 102      $sesutats = array_flip($statuses);
 103  
 104      switch ($sort) {
 105          case 'id':
 106              $sort_sql = "textpattern.ID $dir";
 107              break;
 108          case 'title':
 109              $sort_sql = "textpattern.Title $dir, textpattern.Posted DESC";
 110              break;
 111          case 'expires':
 112              $sort_sql = "textpattern.Expires $dir";
 113              break;
 114          case 'section':
 115              $sort_sql = "section.title $dir, textpattern.Posted DESC";
 116              break;
 117          case 'category1':
 118              $sort_sql = "category1.title $dir, textpattern.Posted DESC";
 119              break;
 120          case 'category2':
 121              $sort_sql = "category2.title $dir, textpattern.Posted DESC";
 122              break;
 123          case 'status':
 124              $sort_sql = "textpattern.Status $dir, textpattern.Posted DESC";
 125              break;
 126          case 'author':
 127              $sort_sql = "user.RealName $dir, textpattern.Posted DESC";
 128              break;
 129          case 'comments':
 130              $sort_sql = "textpattern.comments_count $dir, textpattern.Posted DESC";
 131              break;
 132          case 'lastmod':
 133              $sort_sql = "textpattern.LastMod $dir, textpattern.Posted DESC";
 134              break;
 135          default:
 136              $sort = 'posted';
 137              $sort_sql = "textpattern.Posted $dir";
 138              break;
 139      }
 140  
 141      $switch_dir = ($dir == 'desc') ? 'asc' : 'desc';
 142  
 143      $search = new Filter($event,
 144          array(
 145              'id' => array(
 146                  'column' => 'textpattern.ID',
 147                  'label'  => gTxt('ID'),
 148                  'type'   => 'integer',
 149              ),
 150              'title_body_excerpt' => array(
 151                  'column' => array('textpattern.Title', 'textpattern.Body', 'textpattern.Excerpt'),
 152                  'label'  => gTxt('title_body_excerpt'),
 153              ),
 154              'section' => array(
 155                  'column' => array('textpattern.Section', 'section.title'),
 156                  'label'  => gTxt('section'),
 157              ),
 158              'keywords' => array(
 159                  'column' => 'textpattern.Keywords',
 160                  'label'  => gTxt('keywords'),
 161                  'type'   => 'find_in_set',
 162              ),
 163              'categories' => array(
 164                  'column' => array('textpattern.Category1', 'textpattern.Category2', 'category1.title', 'category2.title'),
 165                  'label'  => gTxt('categories'),
 166              ),
 167              'status' => array(
 168                  'column' => array('textpattern.Status'),
 169                  'label'  => gTxt('status'),
 170                  'type'   => 'boolean',
 171              ),
 172              'author' => array(
 173                  'column' => array('textpattern.AuthorID', 'user.RealName'),
 174                  'label'  => gTxt('author'),
 175              ),
 176              'article_image' => array(
 177                  'column' => array('textpattern.Image'),
 178                  'label'  => gTxt('article_image'),
 179                  'type'   => 'integer',
 180              ),
 181              'posted' => array(
 182                  'column'  => array('textpattern.Posted'),
 183                  'label'   => gTxt('posted'),
 184                  'options' => array('case_sensitive' => true),
 185              ),
 186              'lastmod' => array(
 187                  'column'  => array('textpattern.LastMod'),
 188                  'label'   => gTxt('article_modified'),
 189                  'options' => array('case_sensitive' => true),
 190              ),
 191          )
 192      );
 193  
 194      $search->setAliases('status', $statuses);
 195  
 196      list($criteria, $crit, $search_method) = $search->getFilter(array(
 197              'id'                 => array('can_list' => true),
 198              'article_image'      => array('can_list' => true),
 199              'title_body_excerpt' => array('always_like' => true),
 200          ));
 201  
 202      $search_render_options = array(
 203          'placeholder' => 'search_articles',
 204      );
 205  
 206      $sql_from =
 207          safe_pfx('textpattern')." textpattern
 208          LEFT JOIN ".safe_pfx('txp_category')." category1 ON category1.name = textpattern.Category1 AND category1.type = 'article'
 209          LEFT JOIN ".safe_pfx('txp_category')." category2 ON category2.name = textpattern.Category2 AND category2.type = 'article'
 210          LEFT JOIN ".safe_pfx('txp_section')." section ON section.name = textpattern.Section
 211          LEFT JOIN ".safe_pfx('txp_users')." user ON user.name = textpattern.AuthorID";
 212  
 213      if ($criteria === 1) {
 214          $total = safe_count('textpattern', $criteria);
 215      } else {
 216          $total = getThing("SELECT COUNT(*) FROM $sql_from WHERE $criteria");
 217      }
 218  
 219      echo n.'<div class="txp-layout">'.
 220          n.tag(
 221              hed(gTxt('tab_list'), 1, array('class' => 'txp-heading')),
 222              'div', array('class' => 'txp-layout-4col-alt')
 223          );
 224  
 225      $searchBlock =
 226          n.tag(
 227              $search->renderForm('list', $search_render_options),
 228              'div', array(
 229                  'class' => 'txp-layout-4col-3span',
 230                  'id'    => $event.'_control',
 231              )
 232          );
 233  
 234      $createBlock = array();
 235  
 236      if (has_privs('article.edit')) {
 237          $createBlock[] =
 238              n.tag(
 239                  sLink('article', '', gTxt('add_new_article'), 'txp-button'),
 240                  'div', array('class' => 'txp-control-panel')
 241              );
 242      }
 243  
 244      $contentBlockStart = n.tag_start('div', array(
 245              'class' => 'txp-layout-1col',
 246              'id'    => $event.'_container',
 247          ));
 248  
 249      $createBlock = implode(n, $createBlock);
 250  
 251      if ($total < 1) {
 252          if ($criteria != 1) {
 253              echo $searchBlock.
 254                  $contentBlockStart.
 255                  $createBlock.
 256                  graf(
 257                      span(null, array('class' => 'ui-icon ui-icon-info')).' '.
 258                      gTxt('no_results_found'),
 259                      array('class' => 'alert-block information')
 260                  );
 261          } else {
 262              echo $contentBlockStart.
 263                  $createBlock.
 264                  graf(
 265                      span(null, array('class' => 'ui-icon ui-icon-info')).' '.
 266                      gTxt('no_articles_recorded'),
 267                      array('class' => 'alert-block information')
 268                  );
 269          }
 270  
 271          echo n.tag_end('div'). // End of .txp-layout-1col.
 272              n.'</div>'; // End of .txp-layout.
 273  
 274          return;
 275      }
 276  
 277      $limit = max($article_list_pageby, 15);
 278  
 279      list($page, $offset, $numPages) = pager($total, $limit, $page);
 280  
 281      echo $searchBlock.$contentBlockStart.$createBlock;
 282  
 283      $rs = safe_query(
 284          "SELECT
 285              textpattern.ID, textpattern.Title, textpattern.url_title, textpattern.Section,
 286              textpattern.Category1, textpattern.Category2,
 287              textpattern.Status, textpattern.Annotate, textpattern.AuthorID,
 288              UNIX_TIMESTAMP(textpattern.Posted) AS posted,
 289              UNIX_TIMESTAMP(textpattern.LastMod) AS lastmod,
 290              UNIX_TIMESTAMP(textpattern.Expires) AS expires,
 291              category1.title AS category1_title,
 292              category2.title AS category2_title,
 293              section.title AS section_title,
 294              user.RealName AS RealName,
 295              (SELECT COUNT(*) FROM ".safe_pfx('txp_discuss')." WHERE parentid = textpattern.ID) AS total_comments
 296          FROM $sql_from WHERE $criteria ORDER BY $sort_sql LIMIT $offset, $limit"
 297      );
 298  
 299      if ($rs) {
 300          $show_authors = !has_single_author('textpattern', 'AuthorID');
 301  
 302          echo n.tag(
 303                  toggle_box('articles_detail'), 'div', array('class' => 'txp-list-options')).
 304              n.tag_start('form', array(
 305                  'class'  => 'multi_edit_form',
 306                  'id'     => 'articles_form',
 307                  'name'   => 'longform',
 308                  'method' => 'post',
 309                  'action' => 'index.php',
 310              )).
 311              n.tag_start('div', array('class' => 'txp-listtables')).
 312              n.tag_start('table', array('class' => 'txp-list')).
 313              n.tag_start('thead').
 314              tr(
 315                  hCell(
 316                      fInput('checkbox', 'select_all', 0, '', '', '', '', '', 'select_all'),
 317                          '', ' class="txp-list-col-multi-edit" scope="col" title="'.gTxt('toggle_all_selected').'"'
 318                  ).
 319                  column_head(
 320                      'ID', 'id', 'list', true, $switch_dir, $crit, $search_method,
 321                          (('id' == $sort) ? "$dir " : '').'txp-list-col-id'
 322                  ).
 323                  column_head(
 324                      'title', 'title', 'list', true, $switch_dir, $crit, $search_method,
 325                          (('title' == $sort) ? "$dir " : '').'txp-list-col-title'
 326                  ).
 327                  column_head(
 328                      'posted', 'posted', 'list', true, $switch_dir, $crit, $search_method,
 329                          (('posted' == $sort) ? "$dir " : '').'txp-list-col-created date'
 330                  ).
 331                  column_head(
 332                      'article_modified', 'lastmod', 'list', true, $switch_dir, $crit, $search_method,
 333                          (('lastmod' == $sort) ? "$dir " : '').'txp-list-col-lastmod date articles_detail'
 334                  ).
 335                  column_head(
 336                      'expires', 'expires', 'list', true, $switch_dir, $crit, $search_method,
 337                          (('expires' == $sort) ? "$dir " : '').'txp-list-col-expires date articles_detail'
 338                  ).
 339                  column_head(
 340                      'section', 'section', 'list', true, $switch_dir, $crit, $search_method,
 341                          (('section' == $sort) ? "$dir " : '').'txp-list-col-section'
 342                  ).
 343                  column_head(
 344                      'category1', 'category1', 'list', true, $switch_dir, $crit, $search_method,
 345                          (('category1' == $sort) ? "$dir " : '').'txp-list-col-category1 category articles_detail'
 346                  ).
 347                  column_head(
 348                      'category2', 'category2', 'list', true, $switch_dir, $crit, $search_method,
 349                          (('category2' == $sort) ? "$dir " : '').'txp-list-col-category2 category articles_detail'
 350                  ).
 351                  column_head(
 352                      'status', 'status', 'list', true, $switch_dir, $crit, $search_method,
 353                          (('status' == $sort) ? "$dir " : '').'txp-list-col-status'
 354                  ).
 355                  (
 356                      $show_authors
 357                      ? column_head('author', 'author', 'list', true, $switch_dir, $crit, $search_method,
 358                          (('author' == $sort) ? "$dir " : '').'txp-list-col-author name')
 359                      : ''
 360                  ).
 361                  (
 362                      $use_comments == 1
 363                      ? column_head('comments', 'comments', 'list', true, $switch_dir, $crit, $search_method,
 364                          (('comments' == $sort) ? "$dir " : '').'txp-list-col-comments articles_detail')
 365                      : ''
 366                  )
 367              ).
 368              n.tag_end('thead');
 369  
 370          include_once txpath.'/publish/taghandlers.php';
 371  
 372          echo n.tag_start('tbody');
 373  
 374          $validator = new Validator();
 375  
 376          while ($a = nextRow($rs)) {
 377              extract($a);
 378  
 379              if ($Title === '') {
 380                  $Title = '<em>'.eLink('article', 'edit', 'ID', $ID, gTxt('untitled')).'</em>';
 381              } else {
 382                  $Title = eLink('article', 'edit', 'ID', $ID, $Title);
 383              }
 384  
 385              // Valid section and categories?
 386              $validator->setConstraints(array(new SectionConstraint($Section)));
 387              $vs = $validator->validate() ? '' : ' error';
 388  
 389              $validator->setConstraints(array(new CategoryConstraint($Category1, array('type' => 'article'))));
 390              $vc[1] = $validator->validate() ? '' : ' error';
 391  
 392              $validator->setConstraints(array(new CategoryConstraint($Category2, array('type' => 'article'))));
 393              $vc[2] = $validator->validate() ? '' : ' error';
 394  
 395              $Category1 = ($Category1) ? span(txpspecialchars($category1_title), array('title' => $Category1)) : '';
 396              $Category2 = ($Category2) ? span(txpspecialchars($category2_title), array('title' => $Category2)) : '';
 397  
 398              if ($Status != STATUS_LIVE and $Status != STATUS_STICKY) {
 399                  $view_url = '?txpreview='.intval($ID).'.'.time();
 400              } else {
 401                  $view_url = permlinkurl($a);
 402              }
 403  
 404              if (isset($statuses[$Status])) {
 405                  $Status = $statuses[$Status];
 406              }
 407  
 408              $comments = '('.$total_comments.')';
 409  
 410              if ($total_comments) {
 411                  $comments = href($comments, array(
 412                      'event'         => 'discuss',
 413                      'step'          => 'list',
 414                      'search_method' => 'parent',
 415                      'crit'          => $ID,
 416                  ), array('title' => gTxt('manage')));
 417              }
 418  
 419              $comment_status = ($Annotate) ? gTxt('on') : gTxt('off');
 420  
 421              if ($comments_disabled_after) {
 422                  $lifespan = $comments_disabled_after * 86400;
 423                  $time_since = time() - $posted;
 424  
 425                  if ($time_since > $lifespan) {
 426                      $comment_status = gTxt('expired');
 427                  }
 428              }
 429  
 430              $comments =
 431                  tag($comment_status, 'span', array('class' => 'comments-status')).' '.
 432                  tag($comments, 'span', array('class' => 'comments-manage'));
 433  
 434              echo tr(
 435                  td(
 436                      (
 437                          (
 438                              ($a['Status'] >= STATUS_LIVE and has_privs('article.edit.published'))
 439                              or ($a['Status'] >= STATUS_LIVE and $AuthorID === $txp_user and has_privs('article.edit.own.published'))
 440                              or ($a['Status'] < STATUS_LIVE and has_privs('article.edit'))
 441                              or ($a['Status'] < STATUS_LIVE and $AuthorID === $txp_user and has_privs('article.edit.own'))
 442                          )
 443                      ? fInput('checkbox', 'selected[]', $ID, 'checkbox')
 444                      : ''
 445                      ), '', 'txp-list-col-multi-edit'
 446                  ).
 447                  hCell(
 448                      eLink('article', 'edit', 'ID', $ID, $ID).
 449                      span(
 450                          sp.span('&#124;', array('role' => 'separator')).
 451                          sp.href(gTxt('view'), $view_url),
 452                          array('class' => 'txp-option-link articles_detail')
 453                      ), '', array(
 454                          'class' => 'txp-list-col-id',
 455                          'scope' => 'row',
 456                      )
 457                  ).
 458                  td(
 459                      $Title, '', 'txp-list-col-title'
 460                  ).
 461                  td(
 462                      gTime($posted), '', 'txp-list-col-created date'.($posted < time() ? '' : ' unpublished')
 463                  ).
 464                  td(
 465                      gTime($lastmod), '', 'txp-list-col-lastmod date articles_detail'.($posted === $lastmod ? ' not-modified' : '')
 466                  ).
 467                  td(
 468                      ($expires ? gTime($expires) : ''), '', 'txp-list-col-expires date articles_detail'
 469                  ).
 470                  td(
 471                      span(txpspecialchars($section_title), array('title' => $Section)), '', 'txp-list-col-section'.$vs
 472                  ).
 473                  td(
 474                      $Category1, '', 'txp-list-col-category1 category articles_detail'.$vc[1]
 475                  ).
 476                  td(
 477                      $Category2, '', 'txp-list-col-category2 category articles_detail'.$vc[2]
 478                  ).
 479                  td(
 480                      href($Status, $view_url, join_atts(array('title' => gTxt('view')), TEXTPATTERN_STRIP_EMPTY)), '', 'txp-list-col-status'
 481                  ).
 482                  (
 483                      $show_authors
 484                      ? td(span(txpspecialchars($RealName), array('title' => $AuthorID)), '', 'txp-list-col-author name')
 485                      : ''
 486                  ).
 487                  (
 488                      $use_comments
 489                      ? td($comments, '', 'txp-list-col-comments articles_detail')
 490                      : ''
 491                  )
 492              );
 493          }
 494  
 495          echo n.tag_end('tbody').
 496              n.tag_end('table').
 497              n.tag_end('div'). // End of .txp-listtables.
 498              list_multiedit_form($page, $sort, $dir, $crit, $search_method).
 499              tInput().
 500              n.tag_end('form').
 501              n.tag_start('div', array(
 502                  'class' => 'txp-navigation',
 503                  'id'    => $event.'_navigation',
 504              )).
 505              pageby_form('list', $article_list_pageby).
 506              nav_form('list', $page, $numPages, $sort, $dir, $crit, $search_method, $total, $limit).
 507              n.tag_end('div');
 508      }
 509  
 510      echo n.tag_end('div'). // End of .txp-layout-1col.
 511          n.'</div>'; // End of .txp-layout.
 512  }
 513  
 514  /**
 515   * Saves pageby value for the article list.
 516   */
 517  
 518  function list_change_pageby()
 519  {
 520      event_change_pageby('article');
 521      list_list();
 522  }
 523  
 524  /**
 525   * Renders a multi-edit form widget for articles.
 526   *
 527   * @param  int    $page          The page number
 528   * @param  string $sort          The current sort value
 529   * @param  string $dir           The current sort direction
 530   * @param  string $crit          The current search criteria
 531   * @param  string $search_method The current search method
 532   * @return string HTML
 533   */
 534  
 535  function list_multiedit_form($page, $sort, $dir, $crit, $search_method)
 536  {
 537      global $statuses, $all_cats, $all_authors, $all_sections;
 538  
 539      if ($all_cats) {
 540          $category1 = treeSelectInput('Category1', $all_cats, '');
 541          $category2 = treeSelectInput('Category2', $all_cats, '');
 542      } else {
 543          $category1 = $category2 = '';
 544      }
 545  
 546      $sections = $all_sections ? selectInput('Section', $all_sections, '', true) : '';
 547      $comments = onoffRadio('Annotate', get_pref('comments_on_default'));
 548      $status = selectInput('Status', $statuses, '', true);
 549      $authors = $all_authors ? selectInput('AuthorID', $all_authors, '', true) : '';
 550  
 551      $methods = array(
 552          'changesection'   => array('label' => gTxt('changesection'),   'html' => $sections),
 553          'changecategory1' => array('label' => gTxt('changecategory1'), 'html' => $category1),
 554          'changecategory2' => array('label' => gTxt('changecategory2'), 'html' => $category2),
 555          'changestatus'    => array('label' => gTxt('changestatus'),    'html' => $status),
 556          'changecomments'  => array('label' => gTxt('changecomments'),  'html' => $comments),
 557          'changeauthor'    => array('label' => gTxt('changeauthor'),    'html' => $authors),
 558          'duplicate'       => gTxt('duplicate'),
 559          'delete'          => gTxt('delete'),
 560      );
 561  
 562      if (!$all_cats) {
 563          unset($methods['changecategory1'], $methods['changecategory2']);
 564      }
 565  
 566      if (has_single_author('textpattern', 'AuthorID') || !has_privs('article.edit')) {
 567          unset($methods['changeauthor']);
 568      }
 569  
 570      if (!has_privs('article.delete.own') && !has_privs('article.delete')) {
 571          unset($methods['delete']);
 572      }
 573  
 574      return multi_edit($methods, 'list', 'list_multi_edit', $page, $sort, $dir, $crit, $search_method);
 575  }
 576  
 577  /**
 578   * Processes multi-edit actions.
 579   */
 580  
 581  function list_multi_edit()
 582  {
 583      global $txp_user, $statuses, $all_cats, $all_authors, $all_sections;
 584  
 585      extract(psa(array(
 586          'selected',
 587          'edit_method',
 588      )));
 589  
 590      if (!$selected || !is_array($selected)) {
 591          return list_list();
 592      }
 593  
 594      $selected = array_map('assert_int', $selected);
 595  
 596      // Empty entry to permit clearing the categories.
 597      $categories = array('');
 598  
 599      foreach ($all_cats as $row) {
 600          $categories[] = $row['name'];
 601      }
 602  
 603      $allowed = array();
 604      $field = $value = '';
 605  
 606      switch ($edit_method) {
 607          // Delete.
 608          case 'delete':
 609              if (!has_privs('article.delete')) {
 610                  if (has_privs('article.delete.own')) {
 611                      $allowed = safe_column_num(
 612                          "ID",
 613                          'textpattern',
 614                          "ID IN (".join(',', $selected).") AND AuthorID = '".doSlash($txp_user)."'"
 615                      );
 616                  }
 617  
 618                  $selected = $allowed;
 619              }
 620  
 621              if ($selected && safe_delete('textpattern', "ID IN (".join(',', $selected).")")) {
 622                  safe_update('txp_discuss', "visible = ".MODERATE, "parentid IN (".join(',', $selected).")");
 623                  callback_event('articles_deleted', '', 0, $selected);
 624                  callback_event('multi_edited.articles', 'delete', 0, compact('selected', 'field', 'value'));
 625                  update_lastmod('articles_deleted', $selected);
 626                  now('posted', true);
 627                  now('expires', true);
 628  
 629                  return list_list(messenger('article', join(', ', $selected), 'deleted'));
 630              }
 631  
 632              return list_list();
 633              break;
 634          // Change author.
 635          case 'changeauthor':
 636              $value = ps('AuthorID');
 637              if (has_privs('article.edit') && in_array($value, $all_authors, true)) {
 638                  $field = 'AuthorID';
 639              }
 640              break;
 641  
 642          // Change category1.
 643          case 'changecategory1':
 644              $value = ps('Category1');
 645              if (in_array($value, $categories, true)) {
 646                  $field = 'Category1';
 647              }
 648              break;
 649          // Change category2.
 650          case 'changecategory2':
 651              $value = ps('Category2');
 652              if (in_array($value, $categories, true)) {
 653                  $field = 'Category2';
 654              }
 655              break;
 656          // Change comment status.
 657          case 'changecomments':
 658              $field = 'Annotate';
 659              $value = (int) ps('Annotate');
 660              break;
 661          // Change section.
 662          case 'changesection':
 663              $value = ps('Section');
 664              if (in_array($value, $all_sections, true)) {
 665                  $field = 'Section';
 666              }
 667              break;
 668          // Change status.
 669          case 'changestatus':
 670              $value = (int) ps('Status');
 671              if (array_key_exists($value, $statuses)) {
 672                  $field = 'Status';
 673              }
 674  
 675              if (!has_privs('article.publish') && $value >= STATUS_LIVE) {
 676                  $value = STATUS_PENDING;
 677              }
 678              break;
 679      }
 680  
 681      $selected = safe_rows(
 682          "ID, AuthorID, Status",
 683          'textpattern',
 684          "ID IN (".join(',', $selected).")"
 685      );
 686  
 687      foreach ($selected as $item) {
 688          if (
 689              ($item['Status'] >= STATUS_LIVE && has_privs('article.edit.published')) ||
 690              ($item['Status'] >= STATUS_LIVE && $item['AuthorID'] === $txp_user && has_privs('article.edit.own.published')) ||
 691              ($item['Status'] < STATUS_LIVE && has_privs('article.edit')) ||
 692              ($item['Status'] < STATUS_LIVE && $item['AuthorID'] === $txp_user && has_privs('article.edit.own'))
 693          ) {
 694              $allowed[] = $item['ID'];
 695          }
 696      }
 697  
 698      $selected = $allowed;
 699  
 700      if ($selected) {
 701          $message = messenger('article', join(', ', $selected), 'modified');
 702  
 703          if ($edit_method === 'duplicate') {
 704              $rs = safe_rows_start("*", 'textpattern', "ID IN (".join(',', $selected).")");
 705  
 706              if ($rs) {
 707                  while ($a = nextRow($rs)) {
 708                      unset($a['ID'], $a['comments_count']);
 709                      $a['uid'] = md5(uniqid(rand(), true));
 710                      $a['AuthorID'] = $txp_user;
 711                      $a['LastModID'] = $txp_user;
 712                      $a['Status'] = ($a['Status'] >= STATUS_LIVE) ? STATUS_DRAFT : $a['Status'];
 713  
 714                      foreach ($a as $name => &$value) {
 715                          if ($name == 'Expires' && !$value) {
 716                              $value = "Expires = NULL";
 717                          } else {
 718                              $value = "`$name` = '".doSlash($value)."'";
 719                          }
 720                      }
 721  
 722                      if ($id = (int) safe_insert('textpattern', join(',', $a))) {
 723                          safe_update(
 724                              'textpattern',
 725                              "Title     = CONCAT(Title, ' (', $id, ')'),
 726                               url_title = CONCAT(url_title, '-', $id),
 727                               LastMod   = NOW(),
 728                               feed_time = NOW()",
 729                              "ID = $id"
 730                          );
 731                      }
 732                  }
 733              }
 734  
 735              $message = gTxt('duplicated_articles', array('{id}' => join(', ', $selected)));
 736          } elseif (!$field || safe_update('textpattern', "$field = '".doSlash($value)."'", "ID IN (".join(',', $selected).")") === false) {
 737              return list_list();
 738          }
 739  
 740          update_lastmod('articles_updated', compact('selected', 'field', 'value'));
 741          now('posted', true);
 742          now('expires', true);
 743          callback_event('multi_edited.articles', $edit_method, 0, compact('selected', 'field', 'value'));
 744  
 745          return list_list($message);
 746      }
 747  
 748      return list_list();
 749  }

title

Description

title

Description

title

Description

title

title

Body