Textpattern PHP Cross Reference Content Management Systems

Source: /textpattern/include/txp_list.php - 744 lines - 26872 bytes - Summary - Text - Print

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

title

Description

title

Description

title

Description

title

title

Body