Textpattern PHP Cross Reference Content Management Systems

Source: /textpattern/include/txp_css.php - 504 lines - 14911 bytes - Summary - Text - Print

Description: Styles 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   * Styles panel.
  26   *
  27   * @package Admin\CSS
  28   */
  29  
  30  use Textpattern\Skin\Skin;
  31  use Textpattern\Skin\Css;
  32  
  33  if (!defined('txpinterface')) {
  34      die('txpinterface is undefined.');
  35  }
  36  
  37  if ($event == 'css') {
  38      require_privs('css');
  39  
  40      $instance = Txp::get('Textpattern\Skin\Css');
  41  
  42      bouncer($step, array(
  43          'pour'            => false,
  44          'css_save'        => true,
  45          'css_delete'      => true,
  46          'css_edit'        => false,
  47          'css_skin_change' => true,
  48      ));
  49  
  50      switch (strtolower($step)) {
  51          case '':
  52              css_edit();
  53              break;
  54          case 'pour':
  55              css_edit();
  56              break;
  57          case 'css_save':
  58              css_save();
  59              break;
  60          case 'css_delete':
  61              css_delete();
  62              break;
  63          case 'css_edit':
  64              css_edit();
  65              break;
  66          case "css_skin_change":
  67              Txp::get('Textpattern\Skin\Css')->selectEdit();
  68              css_edit();
  69              break;
  70      }
  71  }
  72  
  73  /**
  74   * Renders a list of stylesheets.
  75   *
  76   * @param  array $current Current record set of the edited sheet
  77   * @return string HTML
  78   */
  79  
  80  function css_list($current)
  81  {
  82      $out = array();
  83      $safe_skin = doSlash($current['skin']);
  84      $protected = safe_column("DISTINCT css", 'txp_section', "skin = '$safe_skin' OR dev_skin = '$safe_skin'");
  85  
  86      $criteria = "skin = '$safe_skin'";
  87      $criteria .= callback_event('admin_criteria', 'css_list', 0, $criteria);
  88  
  89      $rs = safe_rows_start("name", 'txp_css', $criteria . ' ORDER BY name');
  90  
  91      while ($a = nextRow($rs)) {
  92          extract($a);
  93  
  94          $active = ($current['name'] === $name);
  95          $edit = eLink('css', '', 'name', $name, $name);
  96  
  97          if (!array_key_exists($name, $protected)) {
  98              $edit .= dLink('css', 'css_delete', 'name', $name);
  99          }
 100  
 101          $out[] = tag(n.$edit.n, 'li', array('class' => $active ? 'active' : ''));
 102      }
 103  
 104      $list = wrapGroup('all_styles_css', tag(join(n, $out), 'ul', array('class' => 'switcher-list')), gTxt('all_stylesheets'));
 105  
 106      return n.tag($list, 'div', array(
 107              'id'    => 'all_styles',
 108              'role'  => 'region',
 109          )
 110      );
 111  }
 112  
 113  /**
 114   * The main stylesheet editor panel.
 115   *
 116   * @param string|array $message          The activity message
 117   * @param bool         $refresh_partials Whether to refresh partial contents
 118   */
 119  
 120  function css_edit($message = '', $refresh_partials = false)
 121  {
 122      global $instance, $event, $step;
 123  
 124      /*
 125      $partials is an array of:
 126      $key => array (
 127          'mode' => {PARTIAL_STATIC | PARTIAL_VOLATILE | PARTIAL_VOLATILE_VALUE},
 128          'selector' => $DOM_selector or array($selector, $fragment) of $DOM_selectors,
 129           'cb' => $callback_function,
 130           'html' => $return_value_of_callback_function (need not be initialised here)
 131      )
 132      */
 133      $partials = array(
 134          // Stylesheet list.
 135          'list' => array(
 136              'mode'     => PARTIAL_VOLATILE,
 137              'selector' => '#all_styles',
 138              'cb'       => 'css_list',
 139          ),
 140          // Name field.
 141          'name' => array(
 142              'mode'     => PARTIAL_VOLATILE,
 143              'selector' => 'div.name',
 144              'cb'       => 'css_partial_name',
 145          ),
 146          // Name value.
 147          'name_value'  => array(
 148              'mode'     => PARTIAL_VOLATILE_VALUE,
 149              'selector' => '#new_style,#main_content input[name=name]',
 150              'cb'       => 'css_partial_name_value',
 151          ),
 152          // Textarea.
 153          'css' => array(
 154              'mode'     => PARTIAL_STATIC,
 155              'selector' => 'div.css',
 156              'cb'       => 'css_partial_css',
 157          ),
 158      );
 159  
 160      extract(array_map('assert_string', gpsa(array(
 161          'copy',
 162          'save_error',
 163          'savenew',
 164          'skin',
 165      ))));
 166  
 167      $default_name = safe_field("css", 'txp_section', "name = 'default'");
 168  
 169      $name = assert_string(gps('name'));
 170      $newname = Css::sanitize(assert_string(gps('newname')));
 171      $skin = ($skin !== '') ? $skin : null;
 172      $class = 'async';
 173  
 174      $thisSkin = Txp::get('Textpattern\Skin\Skin');
 175      $skin = $thisSkin->setName($skin)->setEditing();
 176  
 177      if ($step == 'css_delete' || empty($name) && $step != 'pour' && !$savenew) {
 178          $name = get_pref('last_css_saved', $default_name);
 179      } elseif ((($copy || $savenew) && $newname) && !$save_error) {
 180          $name = $newname;
 181      } elseif ((($newname && ($newname != $name)) || $step === 'pour') && !$save_error) {
 182          $name = $newname;
 183          $class = '';
 184      } elseif ($savenew && $save_error) {
 185          $class = '';
 186      }
 187  
 188      if (!$save_error) {
 189          $thecss = safe_field('css', 'txp_css', "name='".doSlash($name)."' AND skin='" . doSlash($skin) . "'");
 190      } else {
 191          $thecss = gps('css');
 192      }
 193  
 194      $actionsExtras = '';
 195  
 196      if ($name) {
 197          $actionsExtras .= sLink('css', 'pour', '<span class="ui-icon ui-extra-icon-new-document"></span> '.gTxt('create_css'), 'txp-new')
 198          .href('<span class="ui-icon ui-icon-copy"></span> '.gTxt('duplicate'), '#', array(
 199              'class'     => 'txp-clone',
 200              'data-form' => 'style_form',
 201          ));
 202      }
 203  
 204      $actions = graf(
 205          $actionsExtras,
 206          array('class' => 'txp-actions txp-actions-inline')
 207      );
 208  
 209      $skinBlock = n.$instance->setSkin($thisSkin)->getSelectEdit();
 210  
 211      $buttons = graf(
 212          (!is_writable($instance->getDirPath()) ? '' :
 213              span(
 214                  checkbox2('export', gps('export'), 0, 'export').
 215                  n.tag(gTxt('export_to_disk'), 'label', array('for' => 'export'))
 216              , array('class' => 'txp-save-export'))
 217          ).n.
 218          tag_void('input', array(
 219              'class'  => 'publish',
 220              'type'   => 'submit',
 221              'method' => 'post',
 222              'value'  =>  gTxt('save'),
 223          )), ' class="txp-save"'
 224      );
 225  
 226      $rs = array(
 227          'name'    => $name,
 228          'newname' => $newname,
 229          'default' => $default_name,
 230          'skin'    => $skin,
 231          'css'     => $thecss,
 232          );
 233  
 234      // Get content for volatile partials.
 235      $partials = updatePartials($partials, $rs, array(PARTIAL_VOLATILE, PARTIAL_VOLATILE_VALUE));
 236  
 237      if ($refresh_partials) {
 238          $response[] = announce($message);
 239          $response = array_merge($response, updateVolatilePartials($partials));
 240          send_script_response(join(";\n", $response));
 241  
 242          // Bail out.
 243          return;
 244      }
 245  
 246      // Get content for static partials.
 247      $partials = updatePartials($partials, $rs, PARTIAL_STATIC);
 248  
 249      pagetop(gTxt('tab_style'), $message);
 250  
 251      echo n.'<div class="txp-layout">'.
 252          n.tag(
 253              hed(gTxt('tab_style'), 1, array('class' => 'txp-heading')),
 254              'div', array('class' => 'txp-layout-1col')
 255          );
 256  
 257      // Styles create/switcher column.
 258      echo n.tag(
 259          $skinBlock.$partials['list']['html'].n,
 260          'div', array(
 261              'class' => 'txp-layout-4col-alt',
 262              'id'    => 'content_switcher',
 263              'role'  => 'region',
 264          )
 265      );
 266  
 267      // Styles code column.
 268      echo n.tag(
 269          form(
 270              $actions.
 271              $partials['name']['html'].
 272              $partials['css']['html'].
 273              $buttons, '', '', 'post', $class, '', 'style_form'),
 274          'div', array(
 275              'class' => 'txp-layout-4col-3span',
 276              'id'    => 'main_content',
 277              'role'  => 'region',
 278          )
 279      );
 280  
 281      echo n.'</div>'; // End of .txp-layout.
 282  }
 283  
 284  /**
 285   * Saves or clones a stylesheet.
 286   */
 287  
 288  function css_save()
 289  {
 290      global $instance, $app_mode;
 291  
 292      extract(doSlash(array_map('assert_string', psa(array(
 293          'savenew',
 294          'copy',
 295          'css',
 296          'skin',
 297      )))));
 298  
 299      $passedName = assert_string(ps('name'));
 300      $name = Css::sanitize($passedName);
 301      $newname = Css::sanitize(assert_string(ps('newname')));
 302  
 303      $skin = Txp::get('Textpattern\Skin\Skin')->setName($skin)->setEditing();
 304  
 305      $save_error = false;
 306      $message = '';
 307  
 308      if (!$newname) {
 309          $message = array(gTxt('css_name_required'), E_ERROR);
 310          $save_error = true;
 311      } else {
 312          if ($copy && ($name === $newname)) {
 313              $newname .= '_copy';
 314              $passedName = $name;
 315              $_POST['newname'] = $newname;
 316          }
 317  
 318          $safe_skin = doSlash($skin);
 319          $safe_name = doSlash($passedName);
 320          $safe_newname = doSlash($newname);
 321  
 322          $exists = safe_field('name', 'txp_css', "name = '$safe_newname' AND skin = '$safe_skin'");
 323  
 324          if (($newname !== $name) && $exists) {
 325              $message = array(gTxt('css_already_exists', array('{name}' => $newname)), E_ERROR);
 326  
 327              if ($savenew) {
 328                  $_POST['newname'] = '';
 329              }
 330  
 331              $save_error = true;
 332          } else {
 333              if ($savenew or $copy) {
 334                  if ($newname) {
 335                      if (safe_insert('txp_css', "name = '$safe_newname', css = '$css', skin = '$safe_skin'")) {
 336                          set_pref('last_css_saved', $newname, 'css', PREF_HIDDEN, 'text_input', 0, PREF_PRIVATE);
 337                          update_lastmod('css_created', compact('newname', 'name', 'css'));
 338  
 339                          $message = gTxt('css_created', array('{list}' => $newname));
 340  
 341                          // If css name has been auto-sanitized, throw a warning.
 342                          if ($passedName !== $name) {
 343                              $message = array($message, E_WARNING);
 344                          }
 345  
 346                          callback_event($copy ? 'css_duplicated' : 'css_created', '', 0, $name, $newname);
 347                      } else {
 348                          $message = array(gTxt('css_save_failed'), E_ERROR);
 349                          $save_error = true;
 350                      }
 351                  } else {
 352                      $message = array(gTxt('css_name_required'), E_ERROR);
 353                      $save_error = true;
 354                  }
 355              } else {
 356                  if (safe_update('txp_css',
 357                      "css = '$css', name = '$safe_newname', skin = '$safe_skin'",
 358                      "name = '$safe_name' AND skin = '$safe_skin'")) {
 359                      safe_update('txp_section', "css = '$safe_newname'", "css='$safe_name' AND skin='$safe_skin'");
 360                      safe_update('txp_section', "dev_css = '$safe_newname'", "dev_css='$safe_name' AND dev_skin='$safe_skin'");
 361                      set_pref('last_css_saved', $newname, 'css', PREF_HIDDEN, 'text_input', 0, PREF_PRIVATE);
 362                      update_lastmod('css_saved', compact('newname', 'name', 'css'));
 363  
 364                      $message = gTxt('css_updated', array('{list}' => $newname));
 365  
 366                      // If css name has been auto-sanitized, throw a warning.
 367                      if ($passedName !== $name) {
 368                          $message = array($message, E_WARNING);
 369                      }
 370  
 371                      callback_event('css_updated', '', 0, $name, $newname);
 372                  } else {
 373                      $message = array(gTxt('css_save_failed'), E_ERROR);
 374                      $save_error = true;
 375                  }
 376              }
 377          }
 378      }
 379  
 380      if ($save_error === true) {
 381          $_POST['save_error'] = '1';
 382      } else {
 383          if (gps('export')) {
 384              $instance->setNames(array($newname))->export()->getMessage();
 385          }
 386  
 387          callback_event('css_saved', '', 0, $name, $newname);
 388      }
 389  
 390      css_edit($message, ($app_mode === 'async') ? true : false);
 391  }
 392  
 393  /**
 394   * Deletes a stylesheet.
 395   */
 396  
 397  function css_delete()
 398  {
 399      global $prefs;
 400  
 401      $name = ps('name');
 402      $safe_name = doSlash($name);
 403      $skin = get_pref('skin_editing', 'default');
 404      $safe_skin = doSlash($skin);
 405  
 406      $count = safe_count('txp_section', "css = '$safe_name' AND (skin='$safe_skin' OR dev_skin='$safe_skin')");
 407      $message = '';
 408  
 409      if ($count) {
 410          $message = array(gTxt('css_used_by_section', array('{name}' => $name, '{count}' => $count)), E_ERROR);
 411      } else {
 412          if (safe_delete('txp_css', "name = '$safe_name' AND skin='$safe_skin'")) {
 413              callback_event('css_deleted', '', 0, compact('name', 'skin'));
 414              $message = gTxt('css_deleted', array('{list}' => $name));
 415              if ($name === get_pref('last_css_saved')) {
 416                  unset($prefs['last_css_saved']);
 417                  remove_pref('last_css_saved', 'css');
 418              }
 419          }
 420      }
 421  
 422      css_edit($message);
 423  }
 424  
 425  /**
 426   * Changes the skin in which styles are being edited.
 427   *
 428   * Keeps track of which skin is being edited from panel to panel.
 429   *
 430   * @param      string $skin Optional skin name. Read from GET/POST otherwise
 431   * @deprecated in 4.7.0
 432   */
 433  
 434  function css_skin_change($skin = null)
 435  {
 436      Txp::get('Textpattern\Skin\Css')->selectEdit($skin);
 437  
 438      return true;
 439  }
 440  
 441  /**
 442   * Renders css name field.
 443   *
 444   * @param  array  $rs Record set
 445   * @return string HTML
 446   */
 447  
 448  function css_partial_name($rs)
 449  {
 450      $name = $rs['name'];
 451      $skin = $rs['skin'];
 452      $nameRegex = '^(?=[^.\s])[^\x00-\x1f\x22\x26\x27\x2a\x2f\x3a\x3c\x3e\x3f\x5c\x7c\x7f]+';
 453  
 454      $titleblock = inputLabel(
 455          'new_style',
 456          fInput('text', array('name' => 'newname', 'pattern' => $nameRegex), $name, 'input-medium', '', '', INPUT_MEDIUM, '', 'new_style', false, true),
 457          'css_name',
 458          array('', 'instructions_style_name'),
 459          array('class' => 'txp-form-field name')
 460      );
 461  
 462      if ($name === '') {
 463          $titleblock .= hInput('savenew', 'savenew');
 464      } else {
 465          $titleblock .= hInput('name', $name);
 466      }
 467  
 468      $titleblock .= hInput('skin', $skin).
 469          eInput('css').sInput('css_save');
 470  
 471      return $titleblock;
 472  }
 473  
 474  /**
 475   * Renders css name value.
 476   *
 477   * @param  array  $rs Record set
 478   * @return string HTML
 479   */
 480  
 481  function css_partial_name_value($rs)
 482  {
 483      return $rs['name'];
 484  }
 485  
 486  /**
 487   * Renders css textarea field.
 488   *
 489   * @param  array  $rs Record set
 490   * @return string HTML
 491   */
 492  
 493  function css_partial_css($rs)
 494  {
 495      $out = inputLabel(
 496          'css',
 497          '<textarea class="code" id="css" name="css" cols="'.INPUT_LARGE.'" rows="'.TEXTAREA_HEIGHT_LARGE.'" dir="ltr">'.txpspecialchars($rs['css']).'</textarea>',
 498          'css_code',
 499          array('', 'instructions_style_code'),
 500          array('class' => 'txp-form-field css')
 501      );
 502  
 503      return $out;
 504  }

title

Description

title

Description

title

Description

title

title

Body