Textpattern | PHP Cross Reference | Content Management Systems |
Description: Category 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 * Category panel. 26 * 27 * @package Admin\Category 28 */ 29 30 if (!defined('txpinterface')) { 31 die('txpinterface is undefined.'); 32 } 33 34 if ($event == 'category') { 35 require_privs('category'); 36 37 $available_steps = array( 38 'cat_category_list' => false, 39 'cat_category_multiedit' => true, 40 'cat_article_create' => true, 41 'cat_image_create' => true, 42 'cat_file_create' => true, 43 'cat_link_create' => true, 44 'cat_article_save' => true, 45 'cat_image_save' => true, 46 'cat_file_save' => true, 47 'cat_link_save' => true, 48 'cat_article_edit' => false, 49 'cat_image_edit' => false, 50 'cat_file_edit' => false, 51 'cat_link_edit' => false, 52 ); 53 54 if ($step && bouncer($step, $available_steps)) { 55 $step(); 56 } else { 57 cat_category_list(); 58 } 59 } 60 61 /** 62 * Outputs the main panel listing all categories. 63 * 64 * @param string|array $message The activity message 65 */ 66 67 function cat_category_list($message = "") 68 { 69 pagetop(gTxt('categories'), $message); 70 $out = array(n.'<div class="txp-layout">'. 71 n.tag( 72 hed(gTxt('tab_organise'), 1, array('class' => 'txp-heading')), 73 'div', array('class' => 'txp-layout-1col') 74 ), 75 n.tag(cat_article_list(), 'section', array( 76 'class' => 'txp-layout-4col', 77 'id' => 'categories_article', 78 ) 79 ), 80 n.tag(cat_image_list(), 'section', array( 81 'class' => 'txp-layout-4col', 82 'id' => 'categories_image', 83 ) 84 ), 85 n.tag(cat_file_list(), 'section', array( 86 'class' => 'txp-layout-4col', 87 'id' => 'categories_file', 88 ) 89 ), 90 n.tag(cat_link_list(), 'section', array( 91 'class' => 'txp-layout-4col', 92 'id' => 'categories_link', 93 ) 94 ), 95 n.'</div>', // End of .txp-layout. 96 script_js(<<<EOS 97 $(document).ready(function () 98 { 99 $('.category-tree').txpMultiEditForm({ 100 'row' : 'p', 101 'highlighted' : 'p' 102 }); 103 }); 104 EOS 105 , false), 106 ); 107 echo join(n, $out); 108 } 109 110 /** 111 * Renders a list of article categories. 112 */ 113 114 function cat_article_list() 115 { 116 return cat_event_category_list('article'); 117 } 118 119 /** 120 * Processes a saved editor form and creates an article category. 121 */ 122 123 function cat_article_create() 124 { 125 return cat_event_category_create('article'); 126 } 127 128 /** 129 * Renders an editor form for article categories. 130 */ 131 132 function cat_article_edit() 133 { 134 return cat_event_category_edit('article'); 135 } 136 137 /** 138 * Saves an article category. 139 */ 140 141 function cat_article_save() 142 { 143 return cat_event_category_save('article', 'textpattern'); 144 } 145 146 /** 147 * Renders a list of parent category options. 148 * 149 * @return string HTML <select> input 150 */ 151 152 function cat_parent_pop($name, $type, $id) 153 { 154 if ($id) { 155 $id = assert_int($id); 156 list($lft, $rgt) = array_values(safe_row("lft, rgt", 'txp_category', "id = '$id'")); 157 158 $rs = getTree('root', $type, "lft NOT BETWEEN $lft AND $rgt"); 159 } else { 160 $rs = getTree('root', $type); 161 } 162 163 if ($rs) { 164 return array(treeSelectInput('parent', $rs, $name, 'category_parent'), true); 165 } 166 167 return array(gTxt('no_other_categories_exist'), false); 168 } 169 170 /** 171 * Renders a list of link categories. 172 */ 173 174 function cat_link_list() 175 { 176 return cat_event_category_list('link'); 177 } 178 179 /** 180 * Processes a saved editor form and creates a link category. 181 */ 182 183 function cat_link_create() 184 { 185 return cat_event_category_create('link'); 186 } 187 188 /** 189 * Renders an editor form for link categories. 190 */ 191 192 function cat_link_edit() 193 { 194 return cat_event_category_edit('link'); 195 } 196 197 /** 198 * Saves a link category. 199 */ 200 201 function cat_link_save() 202 { 203 return cat_event_category_save('link', 'txp_link'); 204 } 205 206 /** 207 * Renders a list of image categories. 208 */ 209 210 function cat_image_list() 211 { 212 return cat_event_category_list('image'); 213 } 214 215 /** 216 * Processes a saved editor form and creates an image category. 217 */ 218 219 function cat_image_create() 220 { 221 return cat_event_category_create('image'); 222 } 223 224 /** 225 * Renders an editor form for image categories. 226 */ 227 228 function cat_image_edit() 229 { 230 return cat_event_category_edit('image'); 231 } 232 233 /** 234 * Saves an image category. 235 */ 236 237 function cat_image_save() 238 { 239 return cat_event_category_save('image', 'txp_image'); 240 } 241 242 /** 243 * Renders a multi-edit form. 244 * 245 * @param string $area Type of category 246 * @param array $array Additional HTML added to the form 247 * @return string HTML 248 */ 249 250 function cat_article_multiedit_form($area, $array) 251 { 252 $rs = getTree('root', $area); 253 $categories = $rs ? treeSelectInput('new_parent', $rs, '') : ''; 254 255 $methods = array( 256 'changeparent' => array( 257 'label' => gTxt('changeparent'), 258 'html' => $categories, 259 ), 260 'deleteforce' => gTxt('deleteforce'), 261 'delete' => gTxt('delete'), 262 ); 263 264 if ($array) { 265 return 266 form( 267 join('', $array). 268 hInput('type', $area). 269 multi_edit($methods, 'category', 'cat_category_multiedit', '', '', '', '', '', $area), '', '', 'post', 'category-tree', '', 'category_'.$area.'_form' 270 ); 271 } 272 273 return; 274 } 275 276 /** 277 * Processes multi-edit actions. 278 */ 279 280 function cat_category_multiedit() 281 { 282 $type = ps('type'); 283 $method = ps('edit_method'); 284 $things = ps('selected'); 285 286 if (is_array($things) and $things and in_array($type, array('article', 'image', 'link', 'file'))) { 287 // Fetch selected items and remove bogus (false) entries to prevent SQL syntax errors. 288 $things = array_map('assert_int', $things); 289 $things = array_filter($things); 290 291 if ($method == 'delete' || $method == 'deleteforce') { 292 if ($type === 'article') { 293 $used = "name NOT IN (SELECT category1 FROM ".safe_pfx('textpattern').") 294 AND name NOT IN (SELECT category2 FROM ".safe_pfx('textpattern').")"; 295 } else { 296 $used = "name NOT IN (SELECT category FROM ".safe_pfx('txp_'.$type).")"; 297 } 298 299 $rs = safe_rows("id, name", 'txp_category', "id IN (".join(',', $things).") AND type = '".$type."'".(($method == 'deleteforce') ? '' : " AND rgt - lft = 1 AND ".$used)); 300 301 if ($rs) { 302 foreach ($rs as $cat) { 303 $catid[] = $cat['id']; 304 $names[] = doSlash($cat['name']); 305 } 306 307 if (safe_delete('txp_category', "id IN (".join(',', $catid).")")) { 308 if ($method == 'deleteforce') { 309 // Clear the deleted category names from assets. 310 $affected = join("','", $names); 311 312 if ($type === 'article') { 313 safe_update('textpattern', "category1 = ''", "category1 IN ('$affected')"); 314 safe_update('textpattern', "category2 = ''", "category2 IN ('$affected')"); 315 } else { 316 safe_update('txp_'.$type, "category = ''", "category IN ('$affected')"); 317 } 318 319 // Promote subcategories of deleted categories to root. 320 safe_update('txp_category', "parent = 'root'", "parent IN ('$affected')"); 321 } 322 323 rebuild_tree_full($type); 324 callback_event('categories_deleted', $type, 0, $catid); 325 326 $message = gTxt($type.'_categories_deleted', array('{list}' => join(', ', $catid))); 327 328 return cat_category_list($message); 329 } 330 } 331 } elseif ($method == 'changeparent') { 332 $new_parent = ps('new_parent') or $new_parent = 'root'; 333 334 $exists = safe_row("name, title, lft, rgt", 'txp_category', "name = '".doSlash($new_parent)."' AND type = '$type'"); 335 $rs = $exists ? safe_rows("id, name, title, lft, rgt", 'txp_category', "id IN (".join(',', $things).") AND type = '".$type."'") : false; 336 337 if ($rs) { 338 $parent = $exists['name']; 339 $title = $exists['title']; 340 $to_change = $affected = array(); 341 342 foreach ($rs as $cat) { 343 // Cannot assign parent to a child. 344 if ($cat['lft']>$exists['lft'] || $cat['rgt']<$exists['rgt']) { 345 $to_change[] = doSlash($cat['name']); 346 $affected[] = $cat['title']; 347 } 348 } 349 350 $ret = safe_update('txp_category', "parent = '".doSlash($parent)."'", "name IN ('".join("','", $to_change)."') AND type = '".$type."'"); 351 352 if ($ret) { 353 rebuild_tree_full($type); 354 355 $message = !empty($affected) 356 ? gTxt('categories_set_parent', array( 357 '{type}' => gTxt($type), 358 '{parent}' => $title, 359 '{list}' => join(', ', $affected), 360 )) 361 : array(gTxt('category_save_failed'), E_ERROR); 362 363 return cat_category_list($message); 364 } 365 } 366 } 367 } 368 369 return cat_category_list(); 370 } 371 372 /** 373 * Renders a list of categories. 374 * 375 * @param string $event Type of category 376 * @return string HTML 377 */ 378 379 function cat_event_category_list($event) 380 { 381 $rs = getTree('root', $event); 382 383 $parent = ps('parent_cat'); 384 385 $heading = 'tab_'.($event == 'article' ? 'list' : $event); 386 $for = $rs ? ' for="'.$event.'_category_parent"' : ''; 387 388 $out = hed(gTxt($heading).popHelp($event.'_category'), 2). 389 form( 390 graf( 391 tag(gTxt('create_category'), 'label', array('for' => $event.'_category_new')).br. 392 fInput('text', 'title', '', '', '', '', INPUT_REGULAR, '', $event.'_category_new', false, true) 393 ). 394 (($rs) 395 ? graf('<label'.$for.'>'.gTxt('parent').'</label>'.br. 396 treeSelectInput('parent_cat', $rs, $parent, $event.'_category_parent'), array('class' => 'parent')) 397 : '' 398 ). 399 graf( 400 fInput('submit', '', gTxt('create')). 401 eInput('category'). 402 sInput('cat_'.$event.'_create') 403 ), '', '', 'post', $event); 404 405 if ($rs) { 406 $total_count = array(); 407 408 if ($event == 'article') { 409 // Count distinct articles for both categories, avoid duplicates. 410 $rs2 = getRows( 411 "SELECT category, COUNT(*) AS num FROM ( 412 SELECT ID, Category1 AS category FROM ".safe_pfx('textpattern')." 413 UNION 414 SELECT ID, Category2 AS category FROM ".safe_pfx('textpattern')." 415 ) AS t WHERE category != '' GROUP BY category"); 416 417 if ($rs2 !== false) { 418 foreach ($rs2 as $a) { 419 $total_count[$a['category']] = $a['num']; 420 } 421 } 422 } else { 423 switch ($event) { 424 case 'link': 425 $rs2 = safe_rows_start("category, COUNT(*) AS num", 'txp_link', "1 = 1 GROUP BY category"); 426 break; 427 case 'image': 428 $rs2 = safe_rows_start("category, COUNT(*) AS num", 'txp_image', "1 = 1 GROUP BY category"); 429 break; 430 case 'file': 431 $rs2 = safe_rows_start("category, COUNT(*) AS num", 'txp_file', "1 = 1 GROUP BY category"); 432 break; 433 } 434 435 while ($a = nextRow($rs2)) { 436 $name = $a['category']; 437 $num = $a['num']; 438 439 $total_count[$name] = $num; 440 } 441 } 442 443 $items = array(); 444 445 foreach ($rs as $a) { 446 extract($a); 447 448 // Format count. 449 switch ($event) { 450 case 'article': 451 $url = 'index.php?event=list'.a.'search_method=categories'.a.'crit='.$name; 452 break; 453 case 'link': 454 $url = 'index.php?event=link'.a.'search_method=category'.a.'crit='.$name; 455 break; 456 case 'image': 457 $url = 'index.php?event=image'.a.'search_method=category'.a.'crit='.$name; 458 break; 459 case 'file': 460 $url = 'index.php?event=file'.a.'search_method=category'.a.'crit='.$name; 461 break; 462 } 463 464 $count = isset($total_count[$name]) ? href('('.$total_count[$name].')', $url) : '(0)'; 465 466 if (empty($title)) { 467 $edit_link = '<em>'.eLink('category', 'cat_'.$event.'_edit', 'id', $id, gTxt('untitled')).'</em>'; 468 } else { 469 $edit_link = eLink('category', 'cat_'.$event.'_edit', 'id', $id, $title); 470 } 471 472 $items[] = graf( 473 checkbox('selected[]', $id, 0).sp.str_repeat(sp.sp, $level * 2).$edit_link.sp.$count, ' class="level-'.$level.'"'); 474 } 475 476 if ($items) { 477 $out .= cat_article_multiedit_form($event, $items); 478 } 479 } else { 480 $out .= graf( 481 span(null, array('class' => 'ui-icon ui-icon-info')).' '. 482 gTxt('no_categories_exist'), 483 array('class' => 'alert-block information') 484 ); 485 } 486 487 return $out; 488 } 489 490 /** 491 * Creates a new category. 492 * 493 * @param string $event The type of category 494 */ 495 496 function cat_event_category_create($event) 497 { 498 $title = ps('title'); 499 500 $name = strtolower(sanitizeForUrl($title)); 501 502 if (!$name) { 503 $message = array(gTxt($event.'_category_invalid', array('{name}' => $title)), E_ERROR); 504 505 return cat_category_list($message); 506 } 507 508 $exists = safe_field("name", 'txp_category', "name = '".doSlash($name)."' AND type = '".doSlash($event)."'"); 509 510 if ($exists !== false) { 511 $message = array(gTxt($event.'_category_already_exists', array('{name}' => $name)), E_ERROR); 512 513 return cat_category_list($message); 514 } 515 516 $parent = strtolower(sanitizeForUrl(ps('parent_cat'))); 517 $parent_exists = safe_field("name", 'txp_category', "name = '".doSlash($parent)."' AND type = '".doSlash($event)."'"); 518 $parent = ($parent_exists !== false) ? $parent_exists : 'root'; 519 520 $q = safe_insert('txp_category', "name = '".doSlash($name)."', title = '".doSlash($title)."', type = '".doSlash($event)."', parent = '".$parent."'"); 521 522 if ($q) { 523 rebuild_tree_full($event); 524 525 $message = gTxt($event.'_category_created', array('{name}' => $name)); 526 527 cat_category_list($message); 528 } else { 529 cat_category_list(array(gTxt('category_save_failed'), E_ERROR)); 530 } 531 } 532 533 /** 534 * Renders and outputs a category editor panel. 535 * 536 * @param string $evname Type of category 537 */ 538 539 function cat_event_category_edit($evname, $message = '') 540 { 541 $id = assert_int(gps('id')); 542 $parent = doSlash(gps('parent')); 543 544 $row = safe_row("*", 'txp_category', "id = '$id'"); 545 546 if ($row) { 547 pagetop(gTxt('edit_category'), $message); 548 extract($row); 549 list($parent_widget, $has_parent) = cat_parent_pop($parent, $evname, $id); 550 551 $out = hed(gTxt('edit_category'), 2). 552 inputLabel( 553 'category_name', 554 fInput('text', 'name', $name, '', '', '', INPUT_REGULAR, '', 'category_name', false, true), 555 $evname.'_category_name', '', array('class' => 'txp-form-field edit-category-name') 556 ). 557 inputLabel( 558 'category_parent', 559 $parent_widget, 560 'parent', '', array('class' => 'txp-form-field edit-category-parent') 561 ). 562 inputLabel( 563 'category_title', 564 fInput('text', 'title', $title, '', '', '', INPUT_REGULAR, '', 'category_title'), 565 $evname.'_category_title', '', array('class' => 'txp-form-field edit-category-title') 566 ). 567 inputLabel( 568 'category_description', 569 '<textarea id="category_description" name="description" cols="'.INPUT_LARGE.'" rows="'.TEXTAREA_HEIGHT_SMALL.'">'.$description.'</textarea>', 570 $evname.'_category_description', 'category_description', array('class' => 'txp-form-field txp-form-field-textarea edit-category-description') 571 ). 572 pluggable_ui('category_ui', 'extend_detail_form', '', $row). 573 hInput('id', $id). 574 graf( 575 sLink('category', '', gTxt('cancel'), 'txp-button'). 576 fInput('submit', '', gTxt('save'), 'publish'), 577 array('class' => 'txp-edit-actions') 578 ). 579 eInput('category'). 580 sInput('cat_'.$evname.'_save'). 581 hInput('old_name', $name); 582 583 echo form($out, '', '', 'post', 'txp-edit'); 584 } else { 585 cat_category_list(array(gTxt('category_not_found'), E_ERROR)); 586 } 587 } 588 589 /** 590 * Saves a category from HTTP POST data. 591 * 592 * @param string $event Type of category 593 * @param string $table Affected database table 594 */ 595 596 function cat_event_category_save($event, $table_name) 597 { 598 extract(doSlash(array_map('assert_string', psa(array('id', 'name', 'description', 'old_name', 'parent', 'title'))))); 599 $id = assert_int($id); 600 601 $rawname = $name; 602 $name = sanitizeForUrl($rawname); 603 604 // Make sure the name is valid. 605 if (!$name) { 606 $message = array(gTxt($event.'_category_invalid', array('{name}' => $rawname)), E_ERROR); 607 608 return cat_event_category_edit($event, $message); 609 } 610 611 // Don't allow rename to clobber an existing category. 612 $existing_id = safe_field("id", 'txp_category', "name = '$name' AND type = '$event'"); 613 614 if ($existing_id and $existing_id != $id) { 615 $message = array(gTxt($event.'_category_already_exists', array('{name}' => $name)), E_ERROR); 616 617 return cat_event_category_edit($event, $message); 618 } 619 620 $parent = ($parent) ? $parent : 'root'; 621 622 $message = array(gTxt('category_save_failed'), E_ERROR); 623 624 if (safe_update('txp_category', "name = '$name', parent = '$parent', title = '$title', description = '$description'", "id = '$id'") && 625 safe_update('txp_category', "parent = '$name'", "parent = '$old_name' AND type = '$event'")) { 626 rebuild_tree_full($event); 627 628 if ($event == 'article') { 629 if (safe_update('textpattern', "Category1 = '$name'", "Category1 = '$old_name'") && 630 safe_update('textpattern', "Category2 = '$name'", "Category2 = '$old_name'")) { 631 $message = gTxt($event.'_category_updated', array('{name}' => doStrip($name))); 632 } 633 } else { 634 if (safe_update($table_name, "category = '$name'", "category = '$old_name'")) { 635 $message = gTxt($event.'_category_updated', array('{name}' => doStrip($name))); 636 } 637 } 638 } 639 cat_category_list($message); 640 } 641 642 /** 643 * Renders a list of file categories. 644 */ 645 646 function cat_file_list() 647 { 648 return cat_event_category_list('file'); 649 } 650 651 /** 652 * Processes a saved editor form and creates a file category. 653 */ 654 655 function cat_file_create() 656 { 657 return cat_event_category_create('file'); 658 } 659 660 /** 661 * Renders an editor form for file categories. 662 */ 663 664 function cat_file_edit() 665 { 666 return cat_event_category_edit('file'); 667 } 668 669 /** 670 * Saves a file category. 671 */ 672 673 function cat_file_save() 674 { 675 return cat_event_category_save('file', 'txp_file'); 676 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
title