Textpattern PHP Cross Reference Content Management Systems

Source: /textpattern/vendors/Textpattern/Skin/CommonBase.php - 842 lines - 22635 bytes - Summary - Text - Print

Description: Common Base Extended by Skin and AssetBase.

   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   * Common Base
  26   *
  27   * Extended by Skin and AssetBase.
  28   *
  29   * @since   4.7.0
  30   * @package Skin
  31   */
  32  
  33  namespace Textpattern\Skin;
  34  
  35  abstract class CommonBase implements CommonInterface
  36  {
  37      /**
  38       * Class related textpack string (usually the event name).
  39       *
  40       * @var string 'skin', 'page', 'form', 'css', etc.
  41       * @see getEvent().
  42       */
  43  
  44      protected $event;
  45  
  46      /**
  47       * Skin/templates directory/files name(s) pattern.
  48       *
  49       * @var string Regex without delimiters.
  50       * @see getNamePattern().
  51       */
  52  
  53      protected static $namePattern = '[a-zA-Z0-9_\-\.]{0,63}';
  54  
  55      /**
  56       * Installed.
  57       *
  58       * @var array Associative array of skin names and their titles.
  59       * @see setUploaded(), getUploaded().
  60       */
  61  
  62      protected $installed;
  63  
  64      /**
  65       * Class related skin/template names to work with.
  66       *
  67       * @var array Names.
  68       * @see setNames(), getNames().
  69       */
  70  
  71      protected $names;
  72  
  73      /**
  74       * Class related file extension.
  75       *
  76       * @see getExtension().
  77       */
  78  
  79      protected static $extension = 'txp';
  80  
  81      /**
  82       * Asset mimetypes.
  83       *
  84       * @var array Associative array of 'extension' => 'mediatype'
  85       */
  86  
  87      protected static $mimeTypes = array();
  88  
  89      /**
  90       * Skin/template name to work with.
  91       *
  92       * @var string Name.
  93       * @see setName(), getName().
  94       */
  95  
  96      protected $name;
  97  
  98      /**
  99       * Skin/template related infos.
 100       *
 101       * @var array Associative array of class related table main fields and their values.
 102       * @see setInfos(), getInfos().
 103       */
 104  
 105      protected $infos;
 106  
 107      /**
 108       * Skin/template name used as the base for update or duplication.
 109       *
 110       * @var string Name.
 111       * @see setBase(), getBase().
 112       */
 113  
 114      protected $base;
 115  
 116      /**
 117       * Storage for admin related method results.
 118       *
 119       * @var array Associative array of 'success', 'warning' and 'error'
 120       *            textpack related items and their related '{list}' parameters.
 121       * @see mergeResult(), getResults(), getMessage().
 122       */
 123  
 124      protected $results = array(
 125          'success' => array(),
 126          'warning' => array(),
 127          'error'   => array(),
 128      );
 129  
 130      /**
 131       * Constructor
 132       */
 133  
 134      public function __construct()
 135      {
 136          $this->setEvent();
 137      }
 138  
 139      /**
 140       * Get the class related Database table name
 141       *
 142       * @return string Table name.
 143       */
 144  
 145      protected function getTable()
 146      {
 147          return 'txp_'.$this->getEvent();
 148      }
 149  
 150      /**
 151       * $event property setter.
 152       *
 153       * @return $this The current object (chainable).
 154       */
 155  
 156      protected function setEvent()
 157      {
 158          $this->event = strtolower((new \ReflectionClass($this))->getShortName());
 159  
 160          return $this;
 161      }
 162  
 163      /**
 164       * $event property getter.
 165       *
 166       * @return string $this->event Class related textpack string (usually the event name).
 167       */
 168  
 169      public function getEvent()
 170      {
 171          return $this->event;
 172      }
 173  
 174      /**
 175       * $namePattern property getter
 176       *
 177       * @return string self::$namePattern Skin/templates directory/files name(s) pattern.
 178       */
 179  
 180      protected static function getNamePattern()
 181      {
 182          return self::$namePattern;
 183      }
 184  
 185      /**
 186       * $mimeTypes property getter.
 187       *
 188       * @return $this->mimeTypes The asset related mimeTypes array.
 189       */
 190  
 191      public function getMimeTypes()
 192      {
 193          return static::$mimeTypes;
 194      }
 195  
 196      /**
 197       * Whether a $name property related value is a valid directory name or not.
 198       *
 199       * @return bool FALSE on error.
 200       */
 201  
 202      protected function isExportable($name = null)
 203      {
 204          return preg_match('#^'.self::getNamePattern().'$#', $this->getName());
 205      }
 206  
 207      /**
 208       * Sanitizes a string for use in a theme template's name.
 209       *
 210       * Just runs sanitizeForPage() followed by sanitizeForFile(),
 211       * then limits the number of characters to 63.
 212       *
 213       * @param  string $text The string
 214       * @return string
 215       */
 216  
 217      public static function sanitize($text)
 218      {
 219          $out = sanitizeForFile(sanitizeForPage($text));
 220  
 221          return \Txp::get('\Textpattern\Type\StringType', $out)->substring(0, 63)->getString();
 222      }
 223  
 224      /**
 225       * $names property setter/sanitizer.
 226       *
 227       * @param  array $names Multiple skin or template names to work with related methods.
 228       * @return object $this  The current class object (chainable).
 229       */
 230  
 231      public function setNames($names = null)
 232      {
 233          if ($names === null) {
 234              $this->names = array();
 235          } else {
 236              $parsed = array();
 237  
 238              foreach ($names as $name) {
 239                  $parsed[] = static::sanitize($name);
 240              }
 241  
 242              $this->names = $parsed;
 243          }
 244  
 245          return $this;
 246      }
 247  
 248      /**
 249       * $names property getter.
 250       *
 251       * @return array Skin or template sanitized names.
 252       */
 253  
 254      protected function getNames()
 255      {
 256          return $this->names;
 257      }
 258  
 259      /**
 260       * $name property setter.
 261       *
 262       * @param  array $name Single skin or template name to work with related methods.
 263       *                     Takes the '_last_saved' or '_editing' related preference
 264       *                     value if null.
 265       * @return object $this The current class object (chainable).
 266       */
 267  
 268      public function setName($name = null)
 269      {
 270          $this->name = $name === null ? $this->getEditing() : static::sanitize($name);
 271  
 272          return $this;
 273      }
 274  
 275      /**
 276       * $name property getter.
 277       *
 278       * @return string Sanitized skin or template name.
 279       */
 280  
 281      protected function getName()
 282      {
 283          return $this->name;
 284      }
 285  
 286      /**
 287       * $infos property getter/parser.
 288       *
 289       * @param  bool $safe Whether to get the property value
 290       *                    as a safe SET query clause.
 291       * @return mixed       The $infos property value or the related SET clause.
 292       */
 293  
 294      protected function getInfos($safe = false)
 295      {
 296          if ($safe) {
 297              $infoQuery = array();
 298  
 299              foreach ($this->infos as $col => $value) {
 300                  $infoQuery[] = $col." = '".doSlash($value)."'";
 301              }
 302  
 303              return implode(', ', $infoQuery);
 304          }
 305  
 306          return $this->infos;
 307      }
 308  
 309      /**
 310       * $base property setter.
 311       *
 312       * @param object $this The current object (chainable).
 313       */
 314  
 315      public function setBase($name)
 316      {
 317          $this->base = static::sanitize($name);
 318  
 319          return $this;
 320      }
 321  
 322      /**
 323       * $base property getter.
 324       *
 325       * @return string Sanitized skin or template base name.
 326       */
 327  
 328      protected function getBase()
 329      {
 330          return $this->base;
 331      }
 332  
 333      /**
 334       * Get the 'synchronize' preference value.
 335       *
 336       * @param  string $name Pref name.
 337       * @return bool
 338       */
 339  
 340      protected function getSyncPref($name)
 341      {
 342          global $prefs;
 343  
 344          $value = get_pref($name, true);
 345  
 346          if (!isset($prefs[$name])) {
 347              $prefs[$name] = $value;
 348          }
 349  
 350          return $value;
 351      }
 352  
 353      /**
 354       * Switch the 'synchronize' preference value
 355       * and its related global variable.
 356       *
 357       * @param  string $name Pref name.
 358       * @return bool         FALSE on error.
 359       */
 360  
 361      protected function switchSyncPref($name)
 362      {
 363          global $prefs;
 364  
 365          return set_pref(
 366              $name,
 367              $prefs[$name] = !$prefs[$name],
 368              'skin',
 369              PREF_HIDDEN,
 370              'text_input',
 371              0,
 372              PREF_PRIVATE
 373          );
 374      }
 375  
 376      /**
 377       * Merge a result into the $results property array.
 378       *
 379       * @param  string $txtItem Textpack related item.
 380       * @param  mixed  $list    A name or an array of names associated with the result
 381       *                         to build the txtItem related '{list}'.
 382       *                         List values can be grouped like so: array($skin => $templates)
 383       * @param  string $status  'success'|'warning'|'error'.
 384       * @return object $this    The current class object (chainable).
 385       */
 386  
 387      protected function mergeResult($txtItem, $list, $status = null)
 388      {
 389          !is_string($list) or $list = array($list);
 390          $status = in_array($status, array('success', 'warning', 'error')) ? $status : 'error';
 391  
 392          $this->results = array_merge_recursive(
 393              $this->getResults(),
 394              array($status => array($txtItem => $list))
 395          );
 396  
 397          return $this;
 398      }
 399  
 400      /**
 401       * $results property getter.
 402       *
 403       * @param  array $status Array of results related status ('success', 'warning', 'error') to filter the output.
 404       * @return array         Associative array of status textpack related items
 405       *                       and their related '{list}' parameters.
 406       */
 407  
 408      protected function getResults($status = null)
 409      {
 410          if ($status === null) {
 411              return $this->results;
 412          } else {
 413              $results = array();
 414  
 415              foreach ($status as $severity) {
 416                  $results[$severity] = $this->results[$severity];
 417              }
 418  
 419              return $results;
 420          }
 421      }
 422  
 423      /**
 424       * Get the $results property value as a message to display in the admin tabs.
 425       *
 426       * @return mixed Message or array containing the message
 427       *               and its related user notice constant.
 428       */
 429  
 430      public function getMessage()
 431      {
 432          $message = array();
 433  
 434          $thisResults = $this->getResults();
 435  
 436          foreach ($this->getResults() as $status => $results) {
 437              foreach ($results as $txtItem => $listGroup) {
 438                  $list = array();
 439  
 440                  if (isset($listGroup[0])) {
 441                      $list = $listGroup;
 442                  } else {
 443                      foreach ($listGroup as $group => $names) {
 444                          if (count($listGroup) > 1) {
 445                              $list[] = '('.$group.') '.implode(', ', $names);
 446                          } else {
 447                              $list[] = implode(', ', $names);
 448                          }
 449                      }
 450                  }
 451  
 452                  $message[] = gTxt($txtItem, array('{list}' => implode(', ', $list)));
 453              }
 454          }
 455  
 456          $message = implode('<br>', $message);
 457  
 458          if ($thisResults['success'] && ($thisResults['warning'] || $thisResults['error'])) {
 459              $severity = 'E_WARNING';
 460          } elseif ($thisResults['warning']) {
 461              $severity = 'E_WARNING';
 462          } elseif ($thisResults['error']) {
 463              $severity = 'E_ERROR';
 464          } else {
 465              $severity = '';
 466          }
 467  
 468          return $severity ? array($message, constant($severity)) : $message;
 469      }
 470  
 471      /**
 472       * $extension property getter.
 473       *
 474       * @return string static::$extension.
 475       */
 476  
 477      protected static function getExtension()
 478      {
 479          return static::$extension;
 480      }
 481  
 482      /**
 483       * Get files from the $dir property value related directory.
 484       *
 485       * @param  array $names    Optional filenames to filter the result.
 486       * @param  int   $maxDepth Optional RecursiveIteratorIterator related property value (default = -1 infinite).
 487       * @return object           Collection of file objects.
 488       */
 489  
 490      protected function getFiles($names = null, $maxDepth = null)
 491      {
 492          $extensions = implode('|', array_keys(static::$mimeTypes + array(self::getExtension() => null)));
 493          $filter = $names ? $names : '#^'.self::getNamePattern().'\.'."(?:$extensions)".'$#';
 494          $files = \Txp::get('Textpattern\Iterator\RecDirIterator', $this->getDirPath());
 495          $filter = \Txp::get('Textpattern\Iterator\RecFilterIterator', $files, $filter);
 496          $filteredFiles = \Txp::get('Textpattern\Iterator\RecIteratorIterator', $filter);
 497          $maxDepth !== null or $filteredFiles->setMaxDepth($maxDepth);
 498  
 499          return $filteredFiles;
 500      }
 501  
 502      /**
 503       * Insert a row into the $table property value related table.
 504       *
 505       * @param  string $set   Optional SET clause.
 506       *                       Builds the clause from the $infos (+ $skin) property value(s) if null.
 507       * @param  bool   $debug Dump query
 508       * @return bool          FALSE on error.
 509       */
 510  
 511      public function createRow($set = null, $debug = false)
 512      {
 513          if ($set === null) {
 514              $set = $this->getInfos(true);
 515  
 516              if (property_exists($this, 'skin')) {
 517                  $set .= " skin = '".doSlash($this->getSkin()->getName())."'";
 518              }
 519          }
 520  
 521          return safe_insert($this->getTable(), $set, $debug);
 522      }
 523  
 524      /**
 525       * Update the $table property value related table.
 526       *
 527       * @param  string $set   Optional SET clause.
 528       *                       Builds the clause from the $infos property value if null.
 529       * @param  string $where Optional WHERE clause.
 530       *                       Builds the clause from the $base (+ $skin) property value(s) if null.
 531       * @param  bool   $debug Dump query
 532       * @return bool          FALSE on error.
 533       */
 534  
 535      public function updateRow($set = null, $where = null, $debug = false)
 536      {
 537          $set !== null or $set = $this->getInfos(true);
 538  
 539          if ($where === null) {
 540              $where = '';
 541              $base = $this->getBase();
 542  
 543              if ($base) {
 544                  $where = "name = '".doSlash($base)."'";
 545              }
 546  
 547              if (property_exists($this, 'skin')) {
 548                  $skin = $this->getSkin();
 549                  $skinName = $skin ? $skin->getName() : '';
 550  
 551                  if ($skinName) {
 552                      !$where or $where .= ' AND ';
 553                      $where .= "skin = '".doSlash($skinName)."'";
 554                  }
 555              }
 556  
 557              $where or $where = '1 = 1';
 558          }
 559  
 560          return safe_update($this->getTable(), $set, $where, $debug);
 561      }
 562  
 563      /**
 564       * Get a row field from the $table property value related table.
 565       *
 566       * @param  string $thing Optional SELECT clause.
 567       *                       Uses 'name' if null.
 568       * @param  string $where Optional WHERE clause.
 569       *                       Builds the clause from the $name (+ $skin) property value(s) if null.
 570       * @param  bool   $debug Dump query
 571       * @return mixed         The Field or FALSE on error.
 572       */
 573  
 574      public function getField($thing = null, $where = null, $debug = false)
 575      {
 576          $thing !== null or $thing = 'name';
 577  
 578          if ($where === null) {
 579              $where = '';
 580              $name = $this->getName();
 581  
 582              if ($name) {
 583                  $where .= "name = '".doSlash($name)."'";
 584              }
 585  
 586              if (property_exists($this, 'skin')) {
 587                  $skin = $this->getSkin();
 588                  $skinName = $skin ? $skin->getName() : '';
 589  
 590                  if ($skinName) {
 591                      !$where or $where .= ' AND ';
 592                      $where .= "skin = '".doSlash($skinName)."'";
 593                  }
 594              }
 595  
 596              $where or $where = '1 = 1';
 597          }
 598  
 599          return safe_field($thing, $this->getTable(), $where, $debug);
 600      }
 601  
 602      /**
 603       * Delete rows from the $table property value related table.
 604       *
 605       * @param  string $where Optional WHERE clause.
 606       *                       Builds the clause from the $names (+ $skin) property value(s) if null.
 607       * @param  bool   $debug Dump query
 608       * @return bool          FALSE on error.
 609       */
 610  
 611      public function deleteRows($where = null, $debug = false)
 612      {
 613          if ($where === null) {
 614              $where = '';
 615              $names = $this->getNames();
 616  
 617              if ($names) {
 618                  $where .= "name IN ('".implode("', '", array_map('doSlash', $names))."')";
 619              }
 620  
 621              if (property_exists($this, 'skin')) {
 622                  $skin = $this->getSkin();
 623                  $skinName = $skin ? $skin->getName() : '';
 624  
 625                  if ($skinName) {
 626                      !$where or $where .= ' AND ';
 627                      $where .= "skin = '".doSlash($skinName)."'";
 628                  }
 629              }
 630  
 631              $where or $where = '1 = 1';
 632          }
 633  
 634          return safe_delete($this->getTable(), $where, $debug);
 635      }
 636  
 637      /**
 638       * Count rows in the $table property value related table.
 639       *
 640       * @param  string $where The where clause.
 641       * @param  bool   $debug Dump query
 642       * @return mixed         Number of rows or FALSE on error
 643       */
 644  
 645      public function countRows($where = null, $debug = false)
 646      {
 647          return safe_count($this->getTable(), ($where === null ? '1 = 1' : $where), $debug);
 648      }
 649  
 650      /**
 651       * Get a row from the $table property value related table as an associative array.
 652       *
 653       * @param  string $things Optional SELECT clause.
 654       *                        Uses '*' (all) if null.
 655       * @param  string $where  Optional WHERE clause.
 656       *                        Builds the clause from the $name (+ $skin) property value(s) if null.
 657       * @param  bool   $debug  Dump query
 658       * @return bool           Array.
 659       */
 660  
 661      public function getRow($things = null, $where = null, $debug = false)
 662      {
 663          $things !== null or $things = '*';
 664  
 665          if ($where === null) {
 666              $where = '';
 667              $name = $this->getName();
 668  
 669              if ($name) {
 670                  $where .= "name = '".doSlash($name)."'";
 671              }
 672  
 673              if (property_exists($this, 'skin')) {
 674                  $skin = $this->getSkin();
 675                  $skinName = $skin ? $skin->getName() : '';
 676  
 677                  if ($skinName) {
 678                      !$where or $where .= ' AND ';
 679                      $where .= "skin = '".doSlash($skinName)."'";
 680                  }
 681              }
 682  
 683              $where or $where = '1=1';
 684          }
 685  
 686          return safe_row($things, $this->getTable(), $where, $debug);
 687      }
 688  
 689      /**
 690       * Get rows from the $table property value related table as an associative array.
 691       *
 692       * @param  string $thing Optional SELECT clause.
 693       *                       Uses '*' (all) if null.
 694       * @param  string $where Optional WHERE clause (default: "name = '".doSlash($this->getName())."'")
 695       *                       Builds the clause from the $names (+ $skin) property value(s) if null.
 696       * @param  bool   $debug Dump query
 697       * @return array         (Empty on error)
 698       */
 699  
 700      public function getRows($things = null, $where = null, $debug = false)
 701      {
 702          $things !== null or $things = '*';
 703  
 704          if ($where === null) {
 705              $where = '';
 706              $names = $this->getNames();
 707  
 708              if ($names) {
 709                  $where .= "name IN ('".implode("', '", array_map('doSlash', $names))."')";
 710              }
 711  
 712              if (property_exists($this, 'skin')) {
 713                  $skin = $this->getSkin();
 714                  $skinName = $skin ? $skin->getName() : '';
 715  
 716                  if ($skinName) {
 717                      !$where or $where .= ' AND ';
 718                      $where .= "skin = '".doSlash($skinName)."'";
 719                  }
 720              }
 721  
 722              $where or $where = '1=1';
 723          }
 724  
 725          $rs = safe_rows_start($things, $this->getTable(), $where, $debug);
 726  
 727          if ($rs) {
 728              $rows = array();
 729  
 730              while ($row = nextRow($rs)) {
 731                  $rows[] = $row;
 732              }
 733  
 734              return $rows;
 735          }
 736  
 737          return array();
 738      }
 739  
 740      /**
 741       * Get the skin name used by the default section.
 742       *
 743       * @return mixed Skin name or FALSE on error.
 744       */
 745  
 746      protected function getDefault()
 747      {
 748          return safe_field($this->getEvent(), 'txp_section', 'name = "default"');
 749      }
 750  
 751      /**
 752       * $installed property setter.
 753       *
 754       * @param array $this->installed.
 755       */
 756  
 757      protected function setInstalled()
 758      {
 759          $things = 'name';
 760          $isAsset = property_exists($this, 'skin');
 761          $thing = $isAsset ? 'skin' : 'title, version';
 762          $things .= ', '.$thing;
 763  
 764          $rows = $this->getRows($things, '1 ORDER BY name');
 765  
 766          $this->installed = array();
 767  
 768          foreach ($rows as $row) {
 769              if ($isAsset) {
 770                  $this->installed[$row[$thing]][] = $row['name'];
 771              } else {
 772                  $this->installed[$row['name']] = $row['title'] . ' ('.$row['version'].')';
 773              }
 774          }
 775  
 776          return $this->getInstalled();
 777      }
 778  
 779      /**
 780       * $installed property getter.
 781       *
 782       * @return array $this->installed.
 783       */
 784  
 785      public function getInstalled()
 786      {
 787          return $this->installed === null ? $this->setInstalled() : $this->installed;
 788      }
 789  
 790      /**
 791       * Whether a skin/template is installed or not.
 792       *
 793       * @param  string $name Skin name (default: $this->getName()).
 794       * @return bool
 795       */
 796  
 797      protected function isInstalled($name = null)
 798      {
 799          $isAsset = property_exists($this, 'skin');
 800          $name !== null or $name = $this->getName();
 801  
 802          if ($this->installed === null) {
 803              $isInstalled = (bool) $this->getField('name', "name = '".$name."'");
 804          } else {
 805              if ($isAsset) {
 806                  $isInstalled = false;
 807                  $installed = $this->getInstalled();
 808  
 809                  foreach ($installed as $skin) {
 810                      if (in_array($name, array_keys($installed))) {
 811                          $isInstalled = $skin;
 812                      }
 813                  }
 814              } else {
 815                  $isInstalled = in_array($name, array_keys($this->getInstalled()));
 816              }
 817          }
 818  
 819          return $isInstalled;
 820      }
 821  
 822      /**
 823       * Whether a directory is empty or not.
 824       *
 825       * @param  string $path The directory path
 826       * @return mixed        NULL if the directory is not readable (or does not exist),
 827       *                      TRUE if empty, otherwise, FALSE.
 828       */
 829      protected static function isDirEmpty($path)
 830      {
 831          if (!is_readable($path)) {
 832              return null;
 833          }
 834          $handle = opendir($path);
 835          while (false !== ($entry = readdir($handle))) {
 836              if ($entry != "." && $entry != "..") {
 837                  return false;
 838              }
 839          }
 840          return true;
 841      }
 842  }

title

Description

title

Description

title

Description

title

title

Body