Textpattern | PHP Cross Reference | Content Management Systems |
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
Body
title
Description
Body
title
Description
Body
title
Body
title