[ Index ] |
PHP Cross Reference of Joomla 4.2.2 documentation |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * @package Joomla.Administrator 5 * @subpackage com_templates 6 * 7 * @copyright (C) 2008 Open Source Matters, Inc. <https://www.joomla.org> 8 * @license GNU General Public License version 2 or later; see LICENSE.txt 9 */ 10 11 namespace Joomla\Component\Templates\Administrator\Model; 12 13 use Joomla\CMS\Application\ApplicationHelper; 14 use Joomla\CMS\Component\ComponentHelper; 15 use Joomla\CMS\Date\Date; 16 use Joomla\CMS\Factory; 17 use Joomla\CMS\Filesystem\File; 18 use Joomla\CMS\Filesystem\Folder; 19 use Joomla\CMS\Filesystem\Path; 20 use Joomla\CMS\Form\Form; 21 use Joomla\CMS\Image\Image; 22 use Joomla\CMS\Language\Text; 23 use Joomla\CMS\MVC\Model\FormModel; 24 use Joomla\CMS\Plugin\PluginHelper; 25 use Joomla\CMS\Uri\Uri; 26 use Joomla\Component\Templates\Administrator\Helper\TemplateHelper; 27 use Joomla\Component\Templates\Administrator\Helper\TemplatesHelper; 28 use Joomla\Database\ParameterType; 29 use Joomla\Utilities\ArrayHelper; 30 31 // phpcs:disable PSR1.Files.SideEffects 32 \defined('_JEXEC') or die; 33 // phpcs:enable PSR1.Files.SideEffects 34 35 /** 36 * Template model class. 37 * 38 * @since 1.6 39 */ 40 class TemplateModel extends FormModel 41 { 42 /** 43 * The information in a template 44 * 45 * @var \stdClass 46 * @since 1.6 47 */ 48 protected $template = null; 49 50 /** 51 * The path to the template 52 * 53 * @var string 54 * @since 3.2 55 */ 56 protected $element = null; 57 58 /** 59 * The path to the static assets 60 * 61 * @var string 62 * @since 4.1.0 63 */ 64 protected $mediaElement = null; 65 66 /** 67 * Internal method to get file properties. 68 * 69 * @param string $path The base path. 70 * @param string $name The file name. 71 * 72 * @return object 73 * 74 * @since 1.6 75 */ 76 protected function getFile($path, $name) 77 { 78 $temp = new \stdClass(); 79 80 if ($this->getTemplate()) { 81 $path = str_replace(JPATH_ROOT . DIRECTORY_SEPARATOR . 'media' . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . ($this->template->client_id === 0 ? 'site' : 'administrator') . DIRECTORY_SEPARATOR . $this->template->element, '', $path); 82 $path = str_replace(JPATH_ROOT . DIRECTORY_SEPARATOR . ($this->template->client_id === 0 ? '' : 'administrator' . DIRECTORY_SEPARATOR) . 'templates' . DIRECTORY_SEPARATOR . $this->template->element, '', $path); 83 $temp->name = $name; 84 $temp->id = urlencode(base64_encode(str_replace('\\', '//', $path))); 85 86 return $temp; 87 } 88 } 89 90 /** 91 * Method to store file information. 92 * 93 * @param string $path The base path. 94 * @param string $name The file name. 95 * @param stdClass $template The std class object of template. 96 * 97 * @return object stdClass object. 98 * 99 * @since 4.0.0 100 */ 101 protected function storeFileInfo($path, $name, $template) 102 { 103 $temp = new \stdClass(); 104 $temp->id = base64_encode($path . $name); 105 $temp->client = $template->client_id; 106 $temp->template = $template->element; 107 $temp->extension_id = $template->extension_id; 108 109 if ($coreFile = $this->getCoreFile($path . $name, $template->client_id)) { 110 $temp->coreFile = md5_file($coreFile); 111 } else { 112 $temp->coreFile = null; 113 } 114 115 return $temp; 116 } 117 118 /** 119 * Method to get all template list. 120 * 121 * @return object stdClass object 122 * 123 * @since 4.0.0 124 */ 125 public function getTemplateList() 126 { 127 // Get a db connection. 128 $db = $this->getDatabase(); 129 130 // Create a new query object. 131 $query = $db->getQuery(true); 132 133 // Select the required fields from the table 134 $query->select( 135 $this->getState( 136 'list.select', 137 'a.extension_id, a.name, a.element, a.client_id' 138 ) 139 ); 140 141 $query->from($db->quoteName('#__extensions', 'a')) 142 ->where($db->quoteName('a.enabled') . ' = 1') 143 ->where($db->quoteName('a.type') . ' = ' . $db->quote('template')); 144 145 // Reset the query. 146 $db->setQuery($query); 147 148 // Load the results as a list of stdClass objects. 149 $results = $db->loadObjectList(); 150 151 return $results; 152 } 153 154 /** 155 * Method to get all updated file list. 156 * 157 * @param boolean $state The optional parameter if you want unchecked list. 158 * @param boolean $all The optional parameter if you want all list. 159 * @param boolean $cleanup The optional parameter if you want to clean record which is no more required. 160 * 161 * @return object stdClass object 162 * 163 * @since 4.0.0 164 */ 165 public function getUpdatedList($state = false, $all = false, $cleanup = false) 166 { 167 // Get a db connection. 168 $db = $this->getDatabase(); 169 170 // Create a new query object. 171 $query = $db->getQuery(true); 172 173 // Select the required fields from the table 174 $query->select( 175 $this->getState( 176 'list.select', 177 'a.template, a.hash_id, a.extension_id, a.state, a.action, a.client_id, a.created_date, a.modified_date' 178 ) 179 ); 180 181 $template = $this->getTemplate(); 182 183 $query->from($db->quoteName('#__template_overrides', 'a')); 184 185 if (!$all) { 186 $teid = (int) $template->extension_id; 187 $query->where($db->quoteName('extension_id') . ' = :teid') 188 ->bind(':teid', $teid, ParameterType::INTEGER); 189 } 190 191 if ($state) { 192 $query->where($db->quoteName('state') . ' = 0'); 193 } 194 195 $query->order($db->quoteName('a.modified_date') . ' DESC'); 196 197 // Reset the query. 198 $db->setQuery($query); 199 200 // Load the results as a list of stdClass objects. 201 $pks = $db->loadObjectList(); 202 203 if ($state) { 204 return $pks; 205 } 206 207 $results = array(); 208 209 foreach ($pks as $pk) { 210 $client = ApplicationHelper::getClientInfo($pk->client_id); 211 $path = Path::clean($client->path . '/templates/' . $pk->template . base64_decode($pk->hash_id)); 212 213 if (file_exists($path)) { 214 $results[] = $pk; 215 } elseif ($cleanup) { 216 $cleanupIds = array(); 217 $cleanupIds[] = $pk->hash_id; 218 $this->publish($cleanupIds, -3, $pk->extension_id); 219 } 220 } 221 222 return $results; 223 } 224 225 /** 226 * Method to get a list of all the core files of override files. 227 * 228 * @return array An array of all core files. 229 * 230 * @since 4.0.0 231 */ 232 public function getCoreList() 233 { 234 // Get list of all templates 235 $templates = $this->getTemplateList(); 236 237 // Initialize the array variable to store core file list. 238 $this->coreFileList = array(); 239 240 $app = Factory::getApplication(); 241 242 foreach ($templates as $template) { 243 $client = ApplicationHelper::getClientInfo($template->client_id); 244 $element = Path::clean($client->path . '/templates/' . $template->element . '/'); 245 $path = Path::clean($element . 'html/'); 246 247 if (is_dir($path)) { 248 $this->prepareCoreFiles($path, $element, $template); 249 } 250 } 251 252 // Sort list of stdClass array. 253 usort( 254 $this->coreFileList, 255 function ($a, $b) { 256 return strcmp($a->id, $b->id); 257 } 258 ); 259 260 return $this->coreFileList; 261 } 262 263 /** 264 * Prepare core files. 265 * 266 * @param string $dir The path of the directory to scan. 267 * @param string $element The path of the template element. 268 * @param \stdClass $template The stdClass object of template. 269 * 270 * @return array 271 * 272 * @since 4.0.0 273 */ 274 public function prepareCoreFiles($dir, $element, $template) 275 { 276 $dirFiles = scandir($dir); 277 278 foreach ($dirFiles as $key => $value) { 279 if (in_array($value, array('.', '..', 'node_modules'))) { 280 continue; 281 } 282 283 if (is_dir($dir . $value)) { 284 $relativePath = str_replace($element, '', $dir . $value); 285 $this->prepareCoreFiles($dir . $value . '/', $element, $template); 286 } else { 287 $ext = pathinfo($dir . $value, PATHINFO_EXTENSION); 288 $allowedFormat = $this->checkFormat($ext); 289 290 if ($allowedFormat === true) { 291 $relativePath = str_replace($element, '', $dir); 292 $info = $this->storeFileInfo('/' . $relativePath, $value, $template); 293 294 if ($info) { 295 $this->coreFileList[] = $info; 296 } 297 } 298 } 299 } 300 } 301 302 /** 303 * Method to update status of list. 304 * 305 * @param array $ids The base path. 306 * @param array $value The file name. 307 * @param integer $exid The template extension id. 308 * 309 * @return integer Number of files changed. 310 * 311 * @since 4.0.0 312 */ 313 public function publish($ids, $value, $exid) 314 { 315 $db = $this->getDatabase(); 316 317 foreach ($ids as $id) { 318 if ($value === -3) { 319 $deleteQuery = $db->getQuery(true) 320 ->delete($db->quoteName('#__template_overrides')) 321 ->where($db->quoteName('hash_id') . ' = :hashid') 322 ->where($db->quoteName('extension_id') . ' = :exid') 323 ->bind(':hashid', $id) 324 ->bind(':exid', $exid, ParameterType::INTEGER); 325 326 try { 327 // Set the query using our newly populated query object and execute it. 328 $db->setQuery($deleteQuery); 329 $result = $db->execute(); 330 } catch (\RuntimeException $e) { 331 return $e; 332 } 333 } elseif ($value === 1 || $value === 0) { 334 $updateQuery = $db->getQuery(true) 335 ->update($db->quoteName('#__template_overrides')) 336 ->set($db->quoteName('state') . ' = :state') 337 ->where($db->quoteName('hash_id') . ' = :hashid') 338 ->where($db->quoteName('extension_id') . ' = :exid') 339 ->bind(':state', $value, ParameterType::INTEGER) 340 ->bind(':hashid', $id) 341 ->bind(':exid', $exid, ParameterType::INTEGER); 342 343 try { 344 // Set the query using our newly populated query object and execute it. 345 $db->setQuery($updateQuery); 346 $result = $db->execute(); 347 } catch (\RuntimeException $e) { 348 return $e; 349 } 350 } 351 } 352 353 return $result; 354 } 355 356 /** 357 * Method to get a list of all the files to edit in a template. 358 * 359 * @return array A nested array of relevant files. 360 * 361 * @since 1.6 362 */ 363 public function getFiles() 364 { 365 $result = array(); 366 367 if ($template = $this->getTemplate()) { 368 $app = Factory::getApplication(); 369 $client = ApplicationHelper::getClientInfo($template->client_id); 370 $path = Path::clean($client->path . '/templates/' . $template->element . '/'); 371 $lang = Factory::getLanguage(); 372 373 // Load the core and/or local language file(s). 374 $lang->load('tpl_' . $template->element, $client->path) 375 || (!empty($template->xmldata->parent) && $lang->load('tpl_' . $template->xmldata->parent, $client->path)) 376 || $lang->load('tpl_' . $template->element, $client->path . '/templates/' . $template->element) 377 || (!empty($template->xmldata->parent) && $lang->load('tpl_' . $template->xmldata->parent, $client->path . '/templates/' . $template->xmldata->parent)); 378 $this->element = $path; 379 380 if (!is_writable($path)) { 381 $app->enqueueMessage(Text::_('COM_TEMPLATES_DIRECTORY_NOT_WRITABLE'), 'error'); 382 } 383 384 if (is_dir($path)) { 385 $result = $this->getDirectoryTree($path); 386 } else { 387 $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_TEMPLATE_FOLDER_NOT_FOUND'), 'error'); 388 389 return false; 390 } 391 392 // Clean up override history 393 $this->getUpdatedList(false, true, true); 394 } 395 396 return $result; 397 } 398 399 /** 400 * Get the directory tree. 401 * 402 * @param string $dir The path of the directory to scan 403 * 404 * @return array 405 * 406 * @since 3.2 407 */ 408 public function getDirectoryTree($dir) 409 { 410 $result = array(); 411 412 $dirFiles = scandir($dir); 413 414 foreach ($dirFiles as $key => $value) { 415 if (!in_array($value, array('.', '..', 'node_modules'))) { 416 if (is_dir($dir . $value)) { 417 $relativePath = str_replace(JPATH_ROOT . DIRECTORY_SEPARATOR . 'media' . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . ($this->template->client_id === 0 ? 'site' : 'administrator') . DIRECTORY_SEPARATOR . $this->template->element, '', $dir . $value); 418 $relativePath = str_replace(JPATH_ROOT . DIRECTORY_SEPARATOR . ($this->template->client_id === 0 ? '' : 'administrator' . DIRECTORY_SEPARATOR) . 'templates' . DIRECTORY_SEPARATOR . $this->template->element, '', $relativePath); 419 $result[str_replace('\\', '//', $relativePath)] = $this->getDirectoryTree($dir . $value . '/'); 420 } else { 421 $ext = pathinfo($dir . $value, PATHINFO_EXTENSION); 422 $allowedFormat = $this->checkFormat($ext); 423 424 if ($allowedFormat == true) { 425 $relativePath = str_replace(JPATH_ROOT . DIRECTORY_SEPARATOR . 'media' . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . ($this->template->client_id === 0 ? 'site' : 'administrator') . DIRECTORY_SEPARATOR . $this->template->element, '', $dir . $value); 426 $relativePath = str_replace(JPATH_ROOT . DIRECTORY_SEPARATOR . ($this->template->client_id === 0 ? '' : 'administrator' . DIRECTORY_SEPARATOR) . 'templates' . DIRECTORY_SEPARATOR . $this->template->element, '', $relativePath); 427 $result[] = $this->getFile($relativePath, $value); 428 } 429 } 430 } 431 } 432 433 return $result; 434 } 435 436 /** 437 * Method to get the core file of override file 438 * 439 * @param string $file Override file 440 * @param integer $client_id Client Id 441 * 442 * @return string $corefile The full path and file name for the target file, or boolean false if the file is not found in any of the paths. 443 * 444 * @since 4.0.0 445 */ 446 public function getCoreFile($file, $client_id) 447 { 448 $app = Factory::getApplication(); 449 $filePath = Path::clean($file); 450 $explodeArray = explode(DIRECTORY_SEPARATOR, $filePath); 451 452 // Only allow html/ folder 453 if ($explodeArray['1'] !== 'html') { 454 return false; 455 } 456 457 $fileName = basename($filePath); 458 $type = $explodeArray['2']; 459 $client = ApplicationHelper::getClientInfo($client_id); 460 461 $componentPath = Path::clean($client->path . '/components/'); 462 $modulePath = Path::clean($client->path . '/modules/'); 463 $layoutPath = Path::clean(JPATH_ROOT . '/layouts/'); 464 465 // For modules 466 if (stristr($type, 'mod_') !== false) { 467 $folder = $explodeArray['2']; 468 $htmlPath = Path::clean($modulePath . $folder . '/tmpl/'); 469 $fileName = $this->getSafeName($fileName); 470 $coreFile = Path::find($htmlPath, $fileName); 471 472 return $coreFile; 473 } elseif (stristr($type, 'com_') !== false) { 474 // For components 475 $folder = $explodeArray['2']; 476 $subFolder = $explodeArray['3']; 477 $fileName = $this->getSafeName($fileName); 478 479 // The new scheme, if a view has a tmpl folder 480 $newHtmlPath = Path::clean($componentPath . $folder . '/tmpl/' . $subFolder . '/'); 481 482 if (!$coreFile = Path::find($newHtmlPath, $fileName)) { 483 // The old scheme, the views are directly in the component/tmpl folder 484 $oldHtmlPath = Path::clean($componentPath . $folder . '/views/' . $subFolder . '/tmpl/'); 485 $coreFile = Path::find($oldHtmlPath, $fileName); 486 487 return $coreFile; 488 } 489 490 return $coreFile; 491 } elseif (stristr($type, 'layouts') !== false) { 492 // For Layouts 493 $subtype = $explodeArray['3']; 494 495 if (stristr($subtype, 'com_')) { 496 $folder = $explodeArray['3']; 497 $subFolder = array_slice($explodeArray, 4, -1); 498 $subFolder = implode(DIRECTORY_SEPARATOR, $subFolder); 499 $htmlPath = Path::clean($componentPath . $folder . '/layouts/' . $subFolder); 500 $fileName = $this->getSafeName($fileName); 501 $coreFile = Path::find($htmlPath, $fileName); 502 503 return $coreFile; 504 } elseif (stristr($subtype, 'joomla') || stristr($subtype, 'libraries') || stristr($subtype, 'plugins')) { 505 $subFolder = array_slice($explodeArray, 3, -1); 506 $subFolder = implode(DIRECTORY_SEPARATOR, $subFolder); 507 $htmlPath = Path::clean($layoutPath . $subFolder); 508 $fileName = $this->getSafeName($fileName); 509 $coreFile = Path::find($htmlPath, $fileName); 510 511 return $coreFile; 512 } 513 } 514 515 return false; 516 } 517 518 /** 519 * Creates a safe file name for the given name. 520 * 521 * @param string $name The filename 522 * 523 * @return string $fileName The filtered name without Date 524 * 525 * @since 4.0.0 526 */ 527 private function getSafeName($name) 528 { 529 if (strpos($name, '-') !== false && preg_match('/[0-9]/', $name)) { 530 // Get the extension 531 $extension = File::getExt($name); 532 533 // Remove ( Date ) from file 534 $explodeArray = explode('-', $name); 535 $size = count($explodeArray); 536 $date = $explodeArray[$size - 2] . '-' . str_replace('.' . $extension, '', $explodeArray[$size - 1]); 537 538 if ($this->validateDate($date)) { 539 $nameWithoutExtension = implode('-', array_slice($explodeArray, 0, -2)); 540 541 // Filtered name 542 $name = $nameWithoutExtension . '.' . $extension; 543 } 544 } 545 546 return $name; 547 } 548 549 /** 550 * Validate Date in file name. 551 * 552 * @param string $date Date to validate. 553 * 554 * @return boolean Return true if date is valid and false if not. 555 * 556 * @since 4.0.0 557 */ 558 private function validateDate($date) 559 { 560 $format = 'Ymd-His'; 561 $valid = Date::createFromFormat($format, $date); 562 563 return $valid && $valid->format($format) === $date; 564 } 565 566 /** 567 * Method to auto-populate the model state. 568 * 569 * Note. Calling getState in this method will result in recursion. 570 * 571 * @return void 572 * 573 * @since 1.6 574 */ 575 protected function populateState() 576 { 577 $app = Factory::getApplication(); 578 579 // Load the User state. 580 $pk = $app->input->getInt('id'); 581 $this->setState('extension.id', $pk); 582 583 // Load the parameters. 584 $params = ComponentHelper::getParams('com_templates'); 585 $this->setState('params', $params); 586 } 587 588 /** 589 * Method to get the template information. 590 * 591 * @return mixed Object if successful, false if not and internal error is set. 592 * 593 * @since 1.6 594 */ 595 public function &getTemplate() 596 { 597 if (empty($this->template)) { 598 $pk = (int) $this->getState('extension.id'); 599 $db = $this->getDatabase(); 600 $app = Factory::getApplication(); 601 602 // Get the template information. 603 $query = $db->getQuery(true) 604 ->select($db->quoteName(['extension_id', 'client_id', 'element', 'name', 'manifest_cache'])) 605 ->from($db->quoteName('#__extensions')) 606 ->where($db->quoteName('extension_id') . ' = :pk') 607 ->where($db->quoteName('type') . ' = ' . $db->quote('template')) 608 ->bind(':pk', $pk, ParameterType::INTEGER); 609 $db->setQuery($query); 610 611 try { 612 $result = $db->loadObject(); 613 } catch (\RuntimeException $e) { 614 $app->enqueueMessage($e->getMessage(), 'warning'); 615 $this->template = false; 616 617 return false; 618 } 619 620 if (empty($result)) { 621 $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_EXTENSION_RECORD_NOT_FOUND'), 'error'); 622 $this->template = false; 623 } else { 624 $this->template = $result; 625 626 // Client ID is not always an integer, so enforce here 627 $this->template->client_id = (int) $this->template->client_id; 628 629 if (!isset($this->template->xmldata)) { 630 $this->template->xmldata = TemplatesHelper::parseXMLTemplateFile($this->template->client_id === 0 ? JPATH_ROOT : JPATH_ROOT . '/administrator', $this->template->name); 631 } 632 } 633 } 634 635 return $this->template; 636 } 637 638 /** 639 * Method to check if new template name already exists 640 * 641 * @return boolean true if name is not used, false otherwise 642 * 643 * @since 2.5 644 */ 645 public function checkNewName() 646 { 647 $db = $this->getDatabase(); 648 $name = $this->getState('new_name'); 649 $query = $db->getQuery(true) 650 ->select('COUNT(*)') 651 ->from($db->quoteName('#__extensions')) 652 ->where($db->quoteName('name') . ' = :name') 653 ->bind(':name', $name); 654 $db->setQuery($query); 655 656 return ($db->loadResult() == 0); 657 } 658 659 /** 660 * Method to check if new template name already exists 661 * 662 * @return string name of current template 663 * 664 * @since 2.5 665 */ 666 public function getFromName() 667 { 668 return $this->getTemplate()->element; 669 } 670 671 /** 672 * Method to check if new template name already exists 673 * 674 * @return boolean true if name is not used, false otherwise 675 * 676 * @since 2.5 677 */ 678 public function copy() 679 { 680 $app = Factory::getApplication(); 681 682 if ($template = $this->getTemplate()) { 683 $client = ApplicationHelper::getClientInfo($template->client_id); 684 $fromPath = Path::clean($client->path . '/templates/' . $template->element . '/'); 685 686 // Delete new folder if it exists 687 $toPath = $this->getState('to_path'); 688 689 if (Folder::exists($toPath)) { 690 if (!Folder::delete($toPath)) { 691 $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_COULD_NOT_WRITE'), 'error'); 692 693 return false; 694 } 695 } 696 697 // Copy all files from $fromName template to $newName folder 698 if (!Folder::copy($fromPath, $toPath)) { 699 return false; 700 } 701 702 // Check manifest for additional files 703 $manifest = simplexml_load_file($toPath . '/templateDetails.xml'); 704 705 // Copy language files from global folder 706 if ($languages = $manifest->languages) { 707 $folder = (string) $languages->attributes()->folder; 708 $languageFiles = $languages->language; 709 710 Folder::create($toPath . '/' . $folder . '/' . $languageFiles->attributes()->tag); 711 712 foreach ($languageFiles as $languageFile) { 713 $src = Path::clean($client->path . '/language/' . $languageFile); 714 $dst = Path::clean($toPath . '/' . $folder . '/' . $languageFile); 715 716 if (File::exists($src)) { 717 File::copy($src, $dst); 718 } 719 } 720 } 721 722 // Copy media files 723 if ($media = $manifest->media) { 724 $folder = (string) $media->attributes()->folder; 725 $destination = (string) $media->attributes()->destination; 726 727 Folder::copy(JPATH_SITE . '/media/' . $destination, $toPath . '/' . $folder); 728 } 729 730 // Adjust to new template name 731 if (!$this->fixTemplateName()) { 732 return false; 733 } 734 735 return true; 736 } else { 737 $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_INVALID_FROM_NAME'), 'error'); 738 739 return false; 740 } 741 } 742 743 /** 744 * Method to delete tmp folder 745 * 746 * @return boolean true if delete successful, false otherwise 747 * 748 * @since 2.5 749 */ 750 public function cleanup() 751 { 752 // Clear installation messages 753 $app = Factory::getApplication(); 754 $app->setUserState('com_installer.message', ''); 755 $app->setUserState('com_installer.extension_message', ''); 756 757 // Delete temporary directory 758 return Folder::delete($this->getState('to_path')); 759 } 760 761 /** 762 * Method to rename the template in the XML files and rename the language files 763 * 764 * @return boolean true if successful, false otherwise 765 * 766 * @since 2.5 767 */ 768 protected function fixTemplateName() 769 { 770 // Rename Language files 771 // Get list of language files 772 $result = true; 773 $files = Folder::files($this->getState('to_path'), '\.ini$', true, true); 774 $newName = strtolower($this->getState('new_name')); 775 $template = $this->getTemplate(); 776 $oldName = $template->element; 777 $manifest = json_decode($template->manifest_cache); 778 779 foreach ($files as $file) { 780 $newFile = '/' . str_replace($oldName, $newName, basename($file)); 781 $result = File::move($file, dirname($file) . $newFile) && $result; 782 } 783 784 // Edit XML file 785 $xmlFile = $this->getState('to_path') . '/templateDetails.xml'; 786 787 if (File::exists($xmlFile)) { 788 $contents = file_get_contents($xmlFile); 789 $pattern[] = '#<name>\s*' . $manifest->name . '\s*</name>#i'; 790 $replace[] = '<name>' . $newName . '</name>'; 791 $pattern[] = '#<language(.*)' . $oldName . '(.*)</language>#'; 792 $replace[] = '<language$1}' . $newName . '$2}</language>'; 793 $pattern[] = '#<media(.*)' . $oldName . '(.*)>#'; 794 $replace[] = '<media$1}' . $newName . '$2}>'; 795 $contents = preg_replace($pattern, $replace, $contents); 796 $result = File::write($xmlFile, $contents) && $result; 797 } 798 799 return $result; 800 } 801 802 /** 803 * Method to get the record form. 804 * 805 * @param array $data Data for the form. 806 * @param boolean $loadData True if the form is to load its own data (default case), false if not. 807 * 808 * @return Form|boolean A Form object on success, false on failure 809 * 810 * @since 1.6 811 */ 812 public function getForm($data = array(), $loadData = true) 813 { 814 $app = Factory::getApplication(); 815 816 // Codemirror or Editor None should be enabled 817 $db = $this->getDatabase(); 818 $query = $db->getQuery(true) 819 ->select('COUNT(*)') 820 ->from('#__extensions as a') 821 ->where( 822 '(a.name =' . $db->quote('plg_editors_codemirror') . 823 ' AND a.enabled = 1) OR (a.name =' . 824 $db->quote('plg_editors_none') . 825 ' AND a.enabled = 1)' 826 ); 827 $db->setQuery($query); 828 $state = $db->loadResult(); 829 830 if ((int) $state < 1) { 831 $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_EDITOR_DISABLED'), 'warning'); 832 } 833 834 // Get the form. 835 $form = $this->loadForm('com_templates.source', 'source', array('control' => 'jform', 'load_data' => $loadData)); 836 837 if (empty($form)) { 838 return false; 839 } 840 841 return $form; 842 } 843 844 /** 845 * Method to get the data that should be injected in the form. 846 * 847 * @return mixed The data for the form. 848 * 849 * @since 1.6 850 */ 851 protected function loadFormData() 852 { 853 $data = $this->getSource(); 854 855 $this->preprocessData('com_templates.source', $data); 856 857 return $data; 858 } 859 860 /** 861 * Method to get a single record. 862 * 863 * @return mixed Object on success, false on failure. 864 * 865 * @since 1.6 866 */ 867 public function &getSource() 868 { 869 $app = Factory::getApplication(); 870 $item = new \stdClass(); 871 872 if (!$this->template) { 873 $this->getTemplate(); 874 } 875 876 if ($this->template) { 877 $input = Factory::getApplication()->input; 878 $fileName = base64_decode($input->get('file')); 879 $fileName = str_replace('//', '/', $fileName); 880 $isMedia = $input->getInt('isMedia', 0); 881 882 $fileName = $isMedia ? Path::clean(JPATH_ROOT . '/media/templates/' . ($this->template->client_id === 0 ? 'site' : 'administrator') . '/' . $this->template->element . $fileName) 883 : Path::clean(JPATH_ROOT . ($this->template->client_id === 0 ? '' : '/administrator') . '/templates/' . $this->template->element . $fileName); 884 885 try { 886 $filePath = Path::check($fileName); 887 } catch (\Exception $e) { 888 $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_SOURCE_FILE_NOT_FOUND'), 'error'); 889 890 return; 891 } 892 893 if (file_exists($filePath)) { 894 $item->extension_id = $this->getState('extension.id'); 895 $item->filename = Path::clean($fileName); 896 $item->source = file_get_contents($filePath); 897 $item->filePath = Path::clean($filePath); 898 $ds = DIRECTORY_SEPARATOR; 899 $cleanFileName = str_replace(JPATH_ROOT . ($this->template->client_id === 1 ? $ds . 'administrator' . $ds : $ds) . 'templates' . $ds . $this->template->element, '', $fileName); 900 901 if ($coreFile = $this->getCoreFile($cleanFileName, $this->template->client_id)) { 902 $item->coreFile = $coreFile; 903 $item->core = file_get_contents($coreFile); 904 } 905 } else { 906 $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_SOURCE_FILE_NOT_FOUND'), 'error'); 907 } 908 } 909 910 return $item; 911 } 912 913 /** 914 * Method to store the source file contents. 915 * 916 * @param array $data The source data to save. 917 * 918 * @return boolean True on success, false otherwise and internal error set. 919 * 920 * @since 1.6 921 */ 922 public function save($data) 923 { 924 // Get the template. 925 $template = $this->getTemplate(); 926 927 if (empty($template)) { 928 return false; 929 } 930 931 $app = Factory::getApplication(); 932 $fileName = base64_decode($app->input->get('file')); 933 $isMedia = $app->input->getInt('isMedia', 0); 934 $fileName = $isMedia ? JPATH_ROOT . '/media/templates/' . ($this->template->client_id === 0 ? 'site' : 'administrator') . '/' . $this->template->element . $fileName : 935 JPATH_ROOT . '/' . ($this->template->client_id === 0 ? '' : 'administrator/') . 'templates/' . $this->template->element . $fileName; 936 937 $filePath = Path::clean($fileName); 938 939 // Include the extension plugins for the save events. 940 PluginHelper::importPlugin('extension'); 941 942 $user = get_current_user(); 943 chown($filePath, $user); 944 Path::setPermissions($filePath, '0644'); 945 946 // Try to make the template file writable. 947 if (!is_writable($filePath)) { 948 $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_SOURCE_FILE_NOT_WRITABLE'), 'warning'); 949 $app->enqueueMessage(Text::sprintf('COM_TEMPLATES_FILE_PERMISSIONS', Path::getPermissions($filePath)), 'warning'); 950 951 if (!Path::isOwner($filePath)) { 952 $app->enqueueMessage(Text::_('COM_TEMPLATES_CHECK_FILE_OWNERSHIP'), 'warning'); 953 } 954 955 return false; 956 } 957 958 // Make sure EOL is Unix 959 $data['source'] = str_replace(array("\r\n", "\r"), "\n", $data['source']); 960 961 // If the asset file for the template ensure we have valid template so we don't instantly destroy it 962 if ($fileName === '/joomla.asset.json' && json_decode($data['source']) === null) { 963 $this->setError(Text::_('COM_TEMPLATES_ERROR_ASSET_FILE_INVALID_JSON')); 964 965 return false; 966 } 967 968 $return = File::write($filePath, $data['source']); 969 970 if (!$return) { 971 $app->enqueueMessage(Text::sprintf('COM_TEMPLATES_ERROR_FAILED_TO_SAVE_FILENAME', $fileName), 'error'); 972 973 return false; 974 } 975 976 // Get the extension of the changed file. 977 $explodeArray = explode('.', $fileName); 978 $ext = end($explodeArray); 979 980 if ($ext == 'less') { 981 $app->enqueueMessage(Text::sprintf('COM_TEMPLATES_COMPILE_LESS', $fileName)); 982 } 983 984 return true; 985 } 986 987 /** 988 * Get overrides folder. 989 * 990 * @param string $name The name of override. 991 * @param string $path Location of override. 992 * 993 * @return object containing override name and path. 994 * 995 * @since 3.2 996 */ 997 public function getOverridesFolder($name, $path) 998 { 999 $folder = new \stdClass(); 1000 $folder->name = $name; 1001 $folder->path = base64_encode($path . $name); 1002 1003 return $folder; 1004 } 1005 1006 /** 1007 * Get a list of overrides. 1008 * 1009 * @return array containing overrides. 1010 * 1011 * @since 3.2 1012 */ 1013 public function getOverridesList() 1014 { 1015 if ($template = $this->getTemplate()) { 1016 $client = ApplicationHelper::getClientInfo($template->client_id); 1017 $componentPath = Path::clean($client->path . '/components/'); 1018 $modulePath = Path::clean($client->path . '/modules/'); 1019 $pluginPath = Path::clean(JPATH_ROOT . '/plugins/'); 1020 $layoutPath = Path::clean(JPATH_ROOT . '/layouts/'); 1021 $components = Folder::folders($componentPath); 1022 1023 foreach ($components as $component) { 1024 // Collect the folders with views 1025 $folders = Folder::folders($componentPath . '/' . $component, '^view[s]?$', false, true); 1026 $folders = array_merge($folders, Folder::folders($componentPath . '/' . $component, '^tmpl?$', false, true)); 1027 1028 if (!$folders) { 1029 continue; 1030 } 1031 1032 foreach ($folders as $folder) { 1033 // The subfolders are views 1034 $views = Folder::folders($folder); 1035 1036 foreach ($views as $view) { 1037 // The old scheme, if a view has a tmpl folder 1038 $path = $folder . '/' . $view . '/tmpl'; 1039 1040 // The new scheme, the views are directly in the component/tmpl folder 1041 if (!is_dir($path) && substr($folder, -4) == 'tmpl') { 1042 $path = $folder . '/' . $view; 1043 } 1044 1045 // Check if the folder exists 1046 if (!is_dir($path)) { 1047 continue; 1048 } 1049 1050 $result['components'][$component][] = $this->getOverridesFolder($view, Path::clean($folder . '/')); 1051 } 1052 } 1053 } 1054 1055 foreach (Folder::folders($pluginPath) as $pluginGroup) { 1056 foreach (Folder::folders($pluginPath . '/' . $pluginGroup) as $plugin) { 1057 if (file_exists($pluginPath . '/' . $pluginGroup . '/' . $plugin . '/tmpl/')) { 1058 $pluginLayoutPath = Path::clean($pluginPath . '/' . $pluginGroup . '/'); 1059 $result['plugins'][$pluginGroup][] = $this->getOverridesFolder($plugin, $pluginLayoutPath); 1060 } 1061 } 1062 } 1063 1064 $modules = Folder::folders($modulePath); 1065 1066 foreach ($modules as $module) { 1067 $result['modules'][] = $this->getOverridesFolder($module, $modulePath); 1068 } 1069 1070 $layoutFolders = Folder::folders($layoutPath); 1071 1072 foreach ($layoutFolders as $layoutFolder) { 1073 $layoutFolderPath = Path::clean($layoutPath . '/' . $layoutFolder . '/'); 1074 $layouts = Folder::folders($layoutFolderPath); 1075 1076 foreach ($layouts as $layout) { 1077 $result['layouts'][$layoutFolder][] = $this->getOverridesFolder($layout, $layoutFolderPath); 1078 } 1079 } 1080 1081 // Check for layouts in component folders 1082 foreach ($components as $component) { 1083 if (file_exists($componentPath . '/' . $component . '/layouts/')) { 1084 $componentLayoutPath = Path::clean($componentPath . '/' . $component . '/layouts/'); 1085 1086 if ($componentLayoutPath) { 1087 $layouts = Folder::folders($componentLayoutPath); 1088 1089 foreach ($layouts as $layout) { 1090 $result['layouts'][$component][] = $this->getOverridesFolder($layout, $componentLayoutPath); 1091 } 1092 } 1093 } 1094 } 1095 } 1096 1097 if (!empty($result)) { 1098 return $result; 1099 } 1100 } 1101 1102 /** 1103 * Create overrides. 1104 * 1105 * @param string $override The override location. 1106 * 1107 * @return boolean true if override creation is successful, false otherwise 1108 * 1109 * @since 3.2 1110 */ 1111 public function createOverride($override) 1112 { 1113 if ($template = $this->getTemplate()) { 1114 $app = Factory::getApplication(); 1115 $explodeArray = explode(DIRECTORY_SEPARATOR, $override); 1116 $name = end($explodeArray); 1117 $client = ApplicationHelper::getClientInfo($template->client_id); 1118 1119 if (stristr($name, 'mod_') != false) { 1120 $htmlPath = Path::clean($client->path . '/templates/' . $template->element . '/html/' . $name); 1121 } elseif (stristr($override, 'com_') != false) { 1122 $size = count($explodeArray); 1123 1124 $url = Path::clean($explodeArray[$size - 3] . '/' . $explodeArray[$size - 1]); 1125 1126 if ($explodeArray[$size - 2] == 'layouts') { 1127 $htmlPath = Path::clean($client->path . '/templates/' . $template->element . '/html/layouts/' . $url); 1128 } else { 1129 $htmlPath = Path::clean($client->path . '/templates/' . $template->element . '/html/' . $url); 1130 } 1131 } elseif (stripos($override, Path::clean(JPATH_ROOT . '/plugins/')) === 0) { 1132 $size = count($explodeArray); 1133 $layoutPath = Path::clean('plg_' . $explodeArray[$size - 2] . '_' . $explodeArray[$size - 1]); 1134 $htmlPath = Path::clean($client->path . '/templates/' . $template->element . '/html/' . $layoutPath); 1135 } else { 1136 $layoutPath = implode('/', array_slice($explodeArray, -2)); 1137 $htmlPath = Path::clean($client->path . '/templates/' . $template->element . '/html/layouts/' . $layoutPath); 1138 } 1139 1140 // Check Html folder, create if not exist 1141 if (!Folder::exists($htmlPath)) { 1142 if (!Folder::create($htmlPath)) { 1143 $app->enqueueMessage(Text::_('COM_TEMPLATES_FOLDER_ERROR'), 'error'); 1144 1145 return false; 1146 } 1147 } 1148 1149 if (stristr($name, 'mod_') != false) { 1150 $return = $this->createTemplateOverride(Path::clean($override . '/tmpl'), $htmlPath); 1151 } elseif (stristr($override, 'com_') != false && stristr($override, 'layouts') == false) { 1152 $path = $override . '/tmpl'; 1153 1154 // View can also be in the top level folder 1155 if (!is_dir($path)) { 1156 $path = $override; 1157 } 1158 1159 $return = $this->createTemplateOverride(Path::clean($path), $htmlPath); 1160 } elseif (stripos($override, Path::clean(JPATH_ROOT . '/plugins/')) === 0) { 1161 $return = $this->createTemplateOverride(Path::clean($override . '/tmpl'), $htmlPath); 1162 } else { 1163 $return = $this->createTemplateOverride($override, $htmlPath); 1164 } 1165 1166 if ($return) { 1167 $app->enqueueMessage(Text::_('COM_TEMPLATES_OVERRIDE_CREATED') . str_replace(JPATH_ROOT, '', $htmlPath)); 1168 1169 return true; 1170 } else { 1171 $app->enqueueMessage(Text::_('COM_TEMPLATES_OVERRIDE_FAILED'), 'error'); 1172 1173 return false; 1174 } 1175 } 1176 } 1177 1178 /** 1179 * Create override folder & file 1180 * 1181 * @param string $overridePath The override location 1182 * @param string $htmlPath The html location 1183 * 1184 * @return boolean True on success. False otherwise. 1185 */ 1186 public function createTemplateOverride($overridePath, $htmlPath) 1187 { 1188 $return = false; 1189 1190 if (empty($overridePath) || empty($htmlPath)) { 1191 return $return; 1192 } 1193 1194 // Get list of template folders 1195 $folders = Folder::folders($overridePath, null, true, true); 1196 1197 if (!empty($folders)) { 1198 foreach ($folders as $folder) { 1199 $htmlFolder = $htmlPath . str_replace($overridePath, '', $folder); 1200 1201 if (!Folder::exists($htmlFolder)) { 1202 Folder::create($htmlFolder); 1203 } 1204 } 1205 } 1206 1207 // Get list of template files (Only get *.php file for template file) 1208 $files = Folder::files($overridePath, '.php', true, true); 1209 1210 if (empty($files)) { 1211 return true; 1212 } 1213 1214 foreach ($files as $file) { 1215 $overrideFilePath = str_replace($overridePath, '', $file); 1216 $htmlFilePath = $htmlPath . $overrideFilePath; 1217 1218 if (File::exists($htmlFilePath)) { 1219 // Generate new unique file name base on current time 1220 $today = Factory::getDate(); 1221 $htmlFilePath = File::stripExt($htmlFilePath) . '-' . $today->format('Ymd-His') . '.' . File::getExt($htmlFilePath); 1222 } 1223 1224 $return = File::copy($file, $htmlFilePath, '', true); 1225 } 1226 1227 return $return; 1228 } 1229 1230 /** 1231 * Delete a particular file. 1232 * 1233 * @param string $file The relative location of the file. 1234 * 1235 * @return boolean True if file deletion is successful, false otherwise 1236 * 1237 * @since 3.2 1238 */ 1239 public function deleteFile($file) 1240 { 1241 if ($this->getTemplate()) { 1242 $app = Factory::getApplication(); 1243 $filePath = $this->getBasePath() . urldecode(base64_decode($file)); 1244 1245 $return = File::delete($filePath); 1246 1247 if (!$return) { 1248 $app->enqueueMessage(Text::_('COM_TEMPLATES_FILE_DELETE_ERROR'), 'error'); 1249 1250 return false; 1251 } 1252 1253 return true; 1254 } 1255 } 1256 1257 /** 1258 * Create new file. 1259 * 1260 * @param string $name The name of file. 1261 * @param string $type The extension of the file. 1262 * @param string $location Location for the new file. 1263 * 1264 * @return boolean true if file created successfully, false otherwise 1265 * 1266 * @since 3.2 1267 */ 1268 public function createFile($name, $type, $location) 1269 { 1270 if ($this->getTemplate()) { 1271 $app = Factory::getApplication(); 1272 $base = $this->getBasePath(); 1273 1274 if (file_exists(Path::clean($base . '/' . $location . '/' . $name . '.' . $type))) { 1275 $app->enqueueMessage(Text::_('COM_TEMPLATES_FILE_EXISTS'), 'error'); 1276 1277 return false; 1278 } 1279 1280 if (!fopen(Path::clean($base . '/' . $location . '/' . $name . '.' . $type), 'x')) { 1281 $app->enqueueMessage(Text::_('COM_TEMPLATES_FILE_CREATE_ERROR'), 'error'); 1282 1283 return false; 1284 } 1285 1286 // Check if the format is allowed and will be showed in the backend 1287 $check = $this->checkFormat($type); 1288 1289 // Add a message if we are not allowed to show this file in the backend. 1290 if (!$check) { 1291 $app->enqueueMessage(Text::sprintf('COM_TEMPLATES_WARNING_FORMAT_WILL_NOT_BE_VISIBLE', $type), 'warning'); 1292 } 1293 1294 return true; 1295 } 1296 } 1297 1298 /** 1299 * Upload new file. 1300 * 1301 * @param array $file The uploaded file array. 1302 * @param string $location Location for the new file. 1303 * 1304 * @return boolean True if file uploaded successfully, false otherwise 1305 * 1306 * @since 3.2 1307 */ 1308 public function uploadFile($file, $location) 1309 { 1310 if ($this->getTemplate()) { 1311 $app = Factory::getApplication(); 1312 $path = $this->getBasePath(); 1313 $fileName = File::makeSafe($file['name']); 1314 1315 $err = null; 1316 1317 if (!TemplateHelper::canUpload($file, $err)) { 1318 // Can't upload the file 1319 return false; 1320 } 1321 1322 if (file_exists(Path::clean($path . '/' . $location . '/' . $file['name']))) { 1323 $app->enqueueMessage(Text::_('COM_TEMPLATES_FILE_EXISTS'), 'error'); 1324 1325 return false; 1326 } 1327 1328 if (!File::upload($file['tmp_name'], Path::clean($path . '/' . $location . '/' . $fileName))) { 1329 $app->enqueueMessage(Text::_('COM_TEMPLATES_FILE_UPLOAD_ERROR'), 'error'); 1330 1331 return false; 1332 } 1333 1334 $url = Path::clean($location . '/' . $fileName); 1335 1336 return $url; 1337 } 1338 } 1339 1340 /** 1341 * Create new folder. 1342 * 1343 * @param string $name The name of the new folder. 1344 * @param string $location Location for the new folder. 1345 * 1346 * @return boolean True if override folder is created successfully, false otherwise 1347 * 1348 * @since 3.2 1349 */ 1350 public function createFolder($name, $location) 1351 { 1352 if ($this->getTemplate()) { 1353 $app = Factory::getApplication(); 1354 $path = Path::clean($location . '/'); 1355 $base = $this->getBasePath(); 1356 1357 if (file_exists(Path::clean($base . $path . $name))) { 1358 $app->enqueueMessage(Text::_('COM_TEMPLATES_FOLDER_EXISTS'), 'error'); 1359 1360 return false; 1361 } 1362 1363 if (!Folder::create(Path::clean($base . $path . $name))) { 1364 $app->enqueueMessage(Text::_('COM_TEMPLATES_FOLDER_CREATE_ERROR'), 'error'); 1365 1366 return false; 1367 } 1368 1369 return true; 1370 } 1371 } 1372 1373 /** 1374 * Delete a folder. 1375 * 1376 * @param string $location The name and location of the folder. 1377 * 1378 * @return boolean True if override folder is deleted successfully, false otherwise 1379 * 1380 * @since 3.2 1381 */ 1382 public function deleteFolder($location) 1383 { 1384 if ($this->getTemplate()) { 1385 $app = Factory::getApplication(); 1386 $base = $this->getBasePath(); 1387 $path = Path::clean($location . '/'); 1388 1389 if (!file_exists($base . $path)) { 1390 $app->enqueueMessage(Text::_('COM_TEMPLATES_FOLDER_NOT_EXISTS'), 'error'); 1391 1392 return false; 1393 } 1394 1395 $return = Folder::delete($base . $path); 1396 1397 if (!$return) { 1398 $app->enqueueMessage(Text::_('COM_TEMPLATES_FOLDER_DELETE_ERROR'), 'error'); 1399 1400 return false; 1401 } 1402 1403 return true; 1404 } 1405 } 1406 1407 /** 1408 * Rename a file. 1409 * 1410 * @param string $file The name and location of the old file 1411 * @param string $name The new name of the file. 1412 * 1413 * @return string Encoded string containing the new file location. 1414 * 1415 * @since 3.2 1416 */ 1417 public function renameFile($file, $name) 1418 { 1419 if ($this->getTemplate()) { 1420 $app = Factory::getApplication(); 1421 $path = $this->getBasePath(); 1422 $fileName = base64_decode($file); 1423 $explodeArray = explode('.', $fileName); 1424 $type = end($explodeArray); 1425 $explodeArray = explode('/', $fileName); 1426 $newName = str_replace(end($explodeArray), $name . '.' . $type, $fileName); 1427 1428 if (file_exists($path . $newName)) { 1429 $app->enqueueMessage(Text::_('COM_TEMPLATES_FILE_EXISTS'), 'error'); 1430 1431 return false; 1432 } 1433 1434 if (!rename($path . $fileName, $path . $newName)) { 1435 $app->enqueueMessage(Text::_('COM_TEMPLATES_FILE_RENAME_ERROR'), 'error'); 1436 1437 return false; 1438 } 1439 1440 return base64_encode($newName); 1441 } 1442 } 1443 1444 /** 1445 * Get an image address, height and width. 1446 * 1447 * @return array an associative array containing image address, height and width. 1448 * 1449 * @since 3.2 1450 */ 1451 public function getImage() 1452 { 1453 if ($this->getTemplate()) { 1454 $app = Factory::getApplication(); 1455 $fileName = base64_decode($app->input->get('file')); 1456 $path = $this->getBasePath(); 1457 1458 $uri = Uri::root(false) . ltrim(str_replace(JPATH_ROOT, '', $this->getBasePath()), '/'); 1459 1460 if (file_exists(Path::clean($path . $fileName))) { 1461 $JImage = new Image(Path::clean($path . $fileName)); 1462 $image['address'] = $uri . $fileName; 1463 $image['path'] = $fileName; 1464 $image['height'] = $JImage->getHeight(); 1465 $image['width'] = $JImage->getWidth(); 1466 } else { 1467 $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_IMAGE_FILE_NOT_FOUND'), 'error'); 1468 1469 return false; 1470 } 1471 1472 return $image; 1473 } 1474 } 1475 1476 /** 1477 * Crop an image. 1478 * 1479 * @param string $file The name and location of the file 1480 * @param string $w width. 1481 * @param string $h height. 1482 * @param string $x x-coordinate. 1483 * @param string $y y-coordinate. 1484 * 1485 * @return boolean true if image cropped successfully, false otherwise. 1486 * 1487 * @since 3.2 1488 */ 1489 public function cropImage($file, $w, $h, $x, $y) 1490 { 1491 if ($this->getTemplate()) { 1492 $app = Factory::getApplication(); 1493 $path = $this->getBasePath() . base64_decode($file); 1494 1495 try { 1496 $image = new Image($path); 1497 $properties = $image->getImageFileProperties($path); 1498 1499 switch ($properties->mime) { 1500 case 'image/webp': 1501 $imageType = \IMAGETYPE_WEBP; 1502 break; 1503 case 'image/png': 1504 $imageType = \IMAGETYPE_PNG; 1505 break; 1506 case 'image/gif': 1507 $imageType = \IMAGETYPE_GIF; 1508 break; 1509 default: 1510 $imageType = \IMAGETYPE_JPEG; 1511 } 1512 1513 $image->crop($w, $h, $x, $y, false); 1514 $image->toFile($path, $imageType); 1515 1516 return true; 1517 } catch (\Exception $e) { 1518 $app->enqueueMessage($e->getMessage(), 'error'); 1519 } 1520 } 1521 } 1522 1523 /** 1524 * Resize an image. 1525 * 1526 * @param string $file The name and location of the file 1527 * @param string $width The new width of the image. 1528 * @param string $height The new height of the image. 1529 * 1530 * @return boolean true if image resize successful, false otherwise. 1531 * 1532 * @since 3.2 1533 */ 1534 public function resizeImage($file, $width, $height) 1535 { 1536 if ($this->getTemplate()) { 1537 $app = Factory::getApplication(); 1538 $path = $this->getBasePath() . base64_decode($file); 1539 1540 try { 1541 $image = new Image($path); 1542 $properties = $image->getImageFileProperties($path); 1543 1544 switch ($properties->mime) { 1545 case 'image/webp': 1546 $imageType = \IMAGETYPE_WEBP; 1547 break; 1548 case 'image/png': 1549 $imageType = \IMAGETYPE_PNG; 1550 break; 1551 case 'image/gif': 1552 $imageType = \IMAGETYPE_GIF; 1553 break; 1554 default: 1555 $imageType = \IMAGETYPE_JPEG; 1556 } 1557 1558 $image->resize($width, $height, false, Image::SCALE_FILL); 1559 $image->toFile($path, $imageType); 1560 1561 return true; 1562 } catch (\Exception $e) { 1563 $app->enqueueMessage($e->getMessage(), 'error'); 1564 } 1565 } 1566 } 1567 1568 /** 1569 * Template preview. 1570 * 1571 * @return object object containing the id of the template. 1572 * 1573 * @since 3.2 1574 */ 1575 public function getPreview() 1576 { 1577 $app = Factory::getApplication(); 1578 $db = $this->getDatabase(); 1579 $query = $db->getQuery(true); 1580 1581 $query->select($db->quoteName(['id', 'client_id'])); 1582 $query->from($db->quoteName('#__template_styles')); 1583 $query->where($db->quoteName('template') . ' = :template') 1584 ->bind(':template', $this->template->element); 1585 1586 $db->setQuery($query); 1587 1588 try { 1589 $result = $db->loadObject(); 1590 } catch (\RuntimeException $e) { 1591 $app->enqueueMessage($e->getMessage(), 'warning'); 1592 } 1593 1594 if (empty($result)) { 1595 $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_EXTENSION_RECORD_NOT_FOUND'), 'warning'); 1596 } else { 1597 return $result; 1598 } 1599 } 1600 1601 /** 1602 * Rename a file. 1603 * 1604 * @return mixed array on success, false on failure 1605 * 1606 * @since 3.2 1607 */ 1608 public function getFont() 1609 { 1610 if ($template = $this->getTemplate()) { 1611 $app = Factory::getApplication(); 1612 $client = ApplicationHelper::getClientInfo($template->client_id); 1613 $relPath = base64_decode($app->input->get('file')); 1614 $explodeArray = explode('/', $relPath); 1615 $fileName = end($explodeArray); 1616 $path = $this->getBasePath() . base64_decode($app->input->get('file')); 1617 1618 if (stristr($client->path, 'administrator') == false) { 1619 $folder = '/templates/'; 1620 } else { 1621 $folder = '/administrator/templates/'; 1622 } 1623 1624 $uri = Uri::root(true) . $folder . $template->element; 1625 1626 if (file_exists(Path::clean($path))) { 1627 $font['address'] = $uri . $relPath; 1628 1629 $font['rel_path'] = $relPath; 1630 1631 $font['name'] = $fileName; 1632 } else { 1633 $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_FONT_FILE_NOT_FOUND'), 'error'); 1634 1635 return false; 1636 } 1637 1638 return $font; 1639 } 1640 } 1641 1642 /** 1643 * Copy a file. 1644 * 1645 * @param string $newName The name of the copied file 1646 * @param string $location The final location where the file is to be copied 1647 * @param string $file The name and location of the file 1648 * 1649 * @return boolean true if image resize successful, false otherwise. 1650 * 1651 * @since 3.2 1652 */ 1653 public function copyFile($newName, $location, $file) 1654 { 1655 if ($this->getTemplate()) { 1656 $app = Factory::getApplication(); 1657 $relPath = base64_decode($file); 1658 $explodeArray = explode('.', $relPath); 1659 $ext = end($explodeArray); 1660 $path = $this->getBasePath(); 1661 $newPath = Path::clean($path . $location . '/' . $newName . '.' . $ext); 1662 1663 if (file_exists($newPath)) { 1664 $app->enqueueMessage(Text::_('COM_TEMPLATES_FILE_EXISTS'), 'error'); 1665 1666 return false; 1667 } 1668 1669 if (File::copy($path . $relPath, $newPath)) { 1670 $app->enqueueMessage(Text::sprintf('COM_TEMPLATES_FILE_COPY_SUCCESS', $newName . '.' . $ext)); 1671 1672 return true; 1673 } else { 1674 return false; 1675 } 1676 } 1677 } 1678 1679 /** 1680 * Get the compressed files. 1681 * 1682 * @return array if file exists, false otherwise 1683 * 1684 * @since 3.2 1685 */ 1686 public function getArchive() 1687 { 1688 if ($this->getTemplate()) { 1689 $app = Factory::getApplication(); 1690 $path = $this->getBasePath() . base64_decode($app->input->get('file')); 1691 1692 if (file_exists(Path::clean($path))) { 1693 $files = array(); 1694 $zip = new \ZipArchive(); 1695 1696 if ($zip->open($path) === true) { 1697 for ($i = 0; $i < $zip->numFiles; $i++) { 1698 $entry = $zip->getNameIndex($i); 1699 $files[] = $entry; 1700 } 1701 } else { 1702 $app->enqueueMessage(Text::_('COM_TEMPLATES_FILE_ARCHIVE_OPEN_FAIL'), 'error'); 1703 1704 return false; 1705 } 1706 } else { 1707 $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_FONT_FILE_NOT_FOUND'), 'error'); 1708 1709 return false; 1710 } 1711 1712 return $files; 1713 } 1714 } 1715 1716 /** 1717 * Extract contents of an archive file. 1718 * 1719 * @param string $file The name and location of the file 1720 * 1721 * @return boolean true if image extraction is successful, false otherwise. 1722 * 1723 * @since 3.2 1724 */ 1725 public function extractArchive($file) 1726 { 1727 if ($this->getTemplate()) { 1728 $app = Factory::getApplication(); 1729 $relPath = base64_decode($file); 1730 $explodeArray = explode('/', $relPath); 1731 $fileName = end($explodeArray); 1732 $path = $this->getBasePath() . base64_decode($file); 1733 1734 if (file_exists(Path::clean($path . '/' . $fileName))) { 1735 $zip = new \ZipArchive(); 1736 1737 if ($zip->open(Path::clean($path . '/' . $fileName)) === true) { 1738 for ($i = 0; $i < $zip->numFiles; $i++) { 1739 $entry = $zip->getNameIndex($i); 1740 1741 if (file_exists(Path::clean($path . '/' . $entry))) { 1742 $app->enqueueMessage(Text::_('COM_TEMPLATES_FILE_ARCHIVE_EXISTS'), 'error'); 1743 1744 return false; 1745 } 1746 } 1747 1748 $zip->extractTo($path); 1749 1750 return true; 1751 } else { 1752 $app->enqueueMessage(Text::_('COM_TEMPLATES_FILE_ARCHIVE_OPEN_FAIL'), 'error'); 1753 1754 return false; 1755 } 1756 } else { 1757 $app->enqueueMessage(Text::_('COM_TEMPLATES_FILE_ARCHIVE_NOT_FOUND'), 'error'); 1758 1759 return false; 1760 } 1761 } 1762 } 1763 1764 /** 1765 * Check if the extension is allowed and will be shown in the template manager 1766 * 1767 * @param string $ext The extension to check if it is allowed 1768 * 1769 * @return boolean true if the extension is allowed false otherwise 1770 * 1771 * @since 3.6.0 1772 */ 1773 protected function checkFormat($ext) 1774 { 1775 if (!isset($this->allowedFormats)) { 1776 $params = ComponentHelper::getParams('com_templates'); 1777 $imageTypes = explode(',', $params->get('image_formats')); 1778 $sourceTypes = explode(',', $params->get('source_formats')); 1779 $fontTypes = explode(',', $params->get('font_formats')); 1780 $archiveTypes = explode(',', $params->get('compressed_formats')); 1781 1782 $this->allowedFormats = array_merge($imageTypes, $sourceTypes, $fontTypes, $archiveTypes); 1783 $this->allowedFormats = array_map('strtolower', $this->allowedFormats); 1784 } 1785 1786 return in_array(strtolower($ext), $this->allowedFormats); 1787 } 1788 1789 /** 1790 * Method to get a list of all the files to edit in a template's media folder. 1791 * 1792 * @return array A nested array of relevant files. 1793 * 1794 * @since 4.1.0 1795 */ 1796 public function getMediaFiles() 1797 { 1798 $result = []; 1799 $template = $this->getTemplate(); 1800 1801 if (!isset($template->xmldata)) { 1802 $template->xmldata = TemplatesHelper::parseXMLTemplateFile($template->client_id === 0 ? JPATH_ROOT : JPATH_ROOT . '/administrator', $template->name); 1803 } 1804 1805 if (!isset($template->xmldata->inheritable) || (isset($template->xmldata->parent) && $template->xmldata->parent === '')) { 1806 return $result; 1807 } 1808 1809 $app = Factory::getApplication(); 1810 $path = Path::clean(JPATH_ROOT . '/media/templates/' . ($template->client_id === 0 ? 'site' : 'administrator') . '/' . $template->element . '/'); 1811 $this->mediaElement = $path; 1812 1813 if (!is_writable($path)) { 1814 $app->enqueueMessage(Text::_('COM_TEMPLATES_DIRECTORY_NOT_WRITABLE'), 'error'); 1815 } 1816 1817 if (is_dir($path)) { 1818 $result = $this->getDirectoryTree($path); 1819 } 1820 1821 return $result; 1822 } 1823 1824 /** 1825 * Method to resolve the base folder. 1826 * 1827 * @return string The absolute path for the base. 1828 * 1829 * @since 4.1.0 1830 */ 1831 private function getBasePath() 1832 { 1833 $app = Factory::getApplication(); 1834 $isMedia = $app->input->getInt('isMedia', 0); 1835 1836 return $isMedia ? JPATH_ROOT . '/media/templates/' . ($this->template->client_id === 0 ? 'site' : 'administrator') . '/' . $this->template->element : 1837 JPATH_ROOT . '/' . ($this->template->client_id === 0 ? '' : 'administrator/') . 'templates/' . $this->template->element; 1838 } 1839 1840 /** 1841 * Method to create the templateDetails.xml for the child template 1842 * 1843 * @return boolean true if name is not used, false otherwise 1844 * 1845 * @since 4.1.0 1846 */ 1847 public function child() 1848 { 1849 $app = Factory::getApplication(); 1850 $template = $this->getTemplate(); 1851 1852 if (!(array) $template) { 1853 $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_COULD_NOT_WRITE'), 'error'); 1854 1855 return false; 1856 } 1857 1858 $client = ApplicationHelper::getClientInfo($template->client_id); 1859 $fromPath = Path::clean($client->path . '/templates/' . $template->element . '/templateDetails.xml'); 1860 1861 // Delete new folder if it exists 1862 $toPath = $this->getState('to_path'); 1863 1864 if (Folder::exists($toPath)) { 1865 if (!Folder::delete($toPath)) { 1866 $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_COULD_NOT_WRITE'), 'error'); 1867 1868 return false; 1869 } 1870 } else { 1871 if (!Folder::create($toPath)) { 1872 $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_COULD_NOT_WRITE'), 'error'); 1873 1874 return false; 1875 } 1876 } 1877 1878 // Copy the template definition from the parent template 1879 if (!File::copy($fromPath, $toPath . '/templateDetails.xml')) { 1880 return false; 1881 } 1882 1883 // Check manifest for additional files 1884 $newName = strtolower($this->getState('new_name')); 1885 $template = $this->getTemplate(); 1886 1887 // Edit XML file 1888 $xmlFile = Path::clean($this->getState('to_path') . '/templateDetails.xml'); 1889 1890 if (!File::exists($xmlFile)) { 1891 $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_INVALID_FROM_NAME'), 'error'); 1892 1893 return false; 1894 } 1895 1896 try { 1897 $xml = simplexml_load_string(file_get_contents($xmlFile)); 1898 } catch (\Exception $e) { 1899 $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_COULD_NOT_READ'), 'error'); 1900 1901 return false; 1902 } 1903 1904 $user = Factory::getUser(); 1905 unset($xml->languages); 1906 unset($xml->media); 1907 unset($xml->files); 1908 unset($xml->parent); 1909 unset($xml->inheritable); 1910 1911 // Remove the update parts 1912 unset($xml->update); 1913 unset($xml->updateservers); 1914 1915 if (isset($xml->creationDate)) { 1916 $xml->creationDate = (new Date('now'))->format('F Y'); 1917 } else { 1918 $xml->addChild('creationDate', (new Date('now'))->format('F Y')); 1919 } 1920 1921 if (isset($xml->author)) { 1922 $xml->author = $user->name; 1923 } else { 1924 $xml->addChild('author', $user->name); 1925 } 1926 1927 if (isset($xml->authorEmail)) { 1928 $xml->authorEmail = $user->email; 1929 } else { 1930 $xml->addChild('authorEmail', $user->email); 1931 } 1932 1933 $files = $xml->addChild('files'); 1934 $files->addChild('filename', 'templateDetails.xml'); 1935 1936 // Media folder 1937 $media = $xml->addChild('media'); 1938 $media->addAttribute('folder', 'media'); 1939 $media->addAttribute('destination', 'templates/' . ($template->client_id === 0 ? 'site/' : 'administrator/') . $template->element . '_' . $newName); 1940 $media->addChild('folder', 'css'); 1941 $media->addChild('folder', 'js'); 1942 $media->addChild('folder', 'images'); 1943 $media->addChild('folder', 'html'); 1944 $media->addChild('folder', 'scss'); 1945 1946 $xml->name = $template->element . '_' . $newName; 1947 $xml->inheritable = 0; 1948 $files = $xml->addChild('parent', $template->element); 1949 1950 $dom = new \DOMDocument(); 1951 $dom->preserveWhiteSpace = false; 1952 $dom->formatOutput = true; 1953 $dom->loadXML($xml->asXML()); 1954 1955 $result = File::write($xmlFile, $dom->saveXML()); 1956 1957 if (!$result) { 1958 $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_COULD_NOT_WRITE'), 'error'); 1959 1960 return false; 1961 } 1962 1963 // Create an empty media folder structure 1964 if ( 1965 !Folder::create($toPath . '/media') 1966 || !Folder::create($toPath . '/media/css') 1967 || !Folder::create($toPath . '/media/js') 1968 || !Folder::create($toPath . '/media/images') 1969 || !Folder::create($toPath . '/media/html/tinymce') 1970 || !Folder::create($toPath . '/media/scss') 1971 ) { 1972 return false; 1973 } 1974 1975 return true; 1976 } 1977 1978 /** 1979 * Method to get the parent template existing styles 1980 * 1981 * @return array array of id,titles of the styles 1982 * 1983 * @since 4.1.3 1984 */ 1985 public function getAllTemplateStyles() 1986 { 1987 $template = $this->getTemplate(); 1988 1989 if (empty($template->xmldata->inheritable)) { 1990 return []; 1991 } 1992 1993 $db = $this->getDatabase(); 1994 $query = $db->getQuery(true); 1995 1996 $query->select($db->quoteName(['id', 'title'])) 1997 ->from($db->quoteName('#__template_styles')) 1998 ->where($db->quoteName('client_id') . ' = :client_id', 'AND') 1999 ->where($db->quoteName('template') . ' = :template') 2000 ->orWhere($db->quoteName('parent') . ' = :parent') 2001 ->bind(':client_id', $template->client_id, ParameterType::INTEGER) 2002 ->bind(':template', $template->element) 2003 ->bind(':parent', $template->element); 2004 2005 $db->setQuery($query); 2006 2007 return $db->loadObjectList(); 2008 } 2009 2010 /** 2011 * Method to copy selected styles to the child template 2012 * 2013 * @return boolean true if name is not used, false otherwise 2014 * 2015 * @since 4.1.3 2016 */ 2017 public function copyStyles() 2018 { 2019 $app = Factory::getApplication(); 2020 $template = $this->getTemplate(); 2021 $newName = strtolower($this->getState('new_name')); 2022 $applyStyles = $this->getState('stylesToCopy'); 2023 2024 // Get a db connection. 2025 $db = $this->getDatabase(); 2026 2027 // Create a new query object. 2028 $query = $db->getQuery(true); 2029 2030 $query->select($db->quoteName(['title', 'params'])) 2031 ->from($db->quoteName('#__template_styles')) 2032 ->whereIn($db->quoteName('id'), ArrayHelper::toInteger($applyStyles)); 2033 // Reset the query using our newly populated query object. 2034 $db->setQuery($query); 2035 2036 try { 2037 $parentStyle = $db->loadObjectList(); 2038 } catch (\Exception $e) { 2039 $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_STYLE_NOT_FOUND'), 'error'); 2040 2041 return false; 2042 } 2043 2044 foreach ($parentStyle as $style) { 2045 $query = $db->getQuery(true); 2046 $styleName = Text::sprintf('COM_TEMPLATES_COPY_CHILD_TEMPLATE_STYLES', ucfirst($template->element . '_' . $newName), $style->title); 2047 2048 // Insert columns and values 2049 $columns = ['id', 'template', 'client_id', 'home', 'title', 'inheritable', 'parent', 'params']; 2050 $values = [0, $db->quote($template->element . '_' . $newName), (int) $template->client_id, $db->quote('0'), $db->quote($styleName), 0, $db->quote($template->element), $db->quote($style->params)]; 2051 2052 $query 2053 ->insert($db->quoteName('#__template_styles')) 2054 ->columns($db->quoteName($columns)) 2055 ->values(implode(',', $values)); 2056 2057 $db->setQuery($query); 2058 2059 try { 2060 $db->execute(); 2061 } catch (\Exception $e) { 2062 $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_COULD_NOT_READ'), 'error'); 2063 2064 return false; 2065 } 2066 } 2067 2068 return true; 2069 } 2070 }
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 |