Textpattern PHP Cross Reference Content Management Systems

Source: /textpattern/include/txp_category.php - 676 lines - 20573 bytes - Summary - Text - Print

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 &lt;select&gt; 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

title

Description

title

Description

title

title

Body