Textpattern PHP Cross Reference Content Management Systems

Source: /textpattern/include/txp_page.php - 602 lines - 17766 bytes - Summary - Text - Print

Description: Pages 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   * Pages panel.
  26   *
  27   * @package Admin\Page
  28   */
  29  
  30  use Textpattern\Skin\Skin;
  31  use Textpattern\Skin\Page;
  32  
  33  if (!defined('txpinterface')) {
  34      die('txpinterface is undefined.');
  35  }
  36  
  37  if ($event == 'page') {
  38      require_privs('page');
  39  
  40      $instance = Txp::get('Textpattern\Skin\Page');
  41  
  42      bouncer($step, array(
  43          'page_edit'        => false,
  44          'page_save'        => true,
  45          'page_delete'      => true,
  46          'page_skin_change' => true,
  47          'tagbuild'         => false,
  48      ));
  49  
  50      switch (strtolower($step)) {
  51          case '':
  52              page_edit();
  53              break;
  54          case 'page_edit':
  55              page_edit();
  56              break;
  57          case 'page_save':
  58              page_save();
  59              break;
  60          case 'page_delete':
  61              page_delete();
  62              break;
  63          case 'page_new':
  64              page_new();
  65              break;
  66          case "page_skin_change":
  67              $instance->selectEdit();
  68              page_edit();
  69              break;
  70          case 'tagbuild':
  71              echo page_tagbuild();
  72              break;
  73      }
  74  }
  75  
  76  /**
  77   * The main Page editor panel.
  78   *
  79   * @param string|array $message          The activity message
  80   * @param bool         $refresh_partials Whether to refresh partial contents
  81   */
  82  
  83  function page_edit($message = '', $refresh_partials = false)
  84  {
  85      global $instance, $event, $step;
  86  
  87      /*
  88      $partials is an array of:
  89      $key => array (
  90          'mode' => {PARTIAL_STATIC | PARTIAL_VOLATILE | PARTIAL_VOLATILE_VALUE},
  91          'selector' => $DOM_selector or array($selector, $fragment) of $DOM_selectors,
  92           'cb' => $callback_function,
  93           'html' => $return_value_of_callback_function (need not be initialised here)
  94      )
  95      */
  96      $partials = array(
  97          // Stylesheet list.
  98          'list' => array(
  99              'mode'     => PARTIAL_VOLATILE,
 100              'selector' => '#all_pages',
 101              'cb'       => 'page_list',
 102          ),
 103          // Name field.
 104          'name' => array(
 105              'mode'     => PARTIAL_VOLATILE,
 106              'selector' => 'div.name',
 107              'cb'       => 'page_partial_name',
 108          ),
 109          // Name value.
 110          'name_value'  => array(
 111              'mode'     => PARTIAL_VOLATILE_VALUE,
 112              'selector' => '#new_page,#main_content input[name=name]',
 113              'cb'       => 'page_partial_name_value',
 114          ),
 115          // Textarea.
 116          'template' => array(
 117              'mode'     => PARTIAL_STATIC,
 118              'selector' => 'div.template',
 119              'cb'       => 'page_partial_template',
 120          ),
 121      );
 122  
 123      extract(array_map('assert_string', gpsa(array(
 124          'copy',
 125          'save_error',
 126          'savenew',
 127          'skin',
 128      ))));
 129  
 130      $default_name = safe_field("page", 'txp_section', "name = 'default'");
 131  
 132      $name = assert_string(gps('name'));
 133      $newname = Page::sanitize(assert_string(gps('newname')));
 134      $skin = ($skin !== '') ? $skin : null;
 135      $class = 'async';
 136  
 137      $thisSkin = Txp::get('Textpattern\Skin\Skin');
 138      $skin = $thisSkin->setName($skin)->setEditing();
 139  
 140      if ($step == 'page_delete' || empty($name) && $step != 'page_new' && !$savenew) {
 141          $name = get_pref('last_page_saved', $default_name);
 142      } elseif ((($copy || $savenew) && $newname) && !$save_error) {
 143          $name = $newname;
 144      } elseif ((($newname && ($newname != $name)) || $step === 'page_new') && !$save_error) {
 145          $name = $newname;
 146          $class = '';
 147      } elseif ($savenew && $save_error) {
 148          $class = '';
 149      }
 150  
 151      if (!$save_error) {
 152          $html = safe_field('user_html', 'txp_page', "name = '".doSlash($name)."' AND skin = '" . doSlash($skin) . "'");
 153      } else {
 154          $html = gps('html');
 155      }
 156  
 157      $actionsExtras = '';
 158  
 159      if ($name) {
 160          $actionsExtras .= sLink('page', 'page_new', '<span class="ui-icon ui-extra-icon-new-document"></span> '.gTxt('create_page'), 'txp-new')
 161          .href('<span class="ui-icon ui-icon-copy"></span> '.gTxt('duplicate'), '#',
 162              array(
 163                  'class'     => 'txp-clone',
 164                  'data-form' => 'page_form',
 165              )
 166          );
 167      }
 168  
 169      $actions = graf(
 170          $actionsExtras,
 171          array('class' => 'txp-actions txp-actions-inline')
 172      );
 173  
 174      $skinBlock = n.$instance->setSkin($thisSkin)->getSelectEdit();
 175  
 176      $buttons = graf(
 177          (!is_writable($instance->getDirPath()) ? '' :
 178              span(
 179                  checkbox2('export', gps('export'), 0, 'export').
 180                  n.tag(gTxt('export_to_disk'), 'label', array('for' => 'export'))
 181              , array('class' => 'txp-save-export'))
 182          ).n.
 183          tag_void('input', array(
 184              'class'  => 'publish',
 185              'type'   => 'submit',
 186              'method' => 'post',
 187              'value'  =>  gTxt('save'),
 188          )), ' class="txp-save"'
 189      );
 190  
 191      $rs = array(
 192          'name'    => $name,
 193          'newname' => $newname,
 194          'default' => $default_name,
 195          'skin'    => $skin,
 196          'html'    => $html,
 197          );
 198  
 199      // Get content for volatile partials.
 200      $partials = updatePartials($partials, $rs, array(PARTIAL_VOLATILE, PARTIAL_VOLATILE_VALUE));
 201  
 202      if ($refresh_partials) {
 203          $response[] = announce($message);
 204          $response = array_merge($response, updateVolatilePartials($partials));
 205          send_script_response(join(";\n", $response));
 206  
 207          // Bail out.
 208          return;
 209      }
 210  
 211      // Get content for static partials.
 212      $partials = updatePartials($partials, $rs, PARTIAL_STATIC);
 213  
 214      pagetop(gTxt('tab_pages'), $message);
 215  
 216      echo n.'<div class="txp-layout">'.
 217          n.tag(
 218              hed(gTxt('tab_pages'), 1, array('class' => 'txp-heading')),
 219              'div', array('class' => 'txp-layout-1col')
 220          );
 221  
 222      // Pages create/switcher column.
 223      echo n.tag(
 224          $skinBlock.$partials['list']['html'].n,
 225          'div', array(
 226              'class' => 'txp-layout-4col-alt',
 227              'id'    => 'content_switcher',
 228              'role'  => 'region',
 229          )
 230      );
 231  
 232      // Pages code column.
 233      echo n.tag(
 234          form(
 235              $actions.
 236              $partials['name']['html'].
 237              $partials['template']['html'].
 238              $buttons, '', '', 'post', $class, '', 'page_form'),
 239          'div', array(
 240              'class' => 'txp-layout-4col-3span',
 241              'id'    => 'main_content',
 242              'role'  => 'region',
 243          )
 244      );
 245  
 246      // Tag builder dialog placeholder.
 247      echo n.tag(
 248          '&nbsp;',
 249          'div', array(
 250              'class'      => 'txp-tagbuilder-content',
 251              'id'         => 'tagbuild_links',
 252              'aria-label' => gTxt('tagbuilder'),
 253              'title'      => gTxt('tagbuilder'),
 254          ));
 255  
 256      echo n.'</div>'; // End of .txp-layout.
 257  }
 258  
 259  /**
 260   * Renders a list of page templates.
 261   *
 262   * @param  string $current The selected template info
 263   * @return string HTML
 264   */
 265  
 266  function page_list($current)
 267  {
 268      $out = array();
 269      $safe_skin = doSlash($current['skin']);
 270      $protected = safe_column("DISTINCT page", 'txp_section', "skin = '$safe_skin' OR dev_skin = '$safe_skin'") + array('error_default');
 271  
 272      $criteria = "skin = '$safe_skin'";
 273      $criteria .= callback_event('admin_criteria', 'page_list', 0, $criteria);
 274  
 275      $rs = safe_rows_start("name", 'txp_page', "$criteria ORDER BY name ASC");
 276  
 277      if ($rs) {
 278          while ($a = nextRow($rs)) {
 279              extract($a);
 280              $active = ($current['name'] === $name);
 281  
 282              $edit = eLink('page', '', 'name', $name, $name);
 283  
 284              if (!in_array($name, $protected)) {
 285                  $edit .= dLink('page', 'page_delete', 'name', $name);
 286              }
 287  
 288              $out[] = tag(n.$edit.n, 'li', array('class' => $active ? 'active' : ''));
 289          }
 290  
 291          $out = tag(join(n, $out), 'ul', array('class' => 'switcher-list'));
 292  
 293          return wrapGroup('all_pages', $out, 'all_pages');
 294      }
 295  }
 296  
 297  /**
 298   * Deletes a page template.
 299   */
 300  
 301  function page_delete()
 302  {
 303      global $prefs;
 304  
 305      $name = ps('name');
 306      $safe_name = doSlash($name);
 307      $skin = get_pref('skin_editing', 'default');
 308      $safe_skin = doSlash($skin);
 309      $count = safe_count('txp_section', "page = '$safe_name' AND (skin='$safe_skin' OR dev_skin='$safe_skin')");
 310      $message = '';
 311  
 312      if ($name == 'error_default') {
 313          return page_edit();
 314      }
 315  
 316      if ($count) {
 317          $message = array(gTxt('page_used_by_section', array(
 318              '{name}'  => $name,
 319              '{count}' => $count,
 320          )), E_WARNING);
 321      } else {
 322          if (safe_delete('txp_page', "name = '$safe_name' AND skin='$safe_skin'")) {
 323              callback_event('page_deleted', '', 0, compact('name', 'skin'));
 324              $message = gTxt('page_deleted', array('{list}' => $name));
 325              if ($name === get_pref('last_page_saved')) {
 326                  unset($prefs['last_page_saved']);
 327                  remove_pref('last_page_saved', 'page');
 328              }
 329          }
 330      }
 331  
 332      page_edit($message);
 333  }
 334  
 335  /**
 336   * Changes the skin in which styles are being edited.
 337   *
 338   * Keeps track of which skin is being edited from panel to panel.
 339   *
 340   * @param      string $skin Optional skin name. Read from GET/POST otherwise
 341   * @deprecated in 4.7.0
 342   */
 343  
 344  function page_skin_change($skin = null)
 345  {
 346      Txp::get('Textpattern\Skin\Page')->selectEdit($skin);
 347  
 348      return true;
 349  }
 350  
 351  /**
 352   * Saves or clones a page template.
 353   */
 354  
 355  function page_save()
 356  {
 357      global $instance, $app_mode;
 358  
 359      extract(doSlash(array_map('assert_string', psa(array(
 360          'savenew',
 361          'html',
 362          'copy',
 363          'skin',
 364      )))));
 365  
 366      $passedName = assert_string(ps('name'));
 367      $name = Page::sanitize($passedName);
 368      $newname = Page::sanitize(assert_string(ps('newname')));
 369  
 370      $skin = Txp::get('Textpattern\Skin\Skin')->setName($skin)->setEditing();
 371  
 372      $save_error = false;
 373      $message = '';
 374  
 375      if (!$newname) {
 376          $message = array(gTxt('page_name_invalid'), E_ERROR);
 377          $save_error = true;
 378      } else {
 379          if ($copy && ($name === $newname)) {
 380              $newname .= '_copy';
 381              $passedName = $name;
 382              $_POST['newname'] = $newname;
 383          }
 384  
 385          $safe_skin = doSlash($skin);
 386          $safe_name = doSlash($passedName);
 387          $safe_newname = doSlash($newname);
 388  
 389          $exists = safe_field('name', 'txp_page', "name = '$safe_newname' AND skin = '$safe_skin'");
 390  
 391          if ($newname !== $name && $exists !== false) {
 392              $message = array(gTxt('page_already_exists', array('{name}' => $newname)), E_ERROR);
 393  
 394              if ($savenew) {
 395                  $_POST['newname'] = '';
 396              }
 397  
 398              $save_error = true;
 399          } else {
 400              if ($savenew or $copy) {
 401                  if ($newname) {
 402                      if (safe_insert('txp_page', "name = '$safe_newname', user_html = '$html', skin = '$safe_skin'")) {
 403                          set_pref('last_page_saved', $newname, 'page', PREF_HIDDEN, 'text_input', 0, PREF_PRIVATE);
 404                          update_lastmod('page_created', compact('newname', 'name', 'html'));
 405  
 406                          $message = gTxt('page_created', array('{list}' => $newname));
 407  
 408                          // If page name has been auto-sanitized, throw a warning.
 409                          if ($passedName !== $name) {
 410                              $message = array($message, E_WARNING);
 411                          }
 412  
 413                          callback_event($copy ? 'page_duplicated' : 'page_created', '', 0, $name, $newname);
 414                      } else {
 415                          $message = array(gTxt('page_save_failed'), E_ERROR);
 416                          $save_error = true;
 417                      }
 418                  } else {
 419                      $message = array(gTxt('page_name_invalid'), E_ERROR);
 420                      $save_error = true;
 421                  }
 422              } else {
 423                  if (safe_update('txp_page',
 424                          "user_html = '$html', name = '$safe_newname', skin = '$safe_skin'",
 425                          "name = '$safe_name' AND skin = '$safe_skin'")) {
 426                      safe_update('txp_section', "page = '$safe_newname'", "page='$safe_name' AND skin='$safe_skin'");
 427                      safe_update('txp_section', "dev_page = '$safe_newname'", "dev_page='$safe_name' AND dev_skin='$safe_skin'");
 428                      set_pref('last_page_saved', $newname, 'page', PREF_HIDDEN, 'text_input', 0, PREF_PRIVATE);
 429                      update_lastmod('page_saved', compact('newname', 'name', 'html'));
 430  
 431                      $message = gTxt('page_updated', array('{list}' => $newname));
 432  
 433                      // If page name has been auto-sanitized, throw a warning.
 434                      if ($passedName !== $name) {
 435                          $message = array($message, E_WARNING);
 436                      }
 437  
 438                      callback_event('page_updated', '', 0, $name, $newname);
 439                  } else {
 440                      $message = array(gTxt('page_save_failed'), E_ERROR);
 441                      $save_error = true;
 442                  }
 443              }
 444          }
 445      }
 446  
 447      if ($save_error === true) {
 448          $_POST['save_error'] = '1';
 449      } else {
 450          if (gps('export')) {
 451              $instance->setNames(array($newname))->export()->getMessage();
 452          }
 453  
 454          callback_event('page_saved', '', 0, $name, $newname);
 455      }
 456  
 457      page_edit($message, ($app_mode === 'async') ? true : false);
 458  }
 459  
 460  /**
 461   * Directs requests to page_edit() armed with a 'page_new' step.
 462   *
 463   * @see page_edit()
 464   */
 465  
 466  function page_new()
 467  {
 468      page_edit();
 469  }
 470  
 471  /**
 472   * Return a list of tag builder tags.
 473   *
 474   * @return HTML
 475   */
 476  
 477  function page_tagbuild()
 478  {
 479      $listActions = graf(
 480          href('<span class="ui-icon ui-icon-arrowthickstop-1-s"></span> '.gTxt('expand_all'), '#', array(
 481              'class'         => 'txp-expand-all',
 482              'aria-controls' => 'tagbuild_links',
 483          )).
 484          href('<span class="ui-icon ui-icon-arrowthickstop-1-n"></span> '.gTxt('collapse_all'), '#', array(
 485              'class'         => 'txp-collapse-all',
 486              'aria-controls' => 'tagbuild_links',
 487          )), array('class' => 'txp-actions')
 488      );
 489  
 490      // Format of each entry is popTagLink -> array ( gTxt() string, class/ID).
 491      $tagbuild_items = array(
 492          'page_article'     => array('page_article_hed', 'article-tags'),
 493          'page_article_nav' => array('page_article_nav_hed', 'article-nav-tags'),
 494          'page_nav'         => array('page_nav_hed', 'nav-tags'),
 495          'page_xml'         => array('page_xml_hed', 'xml-tags'),
 496          'page_misc'        => array('page_misc_hed', 'misc-tags'),
 497          'page_file'        => array('page_file_hed', 'file-tags'),
 498      );
 499  
 500      $tagbuild_links = '';
 501  
 502      foreach ($tagbuild_items as $tb => $item) {
 503          $tagbuild_links .= wrapRegion($item[1].'_group', taglinks($tb), $item[1], $item[0], 'page_'.$item[1]);
 504      }
 505  
 506      return $listActions.$tagbuild_links;
 507  }
 508  
 509  /**
 510   * Renders a list of tag builder options.
 511   *
 512   * @param  string $type
 513   * @return HTML
 514   * @access private
 515   * @see    popTagLinks()
 516   */
 517  
 518  function taglinks($type)
 519  {
 520      return popTagLinks($type);
 521  }
 522  
 523  /**
 524   * Renders page name field.
 525   *
 526   * @param  array  $rs Record set
 527   * @return string HTML
 528   */
 529  
 530  function page_partial_name($rs)
 531  {
 532      $name = $rs['name'];
 533      $skin = $rs['skin'];
 534      $nameRegex = '^(?=[^.\s])[^\x00-\x1f\x22\x26\x27\x2a\x2f\x3a\x3c\x3e\x3f\x5c\x7c\x7f]+';
 535  
 536      $titleblock = inputLabel(
 537          'new_page',
 538          fInput('text', array('name' => 'newname', 'pattern' => $nameRegex), $name, 'input-medium', '', '', INPUT_MEDIUM, '', 'new_page', false, true),
 539          'page_name',
 540          array('', 'instructions_page_name'),
 541          array('class' => 'txp-form-field name')
 542      );
 543  
 544      if ($name === '') {
 545          $titleblock .= hInput('savenew', 'savenew');
 546      } else {
 547          $titleblock .= hInput('name', $name);
 548      }
 549  
 550      $titleblock .= hInput('skin', $skin).
 551          eInput('page').sInput('page_save');
 552  
 553      return $titleblock;
 554  }
 555  
 556  /**
 557   * Renders page name value.
 558   *
 559   * @param  array  $rs Record set
 560   * @return string HTML
 561   */
 562  
 563  function page_partial_name_value($rs)
 564  {
 565      return $rs['name'];
 566  }
 567  
 568  /**
 569   * Renders page textarea field.
 570   *
 571   * @param  array  $rs Record set
 572   * @return string HTML
 573   */
 574  
 575  function page_partial_template($rs)
 576  {
 577      global $event;
 578  
 579      $out = inputLabel(
 580          'html',
 581          '<textarea class="code" id="html" name="html" cols="'.INPUT_LARGE.'" rows="'.TEXTAREA_HEIGHT_LARGE.'" dir="ltr">'.txpspecialchars($rs['html']).'</textarea>',
 582          array(
 583              'page_code',
 584              n.span(
 585                  (has_privs('tag')
 586                      ? href(
 587                          span(null, array('class' => 'ui-icon ui-extra-icon-code')).' '.gTxt('tagbuilder'),
 588                          array('event' => 'tag', 'panel' => $event),
 589                          array('class' => 'txp-tagbuilder-dialog')
 590                      )
 591                      : ''
 592                  ),
 593                  array('class' => 'txp-textarea-options')
 594              )
 595          ),
 596          array('', 'instructions_page_code'),
 597          array('class' => 'txp-form-field template'),
 598          array('div', 'div')
 599      );
 600  
 601      return $out;
 602  }

title

Description

title

Description

title

Description

title

title

Body