Textpattern PHP Cross Reference Content Management Systems

Source: /textpattern/include/txp_category.php - 665 lines - 20071 bytes - Summary - Text - Print

Description: Category panel.

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

title

Description

title

Description

title

Description

title

title

Body