Textpattern | PHP Cross Reference | Content Management Systems |
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
Body
title
Description
Body
title
Description
Body
title
Body
title