Textpattern PHP Cross Reference Content Management Systems

Source: /textpattern/include/txp_section.php - 1112 lines - 38346 bytes - Summary - Text - Print

Description: Sections 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   * Sections panel.
  26   *
  27   * @package Admin\Section
  28   */
  29  
  30  use Textpattern\Search\Filter;
  31  
  32  if (!defined('txpinterface')) {
  33      die('txpinterface is undefined.');
  34  }
  35  
  36  if ($event == 'section') {
  37      require_privs('section');
  38  
  39      global $all_skins, $all_pages, $all_styles;
  40  
  41      $all_skins = \Txp::get('Textpattern\Skin\Skin')->getInstalled();
  42      $all_pages = \Txp::get('Textpattern\Skin\Page')->getInstalled();
  43      $all_styles = \Txp::get('Textpattern\Skin\Css')->getInstalled();
  44  
  45      $available_steps = array(
  46          'section_change_pageby' => true,
  47          'sec_section_list'      => false,
  48          'section_delete'        => true,
  49          'section_save'          => true,
  50          'section_edit'          => false,
  51          'section_multi_edit'    => true,
  52          'section_set_default'   => true,
  53          'section_set_theme'     => true,
  54          'section_select_skin'   => false,
  55          'section_toggle_option' => true,
  56      );
  57  
  58      if ($step && is_callable($step) && bouncer($step, $available_steps)) {
  59          $step();
  60      } else {
  61          sec_section_list();
  62      }
  63  }
  64  
  65  /**
  66   * The main panel listing all sections.
  67   *
  68   * So-named to avoid clashing with the &lt;txp:section_list /&gt; tag.
  69   *
  70   * @param string|array $message The activity message
  71   */
  72  
  73  function sec_section_list($message = '', $update = false)
  74  {
  75      global $event, $step, $all_pages, $all_styles, $txp_sections;
  76  
  77      if ($update) {
  78          $txp_sections = safe_column(array('name'), 'txp_section', '1 ORDER BY title, name');
  79      }
  80  
  81      pagetop(gTxt('tab_sections'), $message);
  82  
  83      extract(gpsa(array(
  84          'page',
  85          'sort',
  86          'dir',
  87          'crit',
  88          'search_method',
  89      )));
  90  
  91      $columns = array('name', 'title', 'skin', 'page', 'css', 'permlink_mode', 'on_frontpage', 'in_rss', 'searchable', 'article_count');
  92      $columns = array_merge(
  93          array_combine($columns, $columns),
  94          array('on_frontpage' => 'on_front_page', 'in_rss' => 'syndicate', 'searchable' => 'include_in_search', 'article_count' => 'articles')
  95      );
  96  
  97      if ($sort === '') {
  98          $sort = get_pref('section_sort_column', 'name');
  99      } else {
 100          if (!isset($columns[$sort])) {
 101              $sort = 'name';
 102          }
 103  
 104          set_pref('section_sort_column', $sort, 'section', PREF_HIDDEN, '', 0, PREF_PRIVATE);
 105      }
 106  
 107      if ($dir === '') {
 108          $dir = get_pref('section_sort_dir', 'desc');
 109      } else {
 110          $dir = ($dir == 'asc') ? "asc" : "desc";
 111          set_pref('section_sort_dir', $dir, 'section', PREF_HIDDEN, '', 0, PREF_PRIVATE);
 112      }
 113  
 114      if (isset($columns[$sort])) {
 115          $sort_sql = "$sort $dir";
 116      } else {
 117          $sort_sql = "name $dir";
 118      }
 119  
 120      $switch_dir = ($dir == 'desc') ? 'asc' : 'desc';
 121  
 122      $search = new Filter($event,
 123          array(
 124              'name' => array(
 125                  'column' => 'txp_section.name',
 126                  'label'  => gTxt('name'),
 127              ),
 128              'title' => array(
 129                  'column' => 'txp_section.title',
 130                  'label'  => gTxt('title'),
 131              ),
 132              'skin' => array(
 133                  'column' => array('txp_section.skin', 'txp_section.dev_skin'),
 134                  'label'  => gTxt('skin'),
 135              ),
 136              'page' => array(
 137                  'column' => array('txp_section.page', 'txp_section.dev_page'),
 138                  'label'  => gTxt('page'),
 139              ),
 140              'css' => array(
 141                  'column' => array('txp_section.css', 'txp_section.dev_css'),
 142                  'label'  => gTxt('css'),
 143              ),
 144              'description' => array(
 145                  'column' => 'txp_section.description',
 146                  'label'  => gTxt('description'),
 147              ),
 148              'permlink_mode' => array(
 149                  'column' => 'txp_section.permlink_mode',
 150                  'label'  => gTxt('permlink_mode'),
 151              ),
 152              'on_frontpage' => array(
 153                  'column' => 'txp_section.on_frontpage',
 154                  'label'  => gTxt('on_front_page'),
 155                  'type'   => 'boolean',
 156              ),
 157              'in_rss' => array(
 158                  'column' => 'txp_section.in_rss',
 159                  'label'  => gTxt('syndicate'),
 160                  'type'   => 'boolean',
 161              ),
 162              'searchable' => array(
 163                  'column' => 'txp_section.searchable',
 164                  'label'  => gTxt('include_in_search'),
 165                  'type'   => 'boolean',
 166              ),
 167          )
 168      );
 169  
 170      $alias_yes = '1, Yes';
 171      $alias_no = '0, No';
 172      $search->setAliases('on_frontpage', array($alias_no, $alias_yes));
 173      $search->setAliases('in_rss', array($alias_no, $alias_yes));
 174      $search->setAliases('searchable', array($alias_no, $alias_yes));
 175  
 176      list($criteria, $crit, $search_method) = $search->getFilter();
 177  
 178      $search_render_options = array('placeholder' => 'search_sections');
 179      $total = safe_count('txp_section', $criteria);
 180  
 181      $searchBlock =
 182          n.tag(
 183              $search->renderForm('sec_section', $search_render_options),
 184              'div', array(
 185                  'class' => 'txp-layout-4col-3span',
 186                  'id'    => $event.'_control',
 187              )
 188          );
 189  
 190  
 191      getDefaultSection();
 192      $createBlock = array();
 193  
 194      if (has_privs('section.edit')) {
 195          $createBlock[] =
 196              n.tag(
 197                  sLink('section', 'section_edit', gTxt('create_section'), 'txp-button').
 198                  n.tag_start('form', array(
 199                      'class'  => 'async',
 200                      'id'     => 'default_section_form',
 201                      'name'   => 'default_section_form',
 202                      'method' => 'post',
 203                      'action' => 'index.php',
 204                  )).
 205                  tag(gTxt('default_write_section'), 'label', array('for' => 'default_section')).
 206                  popHelp('section_default').
 207                  section_select_list().
 208                  eInput('section').
 209                  sInput('section_set_default').
 210                  n.tag_end('form'),
 211                  'div', array('class' => 'txp-control-panel')
 212              );
 213      }
 214  
 215      $paginator = new \Textpattern\Admin\Paginator();
 216      $limit = $step == 'section_select_skin' ? PHP_INT_MAX : $paginator->getLimit();
 217  
 218      list($page, $offset, $numPages) = pager($total, $limit, $page);
 219  
 220      $createBlock = implode(n, $createBlock);
 221      $contentBlock = '';
 222  
 223      if ($total < 1) {
 224          if ($crit !== '') {
 225              $contentBlock .= graf(
 226                  span(null, array('class' => 'ui-icon ui-icon-info')).' '.
 227                  gTxt('no_results_found'),
 228                  array('class' => 'alert-block information')
 229              );
 230          }
 231      } else {
 232          $rs = safe_rows_start(
 233              "*, (SELECT COUNT(*) FROM ".safe_pfx_j('textpattern')." WHERE textpattern.Section = txp_section.name) AS article_count",
 234              'txp_section',
 235              "$criteria ORDER BY $sort_sql LIMIT $offset, $limit"
 236          );
 237  
 238          if ($rs) {
 239              $dev_set = false;
 240              $dev_preview = get_pref('enable_dev_preview') && has_privs('skin.edit');
 241              $contentBlock .= n.tag_start('form', array(
 242                      'class'  => 'multi_edit_form',
 243                      'id'     => 'section_form',
 244                      'name'   => 'longform',
 245                      'method' => 'post',
 246                      'action' => 'index.php',
 247                  )).
 248                  n.tag_start('div', array(
 249                      'class'      => 'txp-listtables',
 250                      'tabindex'   => 0,
 251                      'aria-label' => gTxt('list'),
 252                  )).
 253                  n.tag_start('table', array('class' => 'txp-list')).
 254                  n.tag_start('thead');
 255                  $thead = hCell(
 256                      fInput('checkbox', 'select_all', 0, '', '', '', '', '', 'select_all'),
 257                          '', ' class="txp-list-col-multi-edit" scope="col" title="'.gTxt('toggle_all_selected').'"'
 258                  );
 259  
 260              foreach ($columns as $column => $label) {
 261                  $thead .= column_head(
 262                      $label, $column, 'section', true, $switch_dir, $crit, $search_method,
 263                          (($column == $sort) ? "$dir " : '').'txp-list-col-'.$column
 264                  );
 265              }
 266  
 267              $contentBlock .= tr($thead).
 268              n.tag_end('thead').
 269              n.tag_start('tbody');
 270  
 271              while ($a = nextRow($rs)) {
 272                  extract($a, EXTR_PREFIX_ALL, 'sec');
 273  
 274                  $edit_url = array(
 275                      'event'         => 'section',
 276                      'step'          => 'section_edit',
 277                      'name'          => $sec_name,
 278                      'sort'          => $sort,
 279                      'dir'           => $dir,
 280                      'page'          => $page,
 281                      'search_method' => $search_method,
 282                      'crit'          => $crit,
 283                  );
 284  
 285                  if ($sec_name == 'default') {
 286                      $articles = $sec_searchable = $sec_in_rss = $sec_on_frontpage = '-';
 287                  } else {
 288                      $sec_on_frontpage = asyncHref(yes_no($sec_on_frontpage), array(
 289                          'step'     => 'section_toggle_option',
 290                          'thing'    => $sec_name,
 291                          'property' => 'on_frontpage',
 292                      ));
 293  
 294                      $sec_in_rss = asyncHref(yes_no($sec_in_rss), array(
 295                          'step'     => 'section_toggle_option',
 296                          'thing'    => $sec_name,
 297                          'property' => 'in_rss',
 298                      ));
 299  
 300                      $sec_searchable = asyncHref(yes_no($sec_searchable), array(
 301                          'step'     => 'section_toggle_option',
 302                          'thing'    => $sec_name,
 303                          'property' => 'searchable',
 304                      ));
 305  
 306                      if ($sec_article_count > 0) {
 307                          $articles = href($sec_article_count, array(
 308                              'event'         => 'list',
 309                              'search_method' => 'section',
 310                              'crit'          => '"'.$sec_name.'"',
 311                          ), array(
 312                              'title' => gTxt('article_count', array('{num}' => $sec_article_count)),
 313                          ));
 314                      } else {
 315                          $articles = 0;
 316                      }
 317                  }
 318  
 319                  $has_dev_skin = !empty($sec_dev_skin) && $sec_dev_skin !== $sec_skin;
 320                  !empty($sec_dev_skin) or $sec_dev_skin = $sec_skin;
 321                  !empty($sec_dev_page) or $sec_dev_page = $sec_page;
 322                  !empty($sec_dev_css) or $sec_dev_css = $sec_css;
 323  
 324                  $in_dev = false;
 325  
 326                  foreach (array('page', 'css') as $item) {
 327                      $all_items = $item === 'page' ? $all_pages : $all_styles;
 328                      $sec_item = ${"sec_$item"};
 329                      $sec_dev_item = ${"sec_dev_$item"};
 330  
 331                      $missing = $sec_dev_item && isset($all_items[$sec_dev_skin]) && !in_array($sec_dev_item, $all_items[$sec_dev_skin]);
 332                      $replaced = $dev_preview && ($has_dev_skin && $sec_dev_item || $sec_item != $sec_dev_item || $sec_dev_item && $missing) ? 'disabled' : false;
 333                      $dev_set = $dev_set || $replaced;
 334                      $in_dev = $in_dev || $replaced;
 335  
 336                      ${"sec_$item"} = ($sec_item ? tag(href(txpspecialchars($sec_item), array(
 337                          'event' => $item,
 338                          'name'  => $sec_item,
 339                          'skin'  => $sec_skin,
 340                      ), array('title' => gTxt('edit'))
 341                      ), $replaced ? 'span' : null, $replaced ? array('class' => 'secondary-text') : '') : tag(gTxt('none'), 'span', array('class' => 'disabled'))).
 342                      ($replaced ?
 343                          n.'<hr class="secondary" />'.n.
 344                          href(txpspecialchars($sec_dev_item), array(
 345                              'event' => $item,
 346                              'name'  => $sec_dev_item,
 347                              'skin'  => $sec_dev_skin,
 348                          ), array('title' => gTxt('edit'))).
 349                          ($missing ? sp.tag(gTxt('status_missing'), 'small', array('class' => 'alert-block alert-pill error')) : '')
 350                      : '');
 351                  }
 352  
 353                  $replaced = $dev_preview && ($sec_skin != $sec_dev_skin) ? 'disabled' : false;
 354                  $dev_set = $dev_set || $replaced;
 355                  $in_dev = $in_dev || $replaced;
 356  
 357                  $contentBlock .= tr(
 358                      td(
 359                          fInput('checkbox', 'selected[]', $sec_name), '', 'txp-list-col-multi-edit'
 360                      ).
 361                      hCell(
 362                          href(
 363                              txpspecialchars($sec_name), $edit_url, array('title' => gTxt('edit'))
 364                          ).
 365                          span(
 366                              sp.span('&#124;', array('role' => 'separator')).
 367                              sp.href(gTxt('view'), pagelinkurl(array('s' => $sec_name), null, $sec_permlink_mode)),
 368                              array('class' => 'txp-option-link')
 369                          ).
 370                          ($in_dev ? n.'<hr class="secondary" />'.n.tag(gTxt('dev_theme'), 'small', array('class' => 'alert-block alert-pill warning')) : ''), '', array(
 371                              'class' => 'txp-list-col-name',
 372                              'scope' => 'row',
 373                          )
 374                      ).
 375                      td(
 376                          txpspecialchars($sec_title), '', 'txp-list-col-title'
 377                      ).
 378                      td(
 379                          tag($sec_skin, $replaced ? 'span' : null, $replaced ? array('class' => 'secondary-text') : '').($replaced ? n.'<hr class="secondary" />'.n.$sec_dev_skin : ''),
 380                          '', 'txp-list-col-skin'
 381                      ).
 382                      td(
 383                          $sec_page, '', 'txp-list-col-page'
 384                      ).
 385                      td(
 386                          $sec_css, '', 'txp-list-col-style'
 387                      ).
 388                      td(
 389                          $sec_permlink_mode ? gTxt($sec_permlink_mode) : '<span class="secondary-text">'.gTxt(get_pref('permlink_mode')).'</span>', '', 'txp-list-col-permlink_mode'
 390                      ).
 391                      td(
 392                          $sec_on_frontpage, '', 'txp-list-col-on_frontpage'
 393                      ).
 394                      td(
 395                          $sec_in_rss, '', 'txp-list-col-in_rss'
 396                      ).
 397                      td(
 398                          $sec_searchable, '', 'txp-list-col-searchable'
 399                      ).
 400                      td(
 401                          $articles, '', 'txp-list-col-article_count'
 402                      ),
 403                      array('id' => 'txp_section_'.$sec_name)
 404                  );
 405              }
 406  
 407              $disabled = $dev_set ? array() : array('switchdevlive');
 408  
 409              $contentBlock .= n.tag_end('tbody').
 410                  n.tag_end('table').
 411                  n.tag_end('div'). // End of .txp-listtables.
 412                  section_multiedit_form($page, $sort, $dir, $crit, $search_method, $disabled).
 413                  tInput().
 414                  n.tag_end('form');
 415          }
 416      }
 417  
 418      $pageBlock = $paginator->render().
 419          nav_form('section', $page, $numPages, $sort, $dir, $crit, $search_method, $total, $limit);
 420  
 421      $table = new \Textpattern\Admin\Table($event);
 422      echo $table->render(compact('total', 'crit') + array('heading' => 'tab_sections'), $searchBlock, $createBlock, $contentBlock, $pageBlock);
 423  }
 424  
 425  /**
 426   * Renders and outputs the section editor panel.
 427   */
 428  
 429  function section_edit()
 430  {
 431      global $event, $step, $all_skins, $all_pages, $all_styles;
 432  
 433      require_privs('section.edit');
 434  
 435      extract(gpsa(array(
 436          'page',
 437          'sort',
 438          'dir',
 439          'crit',
 440          'search_method',
 441          'name',
 442      )));
 443  
 444      $is_edit = ($name && $step == 'section_edit');
 445      $caption = gTxt('create_section');
 446      $is_default_section = false;
 447  
 448      if ($is_edit) {
 449          $rs = safe_row(
 450              "*",
 451              'txp_section',
 452              "name = '".doSlash($name)."'"
 453          );
 454  
 455          if ($name == 'default') {
 456              $caption = gTxt('edit_default_section');
 457              $is_default_section = true;
 458          } else {
 459              $caption = gTxt('edit_section');
 460          }
 461      } else {
 462          // Pulls defaults for the new section from the 'default'.
 463          $rs = safe_row(
 464              "skin, page, css, on_frontpage, in_rss, searchable",
 465              'txp_section',
 466              "name = 'default'"
 467          );
 468  
 469          if ($rs) {
 470              $rs['name'] = $rs['title'] = $rs['description'] = $rs['permlink_mode'] = '';
 471          }
 472      }
 473  
 474      if (!$rs) {
 475          sec_section_list(array(gTxt('unknown_section'), E_ERROR));
 476  
 477          return;
 478      }
 479  
 480      extract($rs, EXTR_PREFIX_ALL, 'sec');
 481      pagetop(gTxt('tab_sections'));
 482  
 483      $out = array();
 484  
 485      $out[] = hed($caption, 2);
 486  
 487      if ($is_default_section) {
 488          $out[] = hInput('name', 'default');
 489      } else {
 490          $out[] = inputLabel(
 491                  'section_name',
 492                  fInput('text', 'name', $sec_name, '', '', '', INPUT_REGULAR, '', 'section_name', false, true),
 493                  'section_name', '', array('class' => 'txp-form-field edit-section-name')
 494              ).
 495              inputLabel(
 496                  'section_title',
 497                  fInput('text', 'title', $sec_title, '', '', '', INPUT_REGULAR, '', 'section_title'),
 498                  'section_longtitle', '', array('class' => 'txp-form-field edit-section-longtitle')
 499              );
 500      }
 501  
 502      $pageSelect = selectInput(array('name' => 'section_page', 'required' => false), array(), '', '', '', 'section_page');
 503      $styleSelect = selectInput(array('name' => 'css', 'required' => false), array(), '', '', '', 'section_css');
 504      $json_page = json_encode($all_pages, TEXTPATTERN_JSON);
 505      $json_style = json_encode($all_styles, TEXTPATTERN_JSON);
 506  
 507      $out[] =
 508          inputLabel(
 509              'section_skin',
 510              selectInput('skin', $all_skins, $sec_skin, '', '', 'section_skin'),
 511              'uses_skin',
 512              'section_uses_skin',
 513              array('class' => 'txp-form-field edit-section-uses-skin')
 514          ).
 515          inputLabel(
 516              'section_page',
 517              $pageSelect,
 518              'uses_page',
 519              'section_uses_page',
 520              array('class' => 'txp-form-field edit-section-uses-page')
 521          ).
 522          inputLabel(
 523              'section_css',
 524              $styleSelect,
 525              'uses_style',
 526              'section_uses_css',
 527              array('class' => 'txp-form-field edit-section-uses-css')
 528          ).
 529          inputLabel(
 530              'permlink_mode',
 531              permlinkmodes('permlink_mode', $is_default_section ? get_pref('permlink_mode') : $sec_permlink_mode, $is_default_section ? false : array('' => gTxt('default'))),
 532              'permlink_mode',
 533              'permlink_mode',
 534              array('class' => 'txp-form-field edit-section-permlink-mode')
 535          ).
 536          script_js(<<<EOJS
 537  var skin_page = {$json_page};
 538  var skin_style = {$json_style};
 539  var page_sel = '{$sec_page}';
 540  var style_sel = '{$sec_css}';
 541  EOJS
 542          );
 543  
 544      if (!$is_default_section) {
 545          $out[] = inputLabel(
 546                  'on_front_page',
 547                  yesnoradio('on_frontpage', $sec_on_frontpage, '', $sec_name),
 548                  '', 'section_on_frontpage', array('class' => 'txp-form-field edit-section-on-frontpage')
 549              ).
 550              inputLabel(
 551                  'syndicate',
 552                  yesnoradio('in_rss', $sec_in_rss, '', $sec_name),
 553                  '', 'section_syndicate', array('class' => 'txp-form-field edit-section-syndicate')
 554              ).
 555              inputLabel(
 556                  'include_in_search',
 557                  yesnoradio('searchable', $sec_searchable, '', $sec_name),
 558                  '', 'section_searchable', array('class' => 'txp-form-field edit-section-searchable')
 559              );
 560      }
 561  
 562      $out[] = inputLabel(
 563              'section_description',
 564              '<textarea id="section_description" name="description" cols="'.INPUT_LARGE.'" rows="'.TEXTAREA_HEIGHT_SMALL.'">'.$sec_description.'</textarea>',
 565              'description', 'section_description', array('class' => 'txp-form-field txp-form-field-textarea edit-section-description')
 566          );
 567  
 568      $out[] = pluggable_ui('section_ui', 'extend_detail_form', '', $rs).
 569          graf(
 570              sLink('section', '', gTxt('cancel'), 'txp-button').
 571              fInput('submit', '', gTxt('save'), 'publish'),
 572              array('class' => 'txp-edit-actions')
 573          ).
 574          eInput('section').
 575          sInput('section_save').
 576          hInput('old_name', $sec_name).
 577          hInput('search_method', $search_method).
 578          hInput('crit', $crit).
 579          hInput('page', $page).
 580          hInput('sort', $sort).
 581          hInput('dir', $dir);
 582  
 583      echo form(join('', $out), '', '', 'post', 'txp-edit', '', 'section_details');
 584  }
 585  
 586  /**
 587   * Saves a section.
 588   */
 589  
 590  function section_save()
 591  {
 592      $in = array_map('assert_string', psa(array(
 593          'name',
 594          'title',
 595          'skin',
 596          'description',
 597          'old_name',
 598          'section_page',
 599          'css',
 600          'permlink_mode',
 601      )));
 602  
 603      if (empty($in['title'])) {
 604          $in['title'] = $in['name'];
 605      }
 606  
 607      // Prevent non-URL characters on section names.
 608      $mbstrings = extension_loaded('mbstrings');
 609      $in['name'] = $mbstrings ?
 610          mb_strtolower(sanitizeForUrl($in['name']), 'UTF-8') :
 611          strtolower(sanitizeForUrl($in['name']));
 612  
 613      extract($in);
 614  
 615      $in = doSlash($in);
 616      extract($in, EXTR_PREFIX_ALL, 'safe');
 617      $lower_name = $mbstrings ?
 618          mb_strtolower($old_name, 'UTF-8') :
 619          strtolower($old_name);
 620  
 621      if ($name != $lower_name) {
 622          if (safe_field("name", 'txp_section', "name = '$safe_name'")) {
 623              // Invalid input. Halt all further processing (e.g. plugin event
 624              // handlers).
 625              $message = array(gTxt('section_name_already_exists', array('{name}' => $name)), E_ERROR);
 626              sec_section_list($message);
 627  
 628              return;
 629          }
 630      }
 631  
 632      $ok = false;
 633  
 634      if ($name == 'default') {
 635          $on_frontpage = $in_rss = $searchable = 0;
 636  
 637          $ok = safe_update('txp_section', "skin = '$safe_skin', page = '$safe_section_page', css = '$safe_css', description = '$safe_description'", "name = 'default'");
 638          set_pref('permlink_mode', $permlink_mode);
 639      } elseif ($name) {
 640          extract(array_map('assert_int', psa(array('on_frontpage', 'in_rss', 'searchable'))));
 641  
 642          if ($safe_old_name) {
 643              $ok = safe_update('txp_section', "
 644                  name           = '$safe_name',
 645                  title          = '$safe_title',
 646                  skin           = '$safe_skin',
 647                  page           = '$safe_section_page',
 648                  css            = '$safe_css',
 649                  description    = '$safe_description',
 650                  permlink_mode  = '$safe_permlink_mode',
 651                  on_frontpage   = '$on_frontpage',
 652                  in_rss         = '$in_rss',
 653                  searchable     = '$searchable'
 654                  ", "name = '$safe_old_name'");
 655  
 656              // Manually maintain referential integrity.
 657              if ($ok) {
 658                  $ok = safe_update('textpattern', "Section = '$safe_name'", "Section = '$safe_old_name'");
 659              }
 660          } else {
 661              $ok = safe_insert('txp_section', "
 662                  name         = '$safe_name',
 663                  title        = '$safe_title',
 664                  skin         = '$safe_skin',
 665                  page         = '$safe_section_page',
 666                  css          = '$safe_css',
 667                  description  = '$safe_description',
 668                  permlink_mode  = '$safe_permlink_mode',
 669                  on_frontpage = '$on_frontpage',
 670                  in_rss       = '$in_rss',
 671                  searchable   = '$searchable'");
 672          }
 673      }
 674  
 675      if ($ok) {
 676          if ($name != $lower_name && $lower_name == get_pref('default_section')) {
 677              set_pref('default_section', $name, 'section', PREF_HIDDEN);
 678          }
 679          update_lastmod('section_saved', compact('name', 'title', 'section_page', 'css', 'description', 'on_frontpage', 'in_rss', 'searchable', 'permlink_mode'));
 680          Txp::get('Textpattern\Skin\Skin')->setEditing($safe_skin);
 681      }
 682  
 683      if ($ok) {
 684          sec_section_list(gTxt(($safe_old_name ? 'section_updated' : 'section_created'), array('{name}' => $name)), true);
 685      } else {
 686          sec_section_list(array(gTxt('section_save_failed'), E_ERROR));
 687      }
 688  }
 689  
 690  /**
 691   * Changes and saves the pageby value.
 692   */
 693  
 694  function section_change_pageby()
 695  {
 696      Txp::get('\Textpattern\Admin\Paginator')->change();
 697      sec_section_list();
 698  }
 699  
 700  /**
 701   * Toggles section yes/no parameters.
 702   *
 703   * This function requires three HTTP POST parameters: 'column', 'value' and
 704   * 'name'. The 'value' is the new value, localised 'Yes' or 'No',
 705   * 'name' is the section and the 'column' is the altered setting,
 706   * either 'on_frontpage', 'in_rss' or 'searchable'.
 707   *
 708   * Outputs a text/plain response comprising the new displayable
 709   * value for the toggled parameter.
 710   */
 711  
 712  function section_toggle_option()
 713  {
 714      extract(psa(array(
 715          'property',
 716          'value',
 717          'thing',
 718      )));
 719  
 720      $value = (int) ($value === gTxt('no'));
 721  
 722      if (in_array($property, array('on_frontpage', 'in_rss', 'searchable'))) {
 723          if (safe_update('txp_section', "$property = $value", "name = '".doSlash($thing)."'")) {
 724              echo yes_no($value);
 725  
 726              return;
 727          }
 728      }
 729  
 730      trigger_error(gTxt('section_save_failed'), E_USER_ERROR);
 731  }
 732  
 733  /**
 734   * Sets a section as the default.
 735   */
 736  
 737  function section_set_default()
 738  {
 739      extract(psa(array('default_section')));
 740  
 741      $exists = safe_row("name", 'txp_section', "name = '".doSlash($default_section)."'");
 742  
 743      if ($exists && set_pref('default_section', $default_section, 'section', PREF_HIDDEN)) {
 744          send_script_response(announce(gTxt('default_section_updated')));
 745  
 746          return;
 747      }
 748  
 749      send_script_response(announce(gTxt('section_save_failed'), E_ERROR));
 750  }
 751  
 752  /**
 753   * Renders a 'default_section' &lt;select&gt; input listing all sections.
 754   *
 755   * Used for changing the default section.
 756   *
 757   * @return string HTML
 758   */
 759  
 760  function section_select_list()
 761  {
 762      global $txp_sections;
 763  
 764      $val = get_pref('default_section');
 765      $vals = array();
 766  
 767      foreach ($txp_sections as $name => $row) {
 768          $name == 'default' or $vals[$name] = $row['title'];
 769      }
 770  
 771      return selectInput(array(
 772          'name' => 'default_section', 'class' => 'txp-async-update'
 773      ), $vals, $val, false, true, 'default_section');
 774  }
 775  
 776  /**
 777   * Processes delete actions sent using the multi-edit form.
 778   */
 779  
 780  function section_delete()
 781  {
 782      global $txp_sections;
 783  
 784      $selectedList = ps('selected');
 785      $selected = join(',', quote_list($selectedList));
 786      $message = '';
 787  
 788      $sections = safe_column(
 789          "name",
 790          'txp_section',
 791          "name != 'default' AND name IN ($selected) AND name NOT IN (SELECT Section FROM ".safe_pfx('textpattern').")"
 792      );
 793  
 794      $sectionsNotDeleted = array_diff($selectedList, $sections);
 795  
 796      if ($sections && safe_delete('txp_section', "name IN (".join(',', quote_list($sections)).")")) {
 797          foreach ($sections as $section) {
 798              unset($txp_sections[$section]);
 799          }
 800  
 801          callback_event('sections_deleted', '', 0, $sections);
 802          $message = gTxt('section_deleted', array('{name}' => join(', ', $sections)));
 803      }
 804  
 805      if ($sectionsNotDeleted) {
 806          $severity = ($message) ? E_WARNING : E_ERROR;
 807          $message = array(($message ? $message.n : '').gTxt('section_delete_failure', array('{name}' => join(', ', $sectionsNotDeleted))), $severity);
 808      }
 809  
 810      sec_section_list($message);
 811  }
 812  
 813  /**
 814   * Processes theme preview actions.
 815   */
 816  
 817  function section_set_theme($type = 'dev_skin')
 818  {
 819      global $all_skins, $all_pages, $all_styles;
 820  
 821      $skin = gps('skin');
 822      $message = '';
 823  
 824      if (isset($all_skins[$skin]) && has_privs('skin.edit')) {
 825          safe_update(
 826              'txp_section',
 827              "$type = '".doSlash($skin)."'",
 828              $type == 'dev_skin' ? '1' : 'page IN ('.join(',', quote_list($all_pages[$skin])).') AND css IN ('.join(',', quote_list($all_styles[$skin])).')'
 829          );
 830          $message = gTxt($type == 'dev_skin' ? 'dev_theme' : 'live_theme').': '.txpspecialchars($all_skins[$skin]);
 831  
 832          if ($type == 'dev_skin') {
 833              Txp::get('Textpattern\Skin\Skin')->setName($skin)->setEditing();
 834          }
 835      }
 836  
 837      script_js(<<<EOS
 838  if (typeof window.history.replaceState == 'function') {history.replaceState({}, '', '?event=section')}
 839  EOS
 840      , false);
 841      sec_section_list($message, true);
 842  }
 843  
 844  /**
 845   * Renders a multi-edit form widget.
 846   *
 847   * @param  int    $page          The page number
 848   * @param  string $sort          The current sorting value
 849   * @param  string $dir           The current sorting direction
 850   * @param  string $crit          The current search criteria
 851   * @param  string $search_method The current search method
 852   * @return string HTML
 853   */
 854  
 855  function section_multiedit_form($page, $sort, $dir, $crit, $search_method, $disabled = array())
 856  {
 857      global $all_skins, $all_pages, $all_styles, $step;
 858  
 859      $json_page = json_encode($all_pages, TEXTPATTERN_JSON);
 860      $json_style = json_encode($all_styles, TEXTPATTERN_JSON);
 861  
 862      $themeSelect = inputLabel(
 863          'multiedit_skin',
 864          selectInput('skin', $all_skins, gps('skin'), false, '', 'multiedit_skin'),
 865          'skin', '', array('class' => 'multi-option multi-step'), ''
 866      );
 867  
 868      $pageSelect = inputLabel(
 869          'multiedit_page',
 870          selectInput('section_page', array(), '', '', '', 'multiedit_page'),
 871          'page', '', array('class' => 'multi-option multi-step'), ''
 872      );
 873  
 874      $styleSelect = inputLabel(
 875          'multiedit_css',
 876          selectInput('css', array(), '', '', '', 'multiedit_css'),
 877          'css', '', array('class' => 'multi-option multi-step'), ''
 878      );
 879  
 880      $devThemeSelect = inputLabel(
 881          'multiedit_skin',
 882          selectInput('dev_skin', $all_skins, '', false, '', 'multiedit_dev_skin'),
 883          'skin', '', array('class' => 'multi-option multi-step'), ''
 884      );
 885  
 886      $devPageSelect = inputLabel(
 887          'multiedit_page',
 888          selectInput('dev_page', array(), '', '', '', 'multiedit_dev_page'),
 889          'page', '', array('class' => 'multi-option multi-step'), ''
 890      );
 891  
 892      $devStyleSelect = inputLabel(
 893          'multiedit_css',
 894          selectInput('dev_css', array(), '', '', '', 'multiedit_dev_css'),
 895          'css', '', array('class' => 'multi-option multi-step'), ''
 896      );
 897  
 898      $dev_preview = get_pref('enable_dev_preview') && has_privs('skin.edit');
 899  
 900      $methods = array(
 901          'changepagestyle' => array(
 902              'label' => gTxt('change_page_style'),
 903              'html'  => (!$dev_preview ?
 904                  hInput('live_theme', 1) :
 905                  inputLabel('dev_theme',
 906                      checkbox2('dev_theme', 1, 0, 'dev_theme'),
 907                      'dev_theme', '', array('class' => 'multi-option multi-step'), ''
 908                  ) . inputLabel('live_theme',
 909                      checkbox2('live_theme', 0, 0, 'live_theme'),
 910                      'live_theme', '', array('class' => 'multi-option multi-step'), ''
 911                  )
 912              ) . $themeSelect . $pageSelect . $styleSelect
 913          ),
 914          'switchdevlive' => array(
 915              'label' => gTxt('switch_dev_live'),
 916              'html'  => radioSet(array(
 917                  0 => gTxt('live_to_dev'),
 918                  1 => gTxt('dev_to_live'),
 919                  ), 'switch_dev_live', 0),
 920          ),
 921          'permlinkmode' => array(
 922              'label' => gTxt('permlink_mode'),
 923              'html'  => permlinkmodes('permlink_mode', '', array('' => gTxt('default'))),
 924          ),
 925          'changeonfrontpage' => array(
 926              'label' => gTxt('on_front_page'),
 927              'html'  => yesnoRadio('on_frontpage', 1),
 928          ),
 929          'changesyndicate' => array(
 930              'label' => gTxt('syndicate'),
 931              'html'  => yesnoRadio('in_rss', 1),
 932          ),
 933          'changesearchable' => array(
 934              'label' => gTxt('include_in_search'),
 935              'html'  => yesnoRadio('searchable', 1),
 936          ),
 937          'delete' => gTxt('delete'),
 938      );
 939  
 940      foreach ($disabled as $method) {
 941          unset($methods[$method]);
 942      }
 943  
 944      $script = <<<EOJS
 945  var skin_page = {$json_page};
 946  var skin_style = {$json_style};
 947  var page_sel = null;
 948  var style_sel = null;
 949  EOJS;
 950  
 951      if ($step == 'section_select_skin') {
 952          $script .= <<<EOJS
 953  $(function() {
 954      $('#select_all').click();
 955      $('[name="edit_method"]').val('changepagestyle').change();
 956      var skin = $('#multiedit_skin');
 957      var selected = skin.find('option[selected]').val();
 958      skin.val(selected || '').change();
 959  });
 960  EOJS;
 961      }
 962      return multi_edit($methods, 'section', 'section_multi_edit', $page, $sort, $dir, $crit, $search_method).
 963      script_js($script, false);
 964  }
 965  
 966  /**
 967   * Processes multi-edit actions.
 968   */
 969  
 970  function section_multi_edit()
 971  {
 972      global $txp_user, $all_skins, $all_pages, $all_styles;
 973  
 974      extract(psa(array(
 975          'edit_method',
 976          'selected',
 977      )));
 978  
 979      if (!$selected || !is_array($selected)) {
 980          return sec_section_list();
 981      }
 982  
 983      $nameVal = array();
 984  
 985      switch ($edit_method) {
 986          case 'delete':
 987              return section_delete();
 988              break;
 989          case 'changepagestyle':
 990              if (ps('live_theme')) {
 991                  $nameVal += array(
 992                      'skin' => ps('skin'),
 993                      'page' => ps('section_page'),
 994                      'css'  => ps('css'),
 995                  );
 996              }
 997  
 998              if (ps('dev_theme')) {
 999                  $nameVal += array(
1000                      'dev_skin' => ps('skin'),
1001                      'dev_page' => ps('section_page'),
1002                      'dev_css'  => ps('css'),
1003                  );
1004              }
1005  
1006              break;
1007          case 'switchdevlive':
1008              $nameVal['switch_dev_live'] = (int) ps('switch_dev_live');
1009              break;
1010          case 'permlinkmode':
1011              $nameVal['permlink_mode'] = (string) ps('permlink_mode');
1012              break;
1013          case 'changeonfrontpage':
1014              $nameVal['on_frontpage'] = (int) ps('on_frontpage');
1015              break;
1016          case 'changesyndicate':
1017              $nameVal['in_rss'] = (int) ps('in_rss');
1018              break;
1019          case 'changesearchable':
1020              $nameVal['searchable'] = (int) ps('searchable');
1021              break;
1022      }
1023  
1024      $setskin = "IF(dev_skin > '', dev_skin, skin)";
1025      $setpage = "IF(dev_page > '', dev_page, page)";
1026      $setcss = "IF(dev_css > '', dev_css, css)";
1027  
1028      $filter = array("name IN (".join(',', quote_list($selected)).")");
1029      $message = '';
1030  
1031      if ($edit_method === 'changepagestyle' && !empty($nameVal['skin'])) {
1032          $skin = $nameVal['skin'];
1033  
1034          if (empty($nameVal['page'])) {
1035              $filter[] = empty($all_pages[$skin]) ?
1036                  '0' :
1037                  "page IN (".join(',', quote_list($all_pages[$skin])).")";
1038          }
1039  
1040          if (empty($nameVal['css'])) {
1041              $filter[] = empty($all_styles[$skin]) ?
1042                  '0' :
1043                  "css IN (".join(',', quote_list($all_styles[$skin])).")";
1044          }
1045      } elseif ($edit_method === 'switchdevlive' && empty($nameVal['switch_dev_live'])) {
1046          $skinset = array();
1047  
1048          foreach ($all_skins as $skin => $title) {
1049              $skinset[] = "$setskin = '".doSlash($skin)."' AND ($setpage = '' OR ".
1050              (empty($all_pages[$skin]) ?
1051                  '0' :
1052                  "$setpage IN (".join(',', quote_list($all_pages[$skin]))."))"
1053              )." AND ($setcss = '' OR ".
1054              (empty($all_styles[$skin]) ?
1055                  '0' :
1056                  "$setcss IN (".join(',', quote_list($all_styles[$skin]))."))"
1057              );
1058          }
1059  
1060          $filter[] = '('.implode(' OR ', $skinset).')';
1061      }
1062  
1063      $sections = safe_column(
1064          "name",
1065          'txp_section',
1066          implode(' AND ', $filter)
1067      );
1068  
1069      if ($nameVal && $sections) {
1070          if ($edit_method == 'switchdevlive') {
1071              $set = ($nameVal['switch_dev_live'] ? '' :
1072                  "skin = $setskin,
1073                  page = $setpage,
1074                  css = $setcss, "
1075              )."dev_skin = '', dev_page = '', dev_css = ''";
1076          } elseif ($edit_method == 'permlinkmode') {
1077              $set = "permlink_mode = IF(name='default', '', '".doSlash($nameVal['permlink_mode'])."')";
1078  
1079              if ($nameVal['permlink_mode'] && in_array('default', $sections)) {
1080                  set_pref('permlink_mode', $nameVal['permlink_mode']);
1081              }
1082          } else {
1083              $in = array();
1084  
1085              foreach ($nameVal as $key => $val) {
1086                  if ((string)$val != '*') {
1087                      $in[] = "{$key} = '".doSlash($val)."'";
1088                  }
1089              }
1090  
1091              $set = implode(',', $in);
1092          }
1093  
1094          if ($set &&
1095              safe_update(
1096                  'txp_section',
1097                  $set,
1098                  "name IN (".join(',', quote_list($sections)).")"
1099              )
1100          ) {
1101              $message = gTxt('section_updated', array('{name}' => join(', ', $sections)));
1102  
1103              if ($edit_method === 'changepagestyle') {
1104                  Txp::get('Textpattern\Skin\Skin')->setEditing(doSlash($nameVal['skin']));
1105              }
1106          }
1107      } else {
1108          $message = array(gTxt('section_save_failed'), E_ERROR);
1109      }
1110  
1111      sec_section_list($message, $nameVal && $sections);
1112  }

title

Description

title

Description

title

Description

title

title

Body