[ Index ] |
PHP Cross Reference of Joomla 4.2.2 documentation |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * Joomla! Content Management System 5 * 6 * @copyright (C) 2005 Open Source Matters, Inc. <https://www.joomla.org> 7 * @license GNU General Public License version 2 or later; see LICENSE.txt 8 */ 9 10 namespace Joomla\CMS\Document; 11 12 use Joomla\CMS\Cache\Cache; 13 use Joomla\CMS\Cache\CacheControllerFactoryAwareInterface; 14 use Joomla\CMS\Cache\CacheControllerFactoryAwareTrait; 15 use Joomla\CMS\Cache\CacheControllerFactoryInterface; 16 use Joomla\CMS\Factory as CmsFactory; 17 use Joomla\CMS\Filter\InputFilter; 18 use Joomla\CMS\Helper\ModuleHelper; 19 use Joomla\CMS\Language\Text; 20 use Joomla\CMS\Uri\Uri; 21 use Joomla\CMS\Utility\Utility; 22 use Joomla\Database\ParameterType; 23 use Joomla\Registry\Registry; 24 use UnexpectedValueException; 25 26 // phpcs:disable PSR1.Files.SideEffects 27 \defined('JPATH_PLATFORM') or die; 28 // phpcs:enable PSR1.Files.SideEffects 29 30 /** 31 * HtmlDocument class, provides an easy interface to parse and display a HTML document 32 * 33 * @since 1.7.0 34 */ 35 class HtmlDocument extends Document implements CacheControllerFactoryAwareInterface 36 { 37 use CacheControllerFactoryAwareTrait; 38 39 /** 40 * Array of Header `<link>` tags 41 * 42 * @var array 43 * @since 1.7.0 44 */ 45 public $_links = array(); 46 47 /** 48 * Array of custom tags 49 * 50 * @var array 51 * @since 1.7.0 52 */ 53 public $_custom = array(); 54 55 /** 56 * Name of the template 57 * 58 * @var string 59 * @since 1.7.0 60 */ 61 public $template = null; 62 63 /** 64 * Base url 65 * 66 * @var string 67 * @since 1.7.0 68 */ 69 public $baseurl = null; 70 71 /** 72 * Array of template parameters 73 * 74 * @var array 75 * @since 1.7.0 76 */ 77 public $params = null; 78 79 /** 80 * File name 81 * 82 * @var array 83 * @since 1.7.0 84 */ 85 public $_file = null; 86 87 /** 88 * Script nonce (string if set, null otherwise) 89 * 90 * @var string|null 91 * @since 4.0.0 92 */ 93 public $cspNonce = null; 94 95 /** 96 * String holding parsed template 97 * 98 * @var string 99 * @since 1.7.0 100 */ 101 protected $_template = ''; 102 103 /** 104 * Array of parsed template JDoc tags 105 * 106 * @var array 107 * @since 1.7.0 108 */ 109 protected $_template_tags = array(); 110 111 /** 112 * Integer with caching setting 113 * 114 * @var integer 115 * @since 1.7.0 116 */ 117 protected $_caching = null; 118 119 /** 120 * Set to true when the document should be output as HTML5 121 * 122 * @var boolean 123 * @since 4.0.0 124 */ 125 private $html5 = true; 126 127 /** 128 * Class constructor 129 * 130 * @param array $options Associative array of options 131 * 132 * @since 1.7.0 133 */ 134 public function __construct($options = array()) 135 { 136 parent::__construct($options); 137 138 // Set document type 139 $this->_type = 'html'; 140 141 // Set default mime type and document metadata (metadata syncs with mime type by default) 142 $this->setMimeEncoding('text/html'); 143 } 144 145 /** 146 * Get the HTML document head data 147 * 148 * @return array The document head data in array form 149 * 150 * @since 1.7.0 151 */ 152 public function getHeadData() 153 { 154 $data = array(); 155 $data['title'] = $this->title; 156 $data['description'] = $this->description; 157 $data['link'] = $this->link; 158 $data['metaTags'] = $this->_metaTags; 159 $data['links'] = $this->_links; 160 $data['styleSheets'] = $this->_styleSheets; 161 $data['style'] = $this->_style; 162 $data['scripts'] = $this->_scripts; 163 $data['script'] = $this->_script; 164 $data['custom'] = $this->_custom; 165 166 // @deprecated 5.0 This property is for backwards compatibility. Pass text through script options in the future 167 $data['scriptText'] = Text::getScriptStrings(); 168 169 $data['scriptOptions'] = $this->scriptOptions; 170 171 // Get Asset manager state 172 $wa = $this->getWebAssetManager(); 173 $waState = $wa->getManagerState(); 174 175 // Get asset objects and filter only manually added/enabled assets, 176 // Dependencies will be picked up from registry files 177 $waState['assets'] = []; 178 179 foreach ($waState['activeAssets'] as $assetType => $assetNames) { 180 foreach ($assetNames as $assetName => $assetState) { 181 $waState['assets'][$assetType][] = $wa->getAsset($assetType, $assetName); 182 } 183 } 184 185 // We have loaded asset objects, now can remove unused stuff 186 unset($waState['activeAssets']); 187 188 $data['assetManager'] = $waState; 189 190 return $data; 191 } 192 193 /** 194 * Reset the HTML document head data 195 * 196 * @param mixed $types type or types of the heads elements to reset 197 * 198 * @return HtmlDocument instance of $this to allow chaining 199 * 200 * @since 3.7.0 201 */ 202 public function resetHeadData($types = null) 203 { 204 if (\is_null($types)) { 205 $this->title = ''; 206 $this->description = ''; 207 $this->link = ''; 208 $this->_metaTags = array(); 209 $this->_links = array(); 210 $this->_styleSheets = array(); 211 $this->_style = array(); 212 $this->_scripts = array(); 213 $this->_script = array(); 214 $this->_custom = array(); 215 $this->scriptOptions = array(); 216 } 217 218 if (\is_array($types)) { 219 foreach ($types as $type) { 220 $this->resetHeadDatum($type); 221 } 222 } 223 224 if (\is_string($types)) { 225 $this->resetHeadDatum($types); 226 } 227 228 return $this; 229 } 230 231 /** 232 * Reset a part the HTML document head data 233 * 234 * @param string $type type of the heads elements to reset 235 * 236 * @return void 237 * 238 * @since 3.7.0 239 */ 240 private function resetHeadDatum($type) 241 { 242 switch ($type) { 243 case 'title': 244 case 'description': 245 case 'link': 246 $this->{$type} = ''; 247 break; 248 249 case 'metaTags': 250 case 'links': 251 case 'styleSheets': 252 case 'style': 253 case 'scripts': 254 case 'script': 255 case 'custom': 256 $realType = '_' . $type; 257 $this->{$realType} = array(); 258 break; 259 260 case 'scriptOptions': 261 $this->{$type} = array(); 262 break; 263 } 264 } 265 266 /** 267 * Set the HTML document head data 268 * 269 * @param array $data The document head data in array form 270 * 271 * @return HtmlDocument|null instance of $this to allow chaining or null for empty input data 272 * 273 * @since 1.7.0 274 */ 275 public function setHeadData($data) 276 { 277 if (empty($data) || !\is_array($data)) { 278 return null; 279 } 280 281 $this->title = $data['title'] ?? $this->title; 282 $this->description = $data['description'] ?? $this->description; 283 $this->link = $data['link'] ?? $this->link; 284 $this->_metaTags = $data['metaTags'] ?? $this->_metaTags; 285 $this->_links = $data['links'] ?? $this->_links; 286 $this->_styleSheets = $data['styleSheets'] ?? $this->_styleSheets; 287 $this->_style = $data['style'] ?? $this->_style; 288 $this->_scripts = $data['scripts'] ?? $this->_scripts; 289 $this->_script = $data['script'] ?? $this->_script; 290 $this->_custom = $data['custom'] ?? $this->_custom; 291 $this->scriptOptions = (isset($data['scriptOptions']) && !empty($data['scriptOptions'])) ? $data['scriptOptions'] : $this->scriptOptions; 292 293 // Restore asset manager state 294 $wa = $this->getWebAssetManager(); 295 296 if (!empty($data['assetManager']['registryFiles'])) { 297 $waRegistry = $wa->getRegistry(); 298 299 foreach ($data['assetManager']['registryFiles'] as $registryFile) { 300 $waRegistry->addRegistryFile($registryFile); 301 } 302 } 303 304 if (!empty($data['assetManager']['assets'])) { 305 foreach ($data['assetManager']['assets'] as $assetType => $assets) { 306 foreach ($assets as $asset) { 307 $wa->registerAsset($assetType, $asset)->useAsset($assetType, $asset->getName()); 308 } 309 } 310 } 311 312 return $this; 313 } 314 315 /** 316 * Merge the HTML document head data 317 * 318 * @param array $data The document head data in array form 319 * 320 * @return HtmlDocument|void instance of $this to allow chaining or void for empty input data 321 * 322 * @since 1.7.0 323 */ 324 public function mergeHeadData($data) 325 { 326 if (empty($data) || !\is_array($data)) { 327 return; 328 } 329 330 $this->title = (isset($data['title']) && !empty($data['title']) && !stristr($this->title, $data['title'])) 331 ? $this->title . $data['title'] 332 : $this->title; 333 $this->description = (isset($data['description']) && !empty($data['description']) && !stristr($this->description, $data['description'])) 334 ? $this->description . $data['description'] 335 : $this->description; 336 $this->link = $data['link'] ?? $this->link; 337 338 if (isset($data['metaTags'])) { 339 foreach ($data['metaTags'] as $type1 => $data1) { 340 $booldog = $type1 === 'http-equiv'; 341 342 foreach ($data1 as $name2 => $data2) { 343 $this->setMetaData($name2, $data2, $booldog); 344 } 345 } 346 } 347 348 $this->_links = (isset($data['links']) && !empty($data['links']) && \is_array($data['links'])) 349 ? array_unique(array_merge($this->_links, $data['links']), SORT_REGULAR) 350 : $this->_links; 351 $this->_styleSheets = (isset($data['styleSheets']) && !empty($data['styleSheets']) && \is_array($data['styleSheets'])) 352 ? array_merge($this->_styleSheets, $data['styleSheets']) 353 : $this->_styleSheets; 354 355 if (isset($data['style'])) { 356 foreach ($data['style'] as $type => $styles) { 357 foreach ($styles as $hash => $style) { 358 if (!isset($this->_style[strtolower($type)][$hash])) { 359 $this->addStyleDeclaration($style, $type); 360 } 361 } 362 } 363 } 364 365 $this->_scripts = (isset($data['scripts']) && !empty($data['scripts']) && \is_array($data['scripts'])) 366 ? array_merge($this->_scripts, $data['scripts']) 367 : $this->_scripts; 368 369 if (isset($data['script'])) { 370 foreach ($data['script'] as $type => $scripts) { 371 foreach ($scripts as $hash => $script) { 372 if (!isset($this->_script[strtolower($type)][$hash])) { 373 $this->addScriptDeclaration($script, $type); 374 } 375 } 376 } 377 } 378 379 $this->_custom = (isset($data['custom']) && !empty($data['custom']) && \is_array($data['custom'])) 380 ? array_unique(array_merge($this->_custom, $data['custom'])) 381 : $this->_custom; 382 383 if (!empty($data['scriptOptions'])) { 384 foreach ($data['scriptOptions'] as $key => $scriptOptions) { 385 $this->addScriptOptions($key, $scriptOptions, true); 386 } 387 } 388 389 // Restore asset manager state 390 $wa = $this->getWebAssetManager(); 391 392 if (!empty($data['assetManager']['registryFiles'])) { 393 $waRegistry = $wa->getRegistry(); 394 395 foreach ($data['assetManager']['registryFiles'] as $registryFile) { 396 $waRegistry->addRegistryFile($registryFile); 397 } 398 } 399 400 if (!empty($data['assetManager']['assets'])) { 401 foreach ($data['assetManager']['assets'] as $assetType => $assets) { 402 foreach ($assets as $asset) { 403 $wa->registerAsset($assetType, $asset)->useAsset($assetType, $asset->getName()); 404 } 405 } 406 } 407 408 return $this; 409 } 410 411 /** 412 * Adds `<link>` tags to the head of the document 413 * 414 * $relType defaults to 'rel' as it is the most common relation type used. 415 * ('rev' refers to reverse relation, 'rel' indicates normal, forward relation.) 416 * Typical tag: `<link href="index.php" rel="Start">` 417 * 418 * @param string $href The link that is being related. 419 * @param string $relation Relation of link. 420 * @param string $relType Relation type attribute. Either rel or rev (default: 'rel'). 421 * @param array $attribs Associative array of remaining attributes. 422 * 423 * @return HtmlDocument instance of $this to allow chaining 424 * 425 * @since 1.7.0 426 */ 427 public function addHeadLink($href, $relation, $relType = 'rel', $attribs = array()) 428 { 429 $this->_links[$href]['relation'] = $relation; 430 $this->_links[$href]['relType'] = $relType; 431 $this->_links[$href]['attribs'] = $attribs; 432 433 return $this; 434 } 435 436 /** 437 * Adds a shortcut icon (favicon) 438 * 439 * This adds a link to the icon shown in the favorites list or on 440 * the left of the url in the address bar. Some browsers display 441 * it on the tab, as well. 442 * 443 * @param string $href The link that is being related. 444 * @param string $type File type 445 * @param string $relation Relation of link 446 * 447 * @return HtmlDocument instance of $this to allow chaining 448 * 449 * @since 1.7.0 450 */ 451 public function addFavicon($href, $type = 'image/vnd.microsoft.icon', $relation = 'shortcut icon') 452 { 453 $href = str_replace('\\', '/', $href); 454 $this->addHeadLink($href, $relation, 'rel', array('type' => $type)); 455 456 return $this; 457 } 458 459 /** 460 * Adds a custom HTML string to the head block 461 * 462 * @param string $html The HTML to add to the head 463 * 464 * @return HtmlDocument instance of $this to allow chaining 465 * 466 * @since 1.7.0 467 */ 468 public function addCustomTag($html) 469 { 470 $this->_custom[] = trim($html); 471 472 return $this; 473 } 474 475 /** 476 * Returns whether the document is set up to be output as HTML5 477 * 478 * @return boolean true when HTML5 is used 479 * 480 * @since 3.0.0 481 */ 482 public function isHtml5() 483 { 484 return $this->html5; 485 } 486 487 /** 488 * Sets whether the document should be output as HTML5 489 * 490 * @param bool $state True when HTML5 should be output 491 * 492 * @return void 493 * 494 * @since 3.0.0 495 */ 496 public function setHtml5($state) 497 { 498 if (\is_bool($state)) { 499 $this->html5 = $state; 500 } 501 } 502 503 /** 504 * Get the contents of a document include 505 * 506 * @param string $type The type of renderer 507 * @param string $name The name of the element to render 508 * @param array $attribs Associative array of remaining attributes. 509 * 510 * @return mixed|string The output of the renderer 511 * 512 * @since 1.7.0 513 */ 514 public function getBuffer($type = null, $name = null, $attribs = array()) 515 { 516 // If no type is specified, return the whole buffer 517 if ($type === null) { 518 return parent::$_buffer; 519 } 520 521 $title = $attribs['title'] ?? null; 522 523 if (isset(parent::$_buffer[$type][$name][$title])) { 524 return parent::$_buffer[$type][$name][$title]; 525 } 526 527 $renderer = $this->loadRenderer($type); 528 529 if ($this->_caching == true && $type === 'modules' && $name !== 'debug') { 530 /** @var \Joomla\CMS\Document\Renderer\Html\ModulesRenderer $renderer */ 531 /** @var \Joomla\CMS\Cache\Controller\OutputController $cache */ 532 $cache = $this->getCacheControllerFactory()->createCacheController('output', ['defaultgroup' => 'com_modules']); 533 $itemId = (int) CmsFactory::getApplication()->input->get('Itemid', 0, 'int'); 534 535 $hash = md5( 536 serialize( 537 [ 538 $name, 539 $attribs, 540 \get_class($renderer), 541 $itemId, 542 ] 543 ) 544 ); 545 $cbuffer = $cache->get('cbuffer_' . $type); 546 547 if (isset($cbuffer[$hash])) { 548 return Cache::getWorkarounds($cbuffer[$hash], array('mergehead' => 1)); 549 } 550 551 $options = array(); 552 $options['nopathway'] = 1; 553 $options['nomodules'] = 1; 554 $options['modulemode'] = 1; 555 556 $this->setBuffer($renderer->render($name, $attribs, null), $type, $name); 557 $data = parent::$_buffer[$type][$name][$title]; 558 559 $tmpdata = Cache::setWorkarounds($data, $options); 560 561 $cbuffer[$hash] = $tmpdata; 562 563 $cache->store($cbuffer, 'cbuffer_' . $type); 564 } else { 565 $this->setBuffer($renderer->render($name, $attribs, null), $type, $name, $title); 566 } 567 568 return parent::$_buffer[$type][$name][$title]; 569 } 570 571 /** 572 * Set the contents a document includes 573 * 574 * @param string $content The content to be set in the buffer. 575 * @param array $options Array of optional elements. 576 * 577 * @return HtmlDocument instance of $this to allow chaining 578 * 579 * @since 1.7.0 580 */ 581 public function setBuffer($content, $options = array()) 582 { 583 // The following code is just for backward compatibility. 584 if (\func_num_args() > 1 && !\is_array($options)) { 585 $args = \func_get_args(); 586 $options = array(); 587 $options['type'] = $args[1]; 588 $options['name'] = $args[2] ?? null; 589 $options['title'] = $args[3] ?? null; 590 } 591 592 parent::$_buffer[$options['type']][$options['name']][$options['title']] = $content; 593 594 return $this; 595 } 596 597 /** 598 * Parses the template and populates the buffer 599 * 600 * @param array $params Parameters for fetching the template 601 * 602 * @return HtmlDocument instance of $this to allow chaining 603 * 604 * @since 1.7.0 605 */ 606 public function parse($params = array()) 607 { 608 return $this->_fetchTemplate($params)->_parseTemplate(); 609 } 610 611 /** 612 * Outputs the template to the browser. 613 * 614 * @param boolean $caching If true, cache the output 615 * @param array $params Associative array of attributes 616 * 617 * @return string The rendered data 618 * 619 * @since 1.7.0 620 */ 621 public function render($caching = false, $params = array()) 622 { 623 $this->_caching = $caching; 624 625 if (empty($this->_template)) { 626 $this->parse($params); 627 } 628 629 if (\array_key_exists('csp_nonce', $params) && $params['csp_nonce'] !== null) { 630 $this->cspNonce = $params['csp_nonce']; 631 } 632 633 $data = $this->_renderTemplate(); 634 parent::render($caching, $params); 635 636 return $data; 637 } 638 639 /** 640 * Count the modules in the given position 641 * 642 * @param string $positionName The position to use 643 * @param boolean $withContentOnly Count only a modules which actually has a content 644 * 645 * @return integer Number of modules found 646 * 647 * @since 1.7.0 648 */ 649 public function countModules(string $positionName, bool $withContentOnly = false) 650 { 651 if ((isset(parent::$_buffer['modules'][$positionName])) && (parent::$_buffer['modules'][$positionName] === false)) { 652 return 0; 653 } 654 655 $modules = ModuleHelper::getModules($positionName); 656 657 if (!$withContentOnly) { 658 return \count($modules); 659 } 660 661 // Now we need to count only modules which actually have a content 662 $result = 0; 663 $renderer = $this->loadRenderer('module'); 664 665 foreach ($modules as $module) { 666 if (empty($module->contentRendered)) { 667 $renderer->render($module, ['contentOnly' => true]); 668 } 669 670 if (trim($module->content) !== '') { 671 $result++; 672 } 673 } 674 675 return $result; 676 } 677 678 /** 679 * Count the number of child menu items of the current active menu item 680 * 681 * @return integer Number of child menu items 682 * 683 * @since 1.7.0 684 */ 685 public function countMenuChildren() 686 { 687 static $children; 688 689 if (!isset($children)) { 690 $db = CmsFactory::getDbo(); 691 $app = CmsFactory::getApplication(); 692 $menu = $app->getMenu(); 693 $active = $menu->getActive(); 694 $children = 0; 695 696 if ($active) { 697 $query = $db->getQuery(true) 698 ->select('COUNT(*)') 699 ->from($db->quoteName('#__menu')) 700 ->where( 701 [ 702 $db->quoteName('parent_id') . ' = :id', 703 $db->quoteName('published') . ' = 1', 704 ] 705 ) 706 ->bind(':id', $active->id, ParameterType::INTEGER); 707 $db->setQuery($query); 708 $children = $db->loadResult(); 709 } 710 } 711 712 return $children; 713 } 714 715 /** 716 * Load a template file 717 * 718 * @param string $directory The name of the template 719 * @param string $filename The actual filename 720 * 721 * @return string The contents of the template 722 * 723 * @since 1.7.0 724 */ 725 protected function _loadTemplate($directory, $filename) 726 { 727 $contents = ''; 728 729 // Check to see if we have a valid template file 730 if (is_file($directory . '/' . $filename)) { 731 // Store the file path 732 $this->_file = $directory . '/' . $filename; 733 734 // Get the file content 735 ob_start(); 736 require $directory . '/' . $filename; 737 $contents = ob_get_contents(); 738 ob_end_clean(); 739 } 740 741 return $contents; 742 } 743 744 /** 745 * Fetch the template, and initialise the params 746 * 747 * @param array $params Parameters to determine the template 748 * 749 * @return HtmlDocument instance of $this to allow chaining 750 * 751 * @since 1.7.0 752 */ 753 protected function _fetchTemplate($params = array()) 754 { 755 // Check 756 $directory = $params['directory'] ?? 'templates'; 757 $filter = InputFilter::getInstance(); 758 $template = $filter->clean($params['template'], 'cmd'); 759 $file = $filter->clean($params['file'], 'cmd'); 760 $inherits = $params['templateInherits'] ?? ''; 761 $baseDir = $directory . '/' . $template; 762 763 if (!is_file($directory . '/' . $template . '/' . $file)) { 764 if ($inherits !== '' && is_file($directory . '/' . $inherits . '/' . $file)) { 765 $baseDir = $directory . '/' . $inherits; 766 } else { 767 $baseDir = $directory . '/system'; 768 $template = 'system'; 769 770 if ($file !== 'index.php' && !is_file($baseDir . '/' . $file)) { 771 $file = 'index.php'; 772 } 773 } 774 } 775 776 // Load the language file for the template 777 $lang = CmsFactory::getLanguage(); 778 779 // 1.5 or core then 1.6 780 $lang->load('tpl_' . $template, JPATH_BASE) 781 || ($inherits !== '' && $lang->load('tpl_' . $inherits, $directory . '/' . $inherits)) 782 || $lang->load('tpl_' . $template, $directory . '/' . $template); 783 784 // Assign the variables 785 $this->baseurl = Uri::base(true); 786 $this->params = $params['params'] ?? new Registry(); 787 $this->template = $template; 788 789 // Load 790 $this->_template = $this->_loadTemplate($baseDir, $file); 791 792 return $this; 793 } 794 795 /** 796 * Parse a document template 797 * 798 * @return HtmlDocument instance of $this to allow chaining 799 * 800 * @since 1.7.0 801 */ 802 protected function _parseTemplate() 803 { 804 $matches = array(); 805 806 if (preg_match_all('#<jdoc:include\ type="([^"]+)"(.*)\/>#iU', $this->_template, $matches)) { 807 $messages = []; 808 $template_tags_first = []; 809 $template_tags_last = []; 810 811 // Step through the jdocs in reverse order. 812 for ($i = \count($matches[0]) - 1; $i >= 0; $i--) { 813 $type = $matches[1][$i]; 814 $attribs = empty($matches[2][$i]) ? array() : Utility::parseAttributes($matches[2][$i]); 815 $name = $attribs['name'] ?? null; 816 817 // Separate buffers to be executed first and last 818 if ($type === 'module' || $type === 'modules') { 819 $template_tags_first[$matches[0][$i]] = ['type' => $type, 'name' => $name, 'attribs' => $attribs]; 820 } elseif ($type === 'message') { 821 $messages = [$matches[0][$i] => ['type' => $type, 'name' => $name, 'attribs' => $attribs]]; 822 } else { 823 $template_tags_last[$matches[0][$i]] = ['type' => $type, 'name' => $name, 'attribs' => $attribs]; 824 } 825 } 826 827 $this->_template_tags = $template_tags_first + $messages + array_reverse($template_tags_last); 828 } 829 830 return $this; 831 } 832 833 /** 834 * Render pre-parsed template 835 * 836 * @return string rendered template 837 * 838 * @since 1.7.0 839 */ 840 protected function _renderTemplate() 841 { 842 $replace = []; 843 $with = []; 844 845 foreach ($this->_template_tags as $jdoc => $args) { 846 $replace[] = $jdoc; 847 $with[] = $this->getBuffer($args['type'], $args['name'], $args['attribs']); 848 } 849 850 return str_replace($replace, $with, $this->_template); 851 } 852 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Sep 7 05:41:13 2022 | Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer |