Textpattern | PHP Cross Reference | Content Management Systems |
Description: Users 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 * Users panel. 26 * 27 * @package Admin\Admin 28 */ 29 30 use Textpattern\Search\Filter; 31 32 if (!defined('txpinterface')) { 33 die('txpinterface is undefined.'); 34 } 35 36 $levels = get_groups(); 37 38 if ($event == 'admin') { 39 require_privs('admin'); 40 41 $available_steps = array( 42 'admin_multi_edit' => true, 43 'admin_change_pageby' => true, 44 'author_list' => false, 45 'author_edit' => false, 46 'author_save' => true, 47 'author_save_new' => true, 48 'change_pass' => true, 49 'new_pass_form' => false, 50 ); 51 52 if ($step && bouncer($step, $available_steps)) { 53 $step(); 54 } else { 55 author_list(); 56 } 57 } 58 59 /** 60 * Updates a user. 61 */ 62 63 function author_save() 64 { 65 global $txp_user; 66 67 require_privs('admin.edit.own'); 68 69 extract(psa(array( 70 'privs', 71 'name', 72 'RealName', 73 'email', 74 'language', 75 ))); 76 77 $privs = assert_int($privs); 78 79 if (!is_valid_email($email)) { 80 $fullEdit = has_privs('admin.list') ? false : true; 81 author_edit(array(gTxt('email_required'), E_ERROR), $fullEdit); 82 83 return; 84 } 85 86 $rs = update_user($name, $email, $RealName); 87 88 if ($rs && $language) { 89 safe_upsert( 90 'txp_prefs', 91 "val = '".doSlash($language)."', 92 event = 'admin', 93 html = 'text_input', 94 type = ".PREF_HIDDEN.", 95 position = 0", 96 array( 97 'name' => 'language_ui', 98 'user_name' => doSlash((string) $name) 99 ) 100 ); 101 } 102 103 if (has_privs('admin.edit') && $rs && ($txp_user === $name || change_user_group($name, $privs))) { 104 author_list(gTxt('author_updated', array('{name}' => $RealName))); 105 106 return; 107 } elseif ($rs && has_privs('admin.edit.own')) { 108 $msg = gTxt('author_updated', array('{name}' => $RealName)); 109 } else { 110 $msg = array(gTxt('author_save_failed'), E_ERROR); 111 } 112 113 if (has_privs('admin.edit')) { 114 author_edit($msg); 115 } elseif (has_privs('admin.edit.own')) { 116 author_list($msg); 117 } 118 } 119 120 /** 121 * Changes current user's password. 122 */ 123 124 function change_pass() 125 { 126 global $txp_user; 127 128 extract(psa(array('current_pass', 'new_pass'))); 129 130 if (empty($new_pass)) { 131 new_pass_form(array(gTxt('password_required'), E_ERROR)); 132 133 return; 134 } 135 136 if (txp_validate($txp_user, $current_pass)) { 137 $rs = change_user_password($txp_user, $new_pass); 138 139 if ($rs) { 140 $message = gTxt('password_changed'); 141 author_list($message); 142 } 143 } else { 144 new_pass_form(array(gTxt('password_invalid'), E_ERROR)); 145 } 146 } 147 148 /** 149 * Creates a new user. 150 */ 151 152 function author_save_new() 153 { 154 require_privs('admin.edit'); 155 156 extract(psa(array( 157 'privs', 158 'name', 159 'email', 160 'RealName', 161 'language', 162 ))); 163 164 $privs = assert_int($privs); 165 166 if (is_valid_username($name) && is_valid_email($email)) { 167 if (user_exists($name)) { 168 author_edit(array(gTxt('author_already_exists', array('{name}' => $name)), E_ERROR)); 169 170 return; 171 } 172 173 $password = Txp::get('\Textpattern\Password\Random')->generate(PASSWORD_LENGTH); 174 175 $rs = create_user($name, $email, $password, $RealName, $privs); 176 177 if ($rs) { 178 if ($language) { 179 safe_upsert( 180 'txp_prefs', 181 "val = '".doSlash($language)."', 182 event = 'admin', 183 html = 'text_input', 184 type = ".PREF_HIDDEN.", 185 position = 0", 186 array( 187 'name' => 'language_ui', 188 'user_name' => doSlash((string) $name) 189 ) 190 ); 191 } 192 193 $message = send_account_activation($name); 194 195 author_list($message); 196 197 return; 198 } 199 } 200 201 author_edit(array(gTxt('error_adding_new_author'), E_ERROR)); 202 } 203 204 /** 205 * Lists user groups as a <select> input. 206 * 207 * @param int $priv Selected option 208 * @return string HTML 209 */ 210 211 function privs($priv = '') 212 { 213 global $levels; 214 215 return selectInput('privs', $levels, $priv, '', '', 'privileges'); 216 } 217 218 /** 219 * Translates a numeric ID to a human-readable user group. 220 * 221 * @param int $priv The group 222 * @return string 223 */ 224 225 function get_priv_level($priv) 226 { 227 global $levels; 228 229 return $levels[$priv]; 230 } 231 232 /** 233 * Password changing form. 234 * 235 * @param string|array $message The activity message 236 */ 237 238 function new_pass_form($message = '') 239 { 240 pagetop(gTxt('tab_site_admin'), $message); 241 242 echo form( 243 hed(gTxt('change_password'), 2). 244 inputLabel( 245 'current_pass', 246 fInput('password', 247 array( 248 'name' => 'current_pass', 249 'autocomplete' => 'current-password', 250 ), '', 'txp-maskable', '', '', INPUT_REGULAR, '', 'current_pass', false, true), 251 'current_password', '', array('class' => 'txp-form-field edit-admin-current-password') 252 ). 253 inputLabel( 254 'new_pass', 255 fInput('password', 256 array( 257 'name' => 'new_pass', 258 'autocomplete' => 'new-password', 259 ), '', 'txp-maskable', '', '', INPUT_REGULAR, '', 'new_pass', false, true). 260 n.tag( 261 checkbox('unmask', 1, false, 0, 'show_password'). 262 n.tag(gTxt('show_password'), 'label', array('for' => 'show_password')), 263 'div', array('class' => 'edit-admin-show-password')), 264 'new_password', '', array('class' => 'txp-form-field edit-admin-new-password') 265 ). 266 graf( 267 sLink('admin', '', gTxt('cancel'), 'txp-button'). 268 fInput('submit', 'change_pass', gTxt('submit'), 'publish'), 269 array('class' => 'txp-edit-actions') 270 ). 271 eInput('admin'). 272 sInput('change_pass'), 273 '', '', 'post', 'txp-edit', '', 'change_password'); 274 } 275 276 /** 277 * The main panel listing all authors. 278 * 279 * @param string|array $message The activity message 280 */ 281 282 function author_list($message = '') 283 { 284 global $event, $txp_user, $levels; 285 286 $buttons = author_edit_buttons(); 287 288 // User list. 289 if (has_privs('admin.list')) { 290 pagetop(gTxt('tab_site_admin'), $message); 291 292 if (is_disabled('mail')) { 293 echo graf( 294 span(null, array('class' => 'ui-icon ui-icon-alert')).' '. 295 gTxt('warn_mail_unavailable'), 296 array('class' => 'alert-block warning') 297 ); 298 } 299 extract(gpsa(array( 300 'page', 301 'sort', 302 'dir', 303 'crit', 304 'search_method', 305 ))); 306 307 if ($sort === '') { 308 $sort = get_pref('admin_sort_column', 'name'); 309 } else { 310 if (!in_array($sort, array('name', 'RealName', 'email', 'privs', 'last_login'))) { 311 $sort = 'name'; 312 } 313 314 set_pref('admin_sort_column', $sort, 'admin', PREF_HIDDEN, '', 0, PREF_PRIVATE); 315 } 316 317 if ($dir === '') { 318 $dir = get_pref('admin_sort_dir', 'asc'); 319 } else { 320 $dir = ($dir == 'desc') ? "desc" : "asc"; 321 set_pref('admin_sort_dir', $dir, 'admin', PREF_HIDDEN, '', 0, PREF_PRIVATE); 322 } 323 324 $sort_sql = $sort.' '.$dir; 325 326 $switch_dir = ($dir == 'desc') ? 'asc' : 'desc'; 327 328 $search = new Filter($event, 329 array( 330 'login' => array( 331 'column' => 'txp_users.name', 332 'label' => gTxt('login_name'), 333 ), 334 'RealName' => array( 335 'column' => 'txp_users.RealName', 336 'label' => gTxt('real_name'), 337 ), 338 'email' => array( 339 'column' => 'txp_users.email', 340 'label' => gTxt('email'), 341 ), 342 'privs' => array( 343 'column' => array('txp_users.privs'), 344 'label' => gTxt('privileges'), 345 'type' => 'boolean', 346 ), 347 ) 348 ); 349 350 $search->setAliases('privs', $levels); 351 352 list($criteria, $crit, $search_method) = $search->getFilter(array('login' => array('can_list' => true))); 353 354 $search_render_options = array('placeholder' => 'search_users'); 355 356 $total = getCount('txp_users', $criteria); 357 358 $searchBlock = 359 n.tag( 360 $search->renderForm('author_list', $search_render_options), 361 'div', array( 362 'class' => 'txp-layout-4col-3span', 363 'id' => 'users_control', 364 ) 365 ); 366 367 $createBlock = n.tag(implode(n, $buttons), 'div', array('class' => 'txp-control-panel')); 368 369 $contentBlock = ''; 370 371 $paginator = new \Textpattern\Admin\Paginator($event, 'author'); 372 $limit = $paginator->getLimit(); 373 374 list($page, $offset, $numPages) = pager($total, $limit, $page); 375 376 if ($total < 1) { 377 if ($crit !== '') { 378 $contentBlock .= 379 graf( 380 span(null, array('class' => 'ui-icon ui-icon-info')).' '. 381 gTxt('no_results_found'), 382 array('class' => 'alert-block information') 383 ); 384 } 385 } else { 386 $use_multi_edit = (has_privs('admin.edit') && ($total > 1 or safe_count('txp_users', "1 = 1") > 1)); 387 388 $rs = safe_rows_start( 389 "*, UNIX_TIMESTAMP(last_access) AS last_login", 390 'txp_users', 391 "$criteria ORDER BY $sort_sql LIMIT $offset, $limit" 392 ); 393 394 if ($rs) { 395 $contentBlock .= 396 n.tag_start('form', array( 397 'class' => 'multi_edit_form', 398 'id' => 'users_form', 399 'name' => 'longform', 400 'method' => 'post', 401 'action' => 'index.php', 402 )). 403 n.tag_start('div', array( 404 'class' => 'txp-listtables', 405 'tabindex' => 0, 406 'aria-label' => gTxt('list'), 407 )). 408 n.tag_start('table', array('class' => 'txp-list')). 409 n.tag_start('thead'). 410 tr( 411 ( 412 ($use_multi_edit) 413 ? hCell( 414 fInput('checkbox', 'select_all', 0, '', '', '', '', '', 'select_all'), 415 '', ' class="txp-list-col-multi-edit" scope="col" title="'.gTxt('toggle_all_selected').'"' 416 ) 417 : hCell('', '', ' class="txp-list-col-multi-edit" scope="col"') 418 ). 419 column_head( 420 'login_name', 'name', 'admin', true, $switch_dir, '', '', 421 (('name' == $sort) ? "$dir " : '').'txp-list-col-login-name name' 422 ). 423 column_head( 424 'real_name', 'RealName', 'admin', true, $switch_dir, '', '', 425 (('RealName' == $sort) ? "$dir " : '').'txp-list-col-real-name name' 426 ). 427 column_head( 428 'email', 'email', 'admin', true, $switch_dir, '', '', 429 (('email' == $sort) ? "$dir " : '').'txp-list-col-email' 430 ). 431 column_head( 432 'privileges', 'privs', 'admin', true, $switch_dir, '', '', 433 (('privs' == $sort) ? "$dir " : '').'txp-list-col-privs' 434 ). 435 column_head( 436 'last_login', 'last_login', 'admin', true, $switch_dir, '', '', 437 (('last_login' == $sort) ? "$dir " : '').'txp-list-col-last-login date' 438 ) 439 ). 440 n.tag_end('thead'). 441 n.tag_start('tbody'); 442 443 while ($a = nextRow($rs)) { 444 extract(doSpecial($a)); 445 446 $contentBlock .= tr( 447 td( 448 ((has_privs('admin.edit') && $txp_user != $a['name']) ? fInput('checkbox', 'selected[]', $a['name'], 'checkbox') : ''), '', 'txp-list-col-multi-edit' 449 ). 450 hCell( 451 ((has_privs('admin.edit') || (has_privs('admin.edit.own') && $txp_user === $a['name'])) ? eLink('admin', 'author_edit', 'user_id', $user_id, $name) : $name), '', ' class="txp-list-col-login-name name" scope="row"' 452 ). 453 td( 454 $RealName, '', 'txp-list-col-real-name name' 455 ). 456 td( 457 href($email, 'mailto:'.$email), '', 'txp-list-col-email' 458 ). 459 td( 460 get_priv_level($privs), '', 'txp-list-col-privs' 461 ). 462 td( 463 ($last_login ? safe_strftime('%b %Y', $last_login) : ''), '', 'txp-list-col-last-login date' 464 ) 465 ); 466 } 467 468 $contentBlock .= 469 n.tag_end('tbody'). 470 n.tag_end('table'). 471 n.tag_end('div'). // End of .txp-listtables. 472 ( 473 ($use_multi_edit) 474 ? author_multiedit_form($page, $sort, $dir, $crit, $search_method) 475 : '' 476 ). 477 tInput(). 478 n.tag_end('form'); 479 } 480 } 481 482 $pageBlock = $paginator->render(). 483 nav_form('admin', $page, $numPages, $sort, $dir, $crit, $search_method, $total, $limit); 484 485 $table = new \Textpattern\Admin\Table('users'); 486 echo $table->render(compact('total', 'crit') + array('heading' => 'tab_site_admin'), $searchBlock, $createBlock, $contentBlock, $pageBlock); 487 488 } elseif (has_privs('admin.edit.own')) { 489 echo author_edit($message, true); 490 } else { 491 require_privs('admin.edit'); 492 } 493 } 494 495 /** 496 * Create additional UI buttons. 497 */ 498 function author_edit_buttons() 499 { 500 $buttons = array(); 501 502 // New author button. 503 if (has_privs('admin.edit')) { 504 $buttons[] = sLink('admin', 'author_edit', gTxt('create_author'), 'txp-button'); 505 } 506 507 // Change password button. 508 $buttons[] = sLink('admin', 'new_pass_form', gTxt('change_password'), 'txp-button'); 509 510 return $buttons; 511 } 512 513 /** 514 * Renders the user edit panel. 515 * 516 * @param string|array $message The activity message 517 * @param bool $fullEdit Whether the user has full edit permissions or not 518 */ 519 520 function author_edit($message = '', $fullEdit = false) 521 { 522 global $step, $txp_user; 523 524 require_privs('admin.edit.own'); 525 526 pagetop(gTxt('tab_site_admin'), $message); 527 528 $vars = array('user_id', 'name', 'RealName', 'email', 'privs'); 529 $rs = array(); 530 $out = array(); 531 532 extract(gpsa($vars)); 533 534 if (has_privs('admin.edit')) { 535 if ($user_id) { 536 $user_id = assert_int($user_id); 537 $rs = safe_row("*", 'txp_users', "user_id = '$user_id'"); 538 539 extract($rs); 540 $is_edit = true; 541 } else { 542 $is_edit = false; 543 } 544 } else { 545 $rs = safe_row("*", 'txp_users', "name = '".doSlash($txp_user)."'"); 546 extract($rs); 547 $is_edit = true; 548 } 549 550 if (!$is_edit) { 551 $out[] = hed(gTxt('create_author'), 2); 552 } else { 553 $out[] = hed(gTxt('edit_author'), 2); 554 } 555 556 if ($is_edit) { 557 $out[] = inputLabel( 558 'login_name', 559 strong(txpspecialchars($name)), 560 '', '', array('class' => 'txp-form-field edit-admin-login-name') 561 ); 562 } elseif (has_privs('admin.edit')) { 563 $out[] = inputLabel( 564 'login_name', 565 fInput('text', 'name', $name, '', '', '', INPUT_REGULAR, '', 'login_name', false, true), 566 'login_name', 'create_author', array('class' => 'txp-form-field edit-admin-login-name') 567 ); 568 } 569 570 // Get author's current admin language, if defined, 571 $txpLang = Txp::get('\Textpattern\L10n\Lang'); 572 $langList = $txpLang->languageList(); 573 $authorLang = safe_field('val', 'txp_prefs', "name='language_ui' AND user_name = '".doSlash($name)."'"); 574 $authorLang = in_array($authorLang, $txpLang->installed()) ? $authorLang : ($is_edit? null : TEXTPATTERN_DEFAULT_LANG); 575 576 if (count($langList) > 1) { 577 $langField = inputLabel( 578 'language', 579 selectInput('language', $langList, $authorLang, true, false, 'language'), 580 'active_language_ui', '', array('class' => 'txp-form-field edit-admin-language') 581 ); 582 } else { 583 $langField = hInput('language', $authorLang); 584 } 585 586 $out[] = inputLabel( 587 'real_name', 588 fInput('text', 'RealName', $RealName, '', '', '', INPUT_REGULAR, '', 'real_name'), 589 'real_name', '', array('class' => 'txp-form-field edit-admin-name') 590 ). 591 inputLabel( 592 'login_email', 593 fInput('email', 'email', $email, '', '', '', INPUT_REGULAR, '', 'login_email', false, true), 594 'email', '', array('class' => 'txp-form-field edit-admin-email') 595 ); 596 597 if (has_privs('admin.edit') && $txp_user != $name) { 598 $out[] = inputLabel( 599 'privileges', 600 privs($privs), 601 'privileges', 'about_privileges', array('class' => 'txp-form-field edit-admin-privileges') 602 ); 603 } else { 604 $out[] = inputLabel( 605 'privileges', 606 strong(get_priv_level($privs)), 607 '', '', array('class' => 'txp-form-field edit-admin-privileges') 608 ). 609 hInput('privs', $privs); 610 } 611 612 $out[] = $langField; 613 $out[] = pluggable_ui('author_ui', 'extend_detail_form', '', $rs). 614 graf( 615 ($fullEdit ? '' : sLink('admin', '', gTxt('cancel'), 'txp-button')). 616 fInput('submit', '', gTxt('save'), 'publish'), 617 array('class' => 'txp-edit-actions') 618 ). 619 eInput('admin'); 620 621 if ($is_edit) { 622 $out[] = hInput('user_id', $user_id). 623 hInput('name', $name). 624 sInput('author_save'); 625 } else { 626 $out[] = sInput('author_save_new'); 627 } 628 629 echo n.'<div class="txp-layout">'. 630 n.tag( 631 hed(gTxt('tab_site_account'), 1, array('class' => 'txp-heading')), 632 'div', array('class' => 'txp-layout-1col') 633 ). 634 n.tag_start('div', array( 635 'class' => 'txp-layout-1col', 636 'id' => 'users_container', 637 )). 638 ($fullEdit 639 ? n.tag(implode(n, author_edit_buttons()), 'div', array('class' => 'txp-control-panel')) 640 : '' 641 ); 642 643 if (!$is_edit) { 644 echo form(join('', $out), '', '', 'post', 'txp-edit', '', 'user_edit', '', false); 645 } else { 646 echo form(join('', $out), '', '', 'post', 'txp-edit', '', 'user_edit'); 647 } 648 649 echo n.tag_end('div'). // End of .txp-layout-1col. 650 n.'</div>'; // End of .txp-layout. 651 } 652 653 /** 654 * Updates pageby value. 655 */ 656 657 function admin_change_pageby() 658 { 659 global $event; 660 661 Txp::get('\Textpattern\Admin\Paginator', $event, 'author')->change(); 662 author_list(); 663 } 664 665 /** 666 * Renders multi-edit form. 667 * 668 * @param int $page The page 669 * @param string $sort The sorting value 670 * @param string $dir The sorting direction 671 * @param string $crit The search string 672 * @param string $search_method The search method 673 * @return string HTML 674 */ 675 676 function author_multiedit_form($page, $sort, $dir, $crit, $search_method) 677 { 678 $privileges = privs(); 679 $users = safe_column("name", 'txp_users', "1 = 1"); 680 681 $methods = array( 682 'changeprivilege' => array( 683 'label' => gTxt('changeprivilege'), 684 'html' => $privileges, 685 ), 686 'resetpassword' => gTxt('resetpassword'), 687 'resendactivation' => gTxt('resend_activation'), 688 ); 689 690 if (count($users) > 1) { 691 $methods['delete'] = array( 692 'label' => gTxt('delete'), 693 'html' => tag(gTxt('assign_assets_to'), 'label', array('for' => 'assign_assets')). 694 selectInput('assign_assets', $users, '', true, '', 'assign_assets'), 695 ); 696 } 697 698 return multi_edit($methods, 'admin', 'admin_multi_edit', $page, $sort, $dir, $crit, $search_method); 699 } 700 701 /** 702 * Processes multi-edit actions. 703 * 704 * Accessing requires 'admin.edit' privileges. 705 */ 706 707 function admin_multi_edit() 708 { 709 global $txp_user; 710 711 require_privs('admin.edit'); 712 713 $selected = ps('selected'); 714 $method = ps('edit_method'); 715 $changed = array(); 716 $msg = ''; 717 718 if (!$selected || !is_array($selected)) { 719 return author_list(); 720 } 721 722 $clause = ''; 723 724 if ($method === 'resetpassword') { 725 $clause = " AND last_access IS NOT NULL"; 726 } elseif ($method === 'resendactivation') { 727 $clause = " AND last_access IS NULL"; 728 } 729 730 $names = safe_column( 731 "name", 732 'txp_users', 733 "name IN (".join(',', quote_list($selected)).") AND name != '".doSlash($txp_user)."'".$clause 734 ); 735 736 if (!$names) { 737 return author_list(); 738 } 739 740 switch ($method) { 741 case 'delete': 742 $assign_assets = ps('assign_assets'); 743 744 if (!$assign_assets) { 745 $msg = array('must_reassign_assets', E_ERROR); 746 } elseif (in_array($assign_assets, $names)) { 747 $msg = array('cannot_assign_assets_to_deletee', E_ERROR); 748 } elseif (remove_user($names, $assign_assets)) { 749 $changed = $names; 750 callback_event('authors_deleted', '', 0, $changed); 751 $msg = 'author_deleted'; 752 } 753 754 break; 755 756 case 'changeprivilege': 757 if (change_user_group($names, ps('privs'))) { 758 $changed = $names; 759 $msg = 'author_updated'; 760 } 761 762 break; 763 764 case 'resetpassword': 765 foreach ($names as $name) { 766 send_reset_confirmation_request($name); 767 $changed[] = $name; 768 } 769 770 $msg = 'password_reset_confirmation_request_sent'; 771 break; 772 773 case 'resendactivation': 774 foreach ($names as $name) { 775 send_account_activation($name); 776 $changed[] = $name; 777 } 778 779 $msg = 'resend_activation_request_sent'; 780 break; 781 } 782 783 if (is_array($msg)) { 784 list($msg, $err) = $msg; 785 } else { 786 $err = 0; 787 } 788 789 if ($changed) { 790 return author_list(array(gTxt($msg, array('{name}' => txpspecialchars(join(', ', $changed)))), $err)); 791 } 792 793 author_list(array(gTxt($msg), $err)); 794 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
title