Textpattern | PHP Cross Reference | Content Management Systems |
Description: Links 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 * Links panel. 26 * 27 * @package Admin\Link 28 */ 29 30 use Textpattern\Validator\CategoryConstraint; 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 == 'link') { 39 require_privs('link'); 40 41 global $vars; 42 $vars = array( 43 'category', 44 'url', 45 'linkname', 46 'linksort', 47 'description', 48 'id', 49 'publish_now', 50 'date', 51 'year', 52 'month', 53 'day', 54 'hour', 55 'minute', 56 'second', 57 ); 58 59 global $all_link_cats, $all_link_authors; 60 $all_link_cats = getTree('root', 'link'); 61 $all_link_authors = the_privileged('link.edit.own', true); 62 63 $available_steps = array( 64 'link_list' => false, 65 'link_edit' => false, 66 'link_save' => true, 67 'link_change_pageby' => true, 68 'link_multi_edit' => true, 69 ); 70 71 if ($step && bouncer($step, $available_steps)) { 72 $step(); 73 } else { 74 link_list(); 75 } 76 } 77 78 /** 79 * The main panel listing all links. 80 * 81 * @param string|array $message The activity message 82 */ 83 84 function link_list($message = '') 85 { 86 global $event, $step, $txp_user; 87 88 pagetop(gTxt('tab_link'), $message); 89 90 extract(gpsa(array( 91 'page', 92 'sort', 93 'dir', 94 'crit', 95 'search_method', 96 ))); 97 98 if ($sort === '') { 99 $sort = get_pref('link_sort_column', 'name'); 100 } else { 101 if (!in_array($sort, array('id', 'description', 'url', 'category', 'date', 'author'))) { 102 $sort = 'name'; 103 } 104 105 set_pref('link_sort_column', $sort, 'link', PREF_HIDDEN, '', 0, PREF_PRIVATE); 106 } 107 108 if ($dir === '') { 109 $dir = get_pref('link_sort_dir', 'asc'); 110 } else { 111 $dir = ($dir == 'desc') ? "desc" : "asc"; 112 set_pref('link_sort_dir', $dir, 'link', PREF_HIDDEN, '', 0, PREF_PRIVATE); 113 } 114 115 switch ($sort) { 116 case 'id': 117 $sort_sql = "txp_link.id $dir"; 118 break; 119 case 'description': 120 $sort_sql = "txp_link.description $dir, txp_link.id ASC"; 121 break; 122 case 'url': 123 $sort_sql = "txp_link.url $dir, txp_link.id ASC"; 124 break; 125 case 'category': 126 $sort_sql = "txp_category.title $dir, txp_link.id ASC"; 127 break; 128 case 'date': 129 $sort_sql = "txp_link.date $dir, txp_link.id ASC"; 130 break; 131 case 'author': 132 $sort_sql = "txp_users.RealName $dir, txp_link.id ASC"; 133 break; 134 default: 135 $sort = 'name'; 136 $sort_sql = "txp_link.linksort $dir, txp_link.id ASC"; 137 break; 138 } 139 140 $switch_dir = ($dir == 'desc') ? 'asc' : 'desc'; 141 142 $search = new Filter($event, 143 array( 144 'id' => array( 145 'column' => 'txp_link.id', 146 'label' => gTxt('id'), 147 'type' => 'integer', 148 ), 149 'name' => array( 150 'column' => 'txp_link.linkname', 151 'label' => gTxt('title'), 152 ), 153 'url' => array( 154 'column' => 'txp_link.url', 155 'label' => gTxt('url'), 156 ), 157 'description' => array( 158 'column' => 'txp_link.description', 159 'label' => gTxt('description'), 160 ), 161 'category' => array( 162 'column' => array('txp_link.category', 'txp_category.title'), 163 'label' => gTxt('category'), 164 ), 165 'author' => array( 166 'column' => array('txp_link.author', 'txp_users.RealName'), 167 'label' => gTxt('author'), 168 ), 169 'linksort' => array( 170 'column' => 'txp_link.linksort', 171 'label' => gTxt('sort_value'), 172 ), 173 ) 174 ); 175 176 list($criteria, $crit, $search_method) = $search->getFilter(array('id' => array('can_list' => true))); 177 178 $search_render_options = array('placeholder' => 'search_links'); 179 180 $sql_from = 181 safe_pfx_j('txp_link')." 182 LEFT JOIN ".safe_pfx_j('txp_category')." ON txp_category.name = txp_link.category AND txp_category.type = 'link' 183 LEFT JOIN ".safe_pfx_j('txp_users')." ON txp_users.name = txp_link.author"; 184 185 if ($crit === '') { 186 $total = safe_count('txp_link', $criteria); 187 } else { 188 $total = getThing("SELECT COUNT(*) FROM $sql_from WHERE $criteria"); 189 } 190 191 $searchBlock = 192 n.tag( 193 $search->renderForm('link_list', $search_render_options), 194 'div', array( 195 'class' => 'txp-layout-4col-3span', 196 'id' => $event.'_control', 197 ) 198 ); 199 200 $createBlock = array(); 201 202 if (has_privs('link.edit')) { 203 $createBlock[] = 204 n.tag( 205 sLink('link', 'link_edit', gTxt('create_link'), 'txp-button'), 206 'div', array('class' => 'txp-control-panel') 207 ); 208 } 209 210 $createBlock = implode(n, $createBlock); 211 $contentBlock = ''; 212 213 $paginator = new \Textpattern\Admin\Paginator(); 214 $limit = $paginator->getLimit(); 215 216 list($page, $offset, $numPages) = pager($total, $limit, $page); 217 218 if ($total < 1) { 219 $contentBlock .= graf( 220 span(null, array('class' => 'ui-icon ui-icon-info')).' '. 221 gTxt($crit === '' ? 'no_links_recorded' : 'no_results_found'), 222 array('class' => 'alert-block information') 223 ); 224 } else { 225 $rs = safe_query( 226 "SELECT 227 txp_link.id, 228 UNIX_TIMESTAMP(txp_link.date) AS uDate, 229 txp_link.category, 230 txp_link.url, 231 txp_link.linkname, 232 txp_link.description, 233 txp_link.author, 234 txp_users.RealName AS realname, 235 txp_category.Title AS category_title 236 FROM $sql_from WHERE $criteria ORDER BY $sort_sql LIMIT $offset, $limit" 237 ); 238 239 if ($rs && numRows($rs)) { 240 $show_authors = !has_single_author('txp_link'); 241 242 $contentBlock .= n.tag_start('form', array( 243 'class' => 'multi_edit_form', 244 'id' => 'links_form', 245 'name' => 'longform', 246 'method' => 'post', 247 'action' => 'index.php', 248 )). 249 n.tag_start('div', array( 250 'class' => 'txp-listtables', 251 'tabindex' => 0, 252 'aria-label' => gTxt('list'), 253 )). 254 n.tag_start('table', array('class' => 'txp-list')). 255 n.tag_start('thead'). 256 tr( 257 hCell( 258 fInput('checkbox', 'select_all', 0, '', '', '', '', '', 'select_all'), 259 '', ' class="txp-list-col-multi-edit" scope="col" title="'.gTxt('toggle_all_selected').'"' 260 ). 261 column_head( 262 'ID', 'id', 'link', true, $switch_dir, $crit, $search_method, 263 (('id' == $sort) ? "$dir " : '').'txp-list-col-id' 264 ). 265 column_head( 266 'title', 'name', 'link', true, $switch_dir, $crit, $search_method, 267 (('name' == $sort) ? "$dir " : '').'txp-list-col-name' 268 ). 269 column_head( 270 'description', 'description', 'link', true, $switch_dir, $crit, $search_method, 271 (('description' == $sort) ? "$dir " : '').'txp-list-col-description' 272 ). 273 column_head( 274 'date', 'date', 'link', true, $switch_dir, $crit, $search_method, 275 (('date' == $sort) ? "$dir " : '').'txp-list-col-created date' 276 ). 277 column_head( 278 'category', 'category', 'link', true, $switch_dir, $crit, $search_method, 279 (('category' == $sort) ? "$dir " : '').'txp-list-col-category category' 280 ). 281 column_head( 282 'url', 'url', 'link', true, $switch_dir, $crit, $search_method, 283 (('url' == $sort) ? "$dir " : '').'txp-list-col-url' 284 ). 285 ( 286 $show_authors 287 ? column_head('author', 'author', 'link', true, $switch_dir, $crit, $search_method, 288 (('author' == $sort) ? "$dir " : '').'txp-list-col-author name') 289 : '' 290 ) 291 ). 292 n.tag_end('thead'). 293 n.tag_start('tbody'); 294 295 $validator = new Validator(); 296 297 while ($a = nextRow($rs)) { 298 extract($a, EXTR_PREFIX_ALL, 'link'); 299 300 $edit_url = array( 301 'event' => 'link', 302 'step' => 'link_edit', 303 'id' => $link_id, 304 'sort' => $sort, 305 'dir' => $dir, 306 'page' => $page, 307 'search_method' => $search_method, 308 'crit' => $crit, 309 ); 310 311 $validator->setConstraints(array(new CategoryConstraint($link_category, array('type' => 'link')))); 312 $vc = $validator->validate() ? '' : ' error'; 313 314 if ($link_category) { 315 $link_category = span(txpspecialchars($link_category_title), array('title' => $link_category)); 316 } 317 318 $can_edit = has_privs('link.edit') || ($link_author === $txp_user && has_privs('link.edit.own')); 319 $view_url = txpspecialchars($link_url); 320 321 $contentBlock .= tr( 322 td( 323 fInput('checkbox', 'selected[]', $link_id), '', 'txp-list-col-multi-edit' 324 ). 325 hCell( 326 ($can_edit ? href($link_id, $edit_url, ' title="'.gTxt('edit').'"') : $link_id), '', ' class="txp-list-col-id" scope="row"' 327 ). 328 td( 329 ($can_edit ? href(txpspecialchars($link_linkname), $edit_url, ' title="'.gTxt('edit').'"') : txpspecialchars($link_linkname)), '', 'txp-list-col-name' 330 ). 331 td( 332 txpspecialchars($link_description), '', 'txp-list-col-description' 333 ). 334 td( 335 gTime($link_uDate), '', 'txp-list-col-created date' 336 ). 337 td( 338 $link_category, '', 'txp-list-col-category category'.$vc 339 ). 340 td( 341 href($view_url, $view_url, ' rel="external noopener" target="_blank"'), '', 'txp-list-col-url txp-contain' 342 ). 343 ( 344 $show_authors 345 ? td(span(txpspecialchars($link_realname), array('title' => $link_author)), '', 'txp-list-col-author name') 346 : '' 347 ) 348 ); 349 } 350 351 $contentBlock .= n.tag_end('tbody'). 352 n.tag_end('table'). 353 n.tag_end('div'). 354 link_multiedit_form($page, $sort, $dir, $crit, $search_method). 355 tInput(). 356 n.tag_end('form'); 357 } 358 } 359 360 $pageBlock = $paginator->render(). 361 nav_form('link', $page, $numPages, $sort, $dir, $crit, $search_method, $total, $limit); 362 363 $table = new \Textpattern\Admin\Table($event); 364 echo $table->render(compact('total', 'crit'), $searchBlock, $createBlock, $contentBlock, $pageBlock); 365 } 366 367 /** 368 * Renders and outputs the link editor panel. 369 * 370 * @param string|array $message The activity message 371 */ 372 373 function link_edit($message = '') 374 { 375 global $vars, $event, $step, $txp_user; 376 377 pagetop(gTxt('tab_link'), $message); 378 379 extract(array_map('assert_string', gpsa($vars))); 380 381 $is_edit = ($id && $step == 'link_edit'); 382 383 $rs = array(); 384 385 if ($is_edit) { 386 $id = assert_int($id); 387 $rs = safe_row("*, UNIX_TIMESTAMP(date) AS date", 'txp_link', "id = '$id'"); 388 389 if ($rs) { 390 extract($rs); 391 392 if (!has_privs('link.edit') && !($author === $txp_user && has_privs('link.edit.own'))) { 393 require_privs('link.edit'); 394 395 return; 396 } 397 } else { 398 link_list(array(gTxt('unknown_link'), E_ERROR)); 399 } 400 } 401 402 if (has_privs('link.edit') || has_privs('link.edit.own')) { 403 $caption = gTxt($is_edit ? 'edit_link' : 'create_link'); 404 $created = 405 inputLabel( 406 'year', 407 tsi('year', '%Y', $date, '', 'year'). 408 ' <span role="separator">/</span> '. 409 tsi('month', '%m', $date, '', 'month'). 410 ' <span role="separator">/</span> '. 411 tsi('day', '%d', $date, '', 'day'), 412 'publish_date', 413 array('timestamp_link', 'instructions_link_date'), 414 array('class' => 'txp-form-field date posted') 415 ). 416 inputLabel( 417 'hour', 418 tsi('hour', '%H', $date, '', 'hour'). 419 ' <span role="separator">:</span> '. 420 tsi('minute', '%M', $date, '', 'minute'). 421 ' <span role="separator">:</span> '. 422 tsi('second', '%S', $date, '', 'second'), 423 'publish_time', 424 array('', 'instructions_link_time'), 425 array('class' => 'txp-form-field time posted') 426 ). 427 n.tag( 428 checkbox('publish_now', '1', $publish_now, '', 'publish_now'). 429 n.tag(gTxt('set_to_now'), 'label', array('for' => 'publish_now')), 430 'div', array('class' => 'txp-form-field-shim posted-now') 431 ); 432 echo form( 433 hed($caption, 2). 434 inputLabel( 435 'link_name', 436 fInput('text', 'linkname', $linkname, '', '', '', INPUT_REGULAR, '', 'link_name', false, true), 437 'title', '', array('class' => 'txp-form-field edit-link-name') 438 ). 439 inputLabel( 440 'link_sort', 441 fInput('text', 'linksort', $linksort, 'input-medium', '', '', INPUT_MEDIUM, '', 'link_sort'), 442 'sort_value', 'link_sort', array('class' => 'txp-form-field edit-link-sort') 443 ). 444 // TODO: maybe use type="url" once browsers are less strict. 445 inputLabel( 446 'link_url', 447 fInput('text', 'url', $url, '', '', '', INPUT_REGULAR, '', 'link_url'), 448 'url', 'link_url', array('class' => 'txp-form-field edit-link-url') 449 ). 450 inputLabel( 451 'link_category', 452 event_category_popup('link', $category, 'link_category'). 453 n.eLink('category', 'list', '', '', gTxt('edit'), '', '', '', 'txp-option-link'), 454 'category', 'link_category', array('class' => 'txp-form-field edit-link-category') 455 ). 456 $created. 457 inputLabel( 458 'link_description', 459 '<textarea id="link_description" name="description" cols="'.INPUT_LARGE.'" rows="'.TEXTAREA_HEIGHT_SMALL.'">'.txpspecialchars($description).'</textarea>', 460 'description', 'link_description', array('class' => 'txp-form-field txp-form-field-textarea edit-link-description') 461 ). 462 pluggable_ui('link_ui', 'extend_detail_form', '', $rs). 463 graf( 464 sLink('link', '', gTxt('cancel'), 'txp-button'). 465 fInput('submit', '', gTxt('save'), 'publish'), 466 array('class' => 'txp-edit-actions') 467 ). 468 eInput('link'). 469 sInput('link_save'). 470 hInput('id', $id). 471 hInput('sort', gps('sort')). 472 hInput('dir', gps('dir')). 473 hInput('page', gps('page')). 474 hInput('search_method', gps('search_method')). 475 hInput('crit', gps('crit')), 476 '', '', 'post', 'txp-edit', '', 'link_details'); 477 } 478 } 479 480 /** 481 * Legacy link category HTML select field. 482 * 483 * @param string $cat 484 * @return string 485 * @deprecated in 4.6.0 486 */ 487 488 function linkcategory_popup($cat = '') 489 { 490 return event_category_popup('link', $cat, 'link_category'); 491 } 492 493 // ------------------------------------------------------------- 494 495 function link_save() 496 { 497 global $vars, $txp_user; 498 499 $varray = array_map('assert_string', gpsa($vars)); 500 extract(doSlash($varray)); 501 502 if ($id) { 503 $id = $varray['id'] = assert_int($id); 504 } 505 506 if ($linkname === '' && $url === '' && $description === '') { 507 link_list(array(gTxt('link_empty'), E_ERROR)); 508 509 return; 510 } 511 512 $author = fetch('author', 'txp_link', 'id', $id); 513 514 if (!has_privs('link.edit') && !($author === $txp_user && has_privs('link.edit.own'))) { 515 require_privs('link.edit'); 516 517 return; 518 } 519 520 if (!$linksort) { 521 $linksort = $linkname; 522 } 523 524 $created_ts = @safe_strtotime($year.'-'.$month.'-'.$day.' '.$hour.':'.$minute.':'.$second); 525 $created = "NOW()"; 526 527 if (!$publish_now && $created_ts > 0) { 528 $created = "FROM_UNIXTIME('".$created_ts."')"; 529 } 530 531 $constraints = array( 532 'category' => new CategoryConstraint($varray['category'], array('type' => 'link')), 533 ); 534 535 callback_event_ref('link_ui', 'validate_save', 0, $varray, $constraints); 536 $validator = new Validator($constraints); 537 538 if ($validator->validate()) { 539 if ($id) { 540 $ok = safe_update('txp_link', 541 "category = '$category', 542 url = '".trim($url)."', 543 linkname = '$linkname', 544 linksort = '$linksort', 545 date = $created, 546 description = '$description', 547 author = '".doSlash($txp_user)."'", 548 "id = $id" 549 ); 550 } else { 551 $ok = safe_insert('txp_link', 552 "category = '$category', 553 url = '".trim($url)."', 554 linkname = '$linkname', 555 linksort = '$linksort', 556 date = $created, 557 description = '$description', 558 author = '".doSlash($txp_user)."'" 559 ); 560 if ($ok) { 561 $GLOBALS['ID'] = $_POST['id'] = $ok; 562 } 563 } 564 565 if ($ok) { 566 $message = gTxt(($id ? 'link_updated' : 'link_created'), array('{name}' => doStrip($linkname))); 567 568 // Update lastmod due to link feeds. 569 $id = empty($id) ? $ok : $id; 570 update_lastmod('link_saved', compact('id', 'linkname', 'linksort', 'url', 'category', 'description')); 571 } else { 572 $message = array(gTxt('link_save_failed'), E_ERROR); 573 } 574 } else { 575 $message = array(gTxt('link_save_failed'), E_ERROR); 576 } 577 578 link_list($message); 579 } 580 581 // ------------------------------------------------------------- 582 583 function link_change_pageby() 584 { 585 Txp::get('\Textpattern\Admin\Paginator')->change(); 586 link_list(); 587 } 588 589 // ------------------------------------------------------------- 590 591 function link_multiedit_form($page, $sort, $dir, $crit, $search_method) 592 { 593 global $all_link_cats, $all_link_authors; 594 595 $categories = $all_link_cats ? treeSelectInput('category', $all_link_cats, '') : ''; 596 $authors = $all_link_authors ? selectInput('author', $all_link_authors, '', true) : ''; 597 598 $methods = array( 599 'changecategory' => array( 600 'label' => gTxt('changecategory'), 601 'html' => $categories 602 ), 603 'changeauthor' => array( 604 'label' => gTxt('changeauthor'), 605 'html' => $authors 606 ), 607 'delete' => gTxt('delete'), 608 ); 609 610 if (!$categories) { 611 unset($methods['changecategory']); 612 } 613 614 if (has_single_author('txp_link') || !has_privs('link.edit')) { 615 unset($methods['changeauthor']); 616 } 617 618 if (!has_privs('link.delete.own') && !has_privs('link.delete')) { 619 unset($methods['delete']); 620 } 621 622 return multi_edit($methods, 'link', 'link_multi_edit', $page, $sort, $dir, $crit, $search_method); 623 } 624 625 // ------------------------------------------------------------- 626 627 function link_multi_edit() 628 { 629 global $txp_user, $all_link_cats, $all_link_authors; 630 631 // Empty entry to permit clearing the category. 632 $categories = array(''); 633 634 foreach ($all_link_cats as $row) { 635 $categories[] = $row['name']; 636 } 637 638 $selected = ps('selected'); 639 640 if (!$selected || !is_array($selected)) { 641 link_list(); 642 643 return; 644 } 645 646 // Fetch and remove bogus (false) entries to prevent SQL syntax errors being thrown. 647 $selected = array_map('assert_int', $selected); 648 $selected = array_filter($selected); 649 $method = ps('edit_method'); 650 $changed = array(); 651 $key = ''; 652 653 switch ($method) { 654 case 'delete': 655 if (!has_privs('link.delete')) { 656 if ($selected && has_privs('link.delete.own')) { 657 $selected = safe_column("id", 'txp_link', "id IN (".join(',', $selected).") AND author = '".doSlash($txp_user)."'"); 658 } else { 659 $selected = array(); 660 } 661 } 662 663 foreach ($selected as $id) { 664 if (safe_delete('txp_link', "id = '$id'")) { 665 $changed[] = $id; 666 } 667 } 668 669 if ($changed) { 670 callback_event('links_deleted', '', 0, $changed); 671 } 672 673 $key = ''; 674 break; 675 case 'changecategory': 676 $val = ps('category'); 677 if (in_array($val, $categories)) { 678 $key = 'category'; 679 } 680 break; 681 case 'changeauthor': 682 $val = ps('author'); 683 if (has_privs('link.edit') && isset($all_link_authors[$val])) { 684 $key = 'author'; 685 } 686 break; 687 default: 688 $key = ''; 689 $val = ''; 690 break; 691 } 692 693 if (!has_privs('link.edit')) { 694 if ($selected && has_privs('link.edit.own')) { 695 $selected = safe_column("id", 'txp_link', "id IN (".join(',', $selected).") AND author = '".doSlash($txp_user)."'"); 696 } else { 697 $selected = array(); 698 } 699 } 700 701 if ($selected && $key) { 702 foreach ($selected as $id) { 703 if (safe_update('txp_link', "$key = '".doSlash($val)."'", "id = '$id'")) { 704 $changed[] = $id; 705 } 706 } 707 } 708 709 if ($changed) { 710 update_lastmod('link_updated', $changed); 711 712 link_list(gTxt( 713 ($method == 'delete' ? 'links_deleted' : 'link_updated'), 714 array(($method == 'delete' ? '{list}' : '{name}') => join(', ', $changed)) 715 )); 716 717 return; 718 } 719 720 link_list(); 721 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
title