[ Index ] |
PHP Cross Reference of Joomla 4.2.2 documentation |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * @package Joomla.Plugin 5 * @subpackage Filesystem.Local 6 * 7 * @copyright (C) 2016 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\Plugin\Filesystem\Local\Adapter; 12 13 use Joomla\CMS\Date\Date; 14 use Joomla\CMS\Factory; 15 use Joomla\CMS\Filesystem\File; 16 use Joomla\CMS\Filesystem\Folder; 17 use Joomla\CMS\Filesystem\Path; 18 use Joomla\CMS\Helper\MediaHelper; 19 use Joomla\CMS\HTML\HTMLHelper; 20 use Joomla\CMS\Image\Exception\UnparsableImageException; 21 use Joomla\CMS\Image\Image; 22 use Joomla\CMS\Language\Text; 23 use Joomla\CMS\String\PunycodeHelper; 24 use Joomla\CMS\Uri\Uri; 25 use Joomla\Component\Media\Administrator\Adapter\AdapterInterface; 26 use Joomla\Component\Media\Administrator\Exception\FileNotFoundException; 27 use Joomla\Component\Media\Administrator\Exception\InvalidPathException; 28 29 // phpcs:disable PSR1.Files.SideEffects 30 \defined('_JEXEC') or die; 31 // phpcs:enable PSR1.Files.SideEffects 32 33 /** 34 * Local file adapter. 35 * 36 * @since 4.0.0 37 */ 38 class LocalAdapter implements AdapterInterface 39 { 40 /** 41 * The root path to gather file information from. 42 * 43 * @var string 44 * 45 * @since 4.0.0 46 */ 47 private $rootPath = null; 48 49 /** 50 * The file_path of media directory related to site 51 * 52 * @var string 53 * 54 * @since 4.0.0 55 */ 56 private $filePath = null; 57 58 /** 59 * The absolute root path in the local file system. 60 * 61 * @param string $rootPath The root path 62 * @param string $filePath The file path of media folder 63 * 64 * @since 4.0.0 65 */ 66 public function __construct(string $rootPath, string $filePath) 67 { 68 if (!file_exists($rootPath)) { 69 throw new \InvalidArgumentException(); 70 } 71 72 $this->rootPath = Path::clean(realpath($rootPath), '/'); 73 $this->filePath = $filePath; 74 } 75 76 /** 77 * Returns the requested file or folder. The returned object 78 * has the following properties available: 79 * - type: The type can be file or dir 80 * - name: The name of the file 81 * - path: The relative path to the root 82 * - extension: The file extension 83 * - size: The size of the file 84 * - create_date: The date created 85 * - modified_date: The date modified 86 * - mime_type: The mime type 87 * - width: The width, when available 88 * - height: The height, when available 89 * 90 * If the path doesn't exist a FileNotFoundException is thrown. 91 * 92 * @param string $path The path to the file or folder 93 * 94 * @return \stdClass 95 * 96 * @since 4.0.0 97 * @throws \Exception 98 */ 99 public function getFile(string $path = '/'): \stdClass 100 { 101 // Get the local path 102 $basePath = $this->getLocalPath($path); 103 104 // Check if file exists 105 if (!file_exists($basePath)) { 106 throw new FileNotFoundException(); 107 } 108 109 return $this->getPathInformation($basePath); 110 } 111 112 /** 113 * Returns the folders and files for the given path. The returned objects 114 * have the following properties available: 115 * - type: The type can be file or dir 116 * - name: The name of the file 117 * - path: The relative path to the root 118 * - extension: The file extension 119 * - size: The size of the file 120 * - create_date: The date created 121 * - modified_date: The date modified 122 * - mime_type: The mime type 123 * - width: The width, when available 124 * - height: The height, when available 125 * 126 * If the path doesn't exist a FileNotFoundException is thrown. 127 * 128 * @param string $path The folder 129 * 130 * @return \stdClass[] 131 * 132 * @since 4.0.0 133 * @throws \Exception 134 */ 135 public function getFiles(string $path = '/'): array 136 { 137 // Get the local path 138 $basePath = $this->getLocalPath($path); 139 140 // Check if file exists 141 if (!file_exists($basePath)) { 142 throw new FileNotFoundException(); 143 } 144 145 // Check if the path points to a file 146 if (is_file($basePath)) { 147 return [$this->getPathInformation($basePath)]; 148 } 149 150 // The data to return 151 $data = []; 152 153 // Read the folders 154 foreach (Folder::folders($basePath) as $folder) { 155 $data[] = $this->getPathInformation(Path::clean($basePath . '/' . $folder)); 156 } 157 158 // Read the files 159 foreach (Folder::files($basePath) as $file) { 160 $data[] = $this->getPathInformation(Path::clean($basePath . '/' . $file)); 161 } 162 163 // Return the data 164 return $data; 165 } 166 167 /** 168 * Returns a resource to download the path. 169 * 170 * @param string $path The path to download 171 * 172 * @return resource 173 * 174 * @since 4.0.0 175 * @throws \Exception 176 */ 177 public function getResource(string $path) 178 { 179 return fopen($this->rootPath . '/' . $path, 'r'); 180 } 181 182 /** 183 * Creates a folder with the given name in the given path. 184 * 185 * It returns the new folder name. This allows the implementation 186 * classes to normalise the file name. 187 * 188 * @param string $name The name 189 * @param string $path The folder 190 * 191 * @return string 192 * 193 * @since 4.0.0 194 * @throws \Exception 195 */ 196 public function createFolder(string $name, string $path): string 197 { 198 $name = $this->getSafeName($name); 199 200 $localPath = $this->getLocalPath($path . '/' . $name); 201 202 Folder::create($localPath); 203 204 return $name; 205 } 206 207 /** 208 * Creates a file with the given name in the given path with the data. 209 * 210 * It returns the new file name. This allows the implementation 211 * classes to normalise the file name. 212 * 213 * @param string $name The name 214 * @param string $path The folder 215 * @param string $data The data 216 * 217 * @return string 218 * 219 * @since 4.0.0 220 * @throws \Exception 221 */ 222 public function createFile(string $name, string $path, $data): string 223 { 224 $name = $this->getSafeName($name); 225 226 $localPath = $this->getLocalPath($path . '/' . $name); 227 228 $this->checkContent($localPath, $data); 229 230 File::write($localPath, $data); 231 232 return $name; 233 } 234 235 /** 236 * Updates the file with the given name in the given path with the data. 237 * 238 * @param string $name The name 239 * @param string $path The folder 240 * @param string $data The data 241 * 242 * @return void 243 * 244 * @since 4.0.0 245 * @throws \Exception 246 */ 247 public function updateFile(string $name, string $path, $data) 248 { 249 $localPath = $this->getLocalPath($path . '/' . $name); 250 251 if (!File::exists($localPath)) { 252 throw new FileNotFoundException(); 253 } 254 255 $this->checkContent($localPath, $data); 256 257 File::write($localPath, $data); 258 } 259 260 /** 261 * Deletes the folder or file of the given path. 262 * 263 * @param string $path The path to the file or folder 264 * 265 * @return void 266 * 267 * @since 4.0.0 268 * @throws \Exception 269 */ 270 public function delete(string $path) 271 { 272 $localPath = $this->getLocalPath($path); 273 274 if (is_file($localPath)) { 275 if (!File::exists($localPath)) { 276 throw new FileNotFoundException(); 277 } 278 279 $success = File::delete($localPath); 280 } else { 281 if (!Folder::exists($localPath)) { 282 throw new FileNotFoundException(); 283 } 284 285 $success = Folder::delete($localPath); 286 } 287 288 if (!$success) { 289 throw new \Exception('Delete not possible!'); 290 } 291 } 292 293 /** 294 * Returns the folder or file information for the given path. The returned object 295 * has the following properties: 296 * - type: The type can be file or dir 297 * - name: The name of the file 298 * - path: The relative path to the root 299 * - extension: The file extension 300 * - size: The size of the file 301 * - create_date: The date created 302 * - modified_date: The date modified 303 * - mime_type: The mime type 304 * - width: The width, when available 305 * - height: The height, when available 306 * - thumb_path The thumbnail path of file, when available 307 * 308 * @param string $path The folder 309 * 310 * @return \stdClass 311 * 312 * @since 4.0.0 313 */ 314 private function getPathInformation(string $path): \stdClass 315 { 316 // Prepare the path 317 $path = Path::clean($path, '/'); 318 319 // The boolean if it is a dir 320 $isDir = is_dir($path); 321 322 $createDate = $this->getDate(filectime($path)); 323 $modifiedDate = $this->getDate(filemtime($path)); 324 325 // Set the values 326 $obj = new \stdClass(); 327 $obj->type = $isDir ? 'dir' : 'file'; 328 $obj->name = $this->getFileName($path); 329 $obj->path = str_replace($this->rootPath, '', $path); 330 $obj->extension = !$isDir ? File::getExt($obj->name) : ''; 331 $obj->size = !$isDir ? filesize($path) : ''; 332 $obj->mime_type = MediaHelper::getMimeType($path, MediaHelper::isImage($obj->name)); 333 $obj->width = 0; 334 $obj->height = 0; 335 336 // Dates 337 $obj->create_date = $createDate->format('c', true); 338 $obj->create_date_formatted = HTMLHelper::_('date', $createDate, Text::_('DATE_FORMAT_LC5')); 339 $obj->modified_date = $modifiedDate->format('c', true); 340 $obj->modified_date_formatted = HTMLHelper::_('date', $modifiedDate, Text::_('DATE_FORMAT_LC5')); 341 342 if (MediaHelper::isImage($obj->name)) { 343 // Get the image properties 344 try { 345 $props = Image::getImageFileProperties($path); 346 $obj->width = $props->width; 347 $obj->height = $props->height; 348 349 // @todo : Change this path to an actual thumbnail path 350 $obj->thumb_path = $this->getUrl($obj->path); 351 } catch (UnparsableImageException $e) { 352 // Ignore the exception - it's an image that we don't know how to parse right now 353 } 354 } 355 356 return $obj; 357 } 358 359 /** 360 * Returns a Date with the correct Joomla timezone for the given date. 361 * 362 * @param string $date The date to create a Date from 363 * 364 * @return Date 365 * 366 * @since 4.0.0 367 */ 368 private function getDate($date = null): Date 369 { 370 $dateObj = Factory::getDate($date); 371 372 $timezone = Factory::getApplication()->get('offset'); 373 $user = Factory::getUser(); 374 375 if ($user->id) { 376 $userTimezone = $user->getParam('timezone'); 377 378 if (!empty($userTimezone)) { 379 $timezone = $userTimezone; 380 } 381 } 382 383 if ($timezone) { 384 $dateObj->setTimezone(new \DateTimeZone($timezone)); 385 } 386 387 return $dateObj; 388 } 389 390 /** 391 * Copies a file or folder from source to destination. 392 * 393 * It returns the new destination path. This allows the implementation 394 * classes to normalise the file name. 395 * 396 * @param string $sourcePath The source path 397 * @param string $destinationPath The destination path 398 * @param bool $force Force to overwrite 399 * 400 * @return string 401 * 402 * @since 4.0.0 403 * @throws \Exception 404 */ 405 public function copy(string $sourcePath, string $destinationPath, bool $force = false): string 406 { 407 // Get absolute paths from relative paths 408 $sourcePath = Path::clean($this->getLocalPath($sourcePath), '/'); 409 $destinationPath = Path::clean($this->getLocalPath($destinationPath), '/'); 410 411 if (!file_exists($sourcePath)) { 412 throw new FileNotFoundException(); 413 } 414 415 $name = $this->getFileName($destinationPath); 416 $safeName = $this->getSafeName($name); 417 418 // If the safe name is different normalise the file name 419 if ($safeName != $name) { 420 $destinationPath = substr($destinationPath, 0, -\strlen($name)) . '/' . $safeName; 421 } 422 423 // Check for existence of the file in destination 424 // if it does not exists simply copy source to destination 425 if (is_dir($sourcePath)) { 426 $this->copyFolder($sourcePath, $destinationPath, $force); 427 } else { 428 $this->copyFile($sourcePath, $destinationPath, $force); 429 } 430 431 // Get the relative path 432 $destinationPath = str_replace($this->rootPath, '', $destinationPath); 433 434 return $destinationPath; 435 } 436 437 /** 438 * Copies a file 439 * 440 * @param string $sourcePath Source path of the file or directory 441 * @param string $destinationPath Destination path of the file or directory 442 * @param bool $force Set true to overwrite files or directories 443 * 444 * @return void 445 * 446 * @since 4.0.0 447 * @throws \Exception 448 */ 449 private function copyFile(string $sourcePath, string $destinationPath, bool $force = false) 450 { 451 if (is_dir($destinationPath)) { 452 // If the destination is a folder we create a file with the same name as the source 453 $destinationPath = $destinationPath . '/' . $this->getFileName($sourcePath); 454 } 455 456 if (file_exists($destinationPath) && !$force) { 457 throw new \Exception('Copy file is not possible as destination file already exists'); 458 } 459 460 if (!File::copy($sourcePath, $destinationPath)) { 461 throw new \Exception('Copy file is not possible'); 462 } 463 } 464 465 /** 466 * Copies a folder 467 * 468 * @param string $sourcePath Source path of the file or directory 469 * @param string $destinationPath Destination path of the file or directory 470 * @param bool $force Set true to overwrite files or directories 471 * 472 * @return void 473 * 474 * @since 4.0.0 475 * @throws \Exception 476 */ 477 private function copyFolder(string $sourcePath, string $destinationPath, bool $force = false) 478 { 479 if (file_exists($destinationPath) && !$force) { 480 throw new \Exception('Copy folder is not possible as destination folder already exists'); 481 } 482 483 if (is_file($destinationPath) && !File::delete($destinationPath)) { 484 throw new \Exception('Copy folder is not possible as destination folder is a file and can not be deleted'); 485 } 486 487 if (!Folder::copy($sourcePath, $destinationPath, '', $force)) { 488 throw new \Exception('Copy folder is not possible'); 489 } 490 } 491 492 /** 493 * Moves a file or folder from source to destination. 494 * 495 * It returns the new destination path. This allows the implementation 496 * classes to normalise the file name. 497 * 498 * @param string $sourcePath The source path 499 * @param string $destinationPath The destination path 500 * @param bool $force Force to overwrite 501 * 502 * @return string 503 * 504 * @since 4.0.0 505 * @throws \Exception 506 */ 507 public function move(string $sourcePath, string $destinationPath, bool $force = false): string 508 { 509 // Get absolute paths from relative paths 510 $sourcePath = Path::clean($this->getLocalPath($sourcePath), '/'); 511 $destinationPath = Path::clean($this->getLocalPath($destinationPath), '/'); 512 513 if (!file_exists($sourcePath)) { 514 throw new FileNotFoundException(); 515 } 516 517 $name = $this->getFileName($destinationPath); 518 $safeName = $this->getSafeName($name); 519 520 // If transliterating could not happen, and all characters except of the file extension are filtered out, then throw an error. 521 if ($safeName === pathinfo($sourcePath, PATHINFO_EXTENSION)) { 522 throw new \Exception(Text::_('COM_MEDIA_ERROR_MAKESAFE')); 523 } 524 525 // If the safe name is different normalise the file name 526 if ($safeName != $name) { 527 $destinationPath = substr($destinationPath, 0, -\strlen($name)) . $safeName; 528 } 529 530 if (is_dir($sourcePath)) { 531 $this->moveFolder($sourcePath, $destinationPath, $force); 532 } else { 533 $this->moveFile($sourcePath, $destinationPath, $force); 534 } 535 536 // Get the relative path 537 $destinationPath = str_replace($this->rootPath, '', $destinationPath); 538 539 return $destinationPath; 540 } 541 542 /** 543 * Moves a file 544 * 545 * @param string $sourcePath Absolute path of source 546 * @param string $destinationPath Absolute path of destination 547 * @param bool $force Set true to overwrite file if exists 548 * 549 * @return void 550 * 551 * @since 4.0.0 552 * @throws \Exception 553 */ 554 private function moveFile(string $sourcePath, string $destinationPath, bool $force = false) 555 { 556 if (is_dir($destinationPath)) { 557 // If the destination is a folder we create a file with the same name as the source 558 $destinationPath = $destinationPath . '/' . $this->getFileName($sourcePath); 559 } 560 561 if (!MediaHelper::checkFileExtension(pathinfo($destinationPath, PATHINFO_EXTENSION))) { 562 throw new \Exception('Move file is not possible as the extension is invalid'); 563 } 564 565 if (file_exists($destinationPath) && !$force) { 566 throw new \Exception('Move file is not possible as destination file already exists'); 567 } 568 569 if (!File::move($sourcePath, $destinationPath)) { 570 throw new \Exception('Move file is not possible'); 571 } 572 } 573 574 /** 575 * Moves a folder from source to destination 576 * 577 * @param string $sourcePath Source path of the file or directory 578 * @param string $destinationPath Destination path of the file or directory 579 * @param bool $force Set true to overwrite files or directories 580 * 581 * @return void 582 * 583 * @since 4.0.0 584 * @throws \Exception 585 */ 586 private function moveFolder(string $sourcePath, string $destinationPath, bool $force = false) 587 { 588 if (file_exists($destinationPath) && !$force) { 589 throw new \Exception('Move folder is not possible as destination folder already exists'); 590 } 591 592 if (is_file($destinationPath) && !File::delete($destinationPath)) { 593 throw new \Exception('Move folder is not possible as destination folder is a file and can not be deleted'); 594 } 595 596 if (is_dir($destinationPath)) { 597 // We need to bypass exception thrown in JFolder when destination exists 598 // So we only copy it in forced condition, then delete the source to simulate a move 599 if (!Folder::copy($sourcePath, $destinationPath, '', true)) { 600 throw new \Exception('Move folder to an existing destination failed'); 601 } 602 603 // Delete the source 604 Folder::delete($sourcePath); 605 606 return; 607 } 608 609 // Perform usual moves 610 $value = Folder::move($sourcePath, $destinationPath); 611 612 if ($value !== true) { 613 throw new \Exception($value); 614 } 615 } 616 617 /** 618 * Returns a url which can be used to display an image from within the "images" directory. 619 * 620 * @param string $path Path of the file relative to adapter 621 * 622 * @return string 623 * 624 * @since 4.0.0 625 */ 626 public function getUrl(string $path): string 627 { 628 return Uri::root() . $this->getEncodedPath($this->filePath . $path); 629 } 630 631 /** 632 * Returns the name of this adapter. 633 * 634 * @return string 635 * 636 * @since 4.0.0 637 */ 638 public function getAdapterName(): string 639 { 640 return $this->filePath; 641 } 642 643 /** 644 * Search for a pattern in a given path 645 * 646 * @param string $path The base path for the search 647 * @param string $needle The path to file 648 * @param bool $recursive Do a recursive search 649 * 650 * @return \stdClass[] 651 * 652 * @since 4.0.0 653 */ 654 public function search(string $path, string $needle, bool $recursive = false): array 655 { 656 $pattern = Path::clean($this->getLocalPath($path) . '/*' . $needle . '*'); 657 658 if ($recursive) { 659 $results = $this->rglob($pattern); 660 } else { 661 $results = glob($pattern); 662 } 663 664 $searchResults = []; 665 666 foreach ($results as $result) { 667 $searchResults[] = $this->getPathInformation($result); 668 } 669 670 return $searchResults; 671 } 672 673 /** 674 * Do a recursive search on a given path 675 * 676 * @param string $pattern The pattern for search 677 * @param int $flags Flags for search 678 * 679 * @return array 680 * 681 * @since 4.0.0 682 */ 683 private function rglob(string $pattern, int $flags = 0): array 684 { 685 $files = glob($pattern, $flags); 686 687 foreach (glob(\dirname($pattern) . '/*', GLOB_ONLYDIR | GLOB_NOSORT) as $dir) { 688 $files = array_merge($files, $this->rglob($dir . '/' . $this->getFileName($pattern), $flags)); 689 } 690 691 return $files; 692 } 693 694 /** 695 * Replace spaces on a path with %20 696 * 697 * @param string $path The Path to be encoded 698 * 699 * @return string 700 * 701 * @since 4.0.0 702 * @throws FileNotFoundException 703 */ 704 private function getEncodedPath(string $path): string 705 { 706 return str_replace(" ", "%20", $path); 707 } 708 709 /** 710 * Creates a safe file name for the given name. 711 * 712 * @param string $name The filename 713 * 714 * @return string 715 * 716 * @since 4.0.0 717 * @throws \Exception 718 */ 719 private function getSafeName(string $name): string 720 { 721 // Make the filename safe 722 if (!$name = File::makeSafe($name)) { 723 throw new \Exception(Text::_('COM_MEDIA_ERROR_MAKESAFE')); 724 } 725 726 // Transform filename to punycode 727 $name = PunycodeHelper::toPunycode($name); 728 729 // Get the extension 730 $extension = File::getExt($name); 731 732 // Normalise extension, always lower case 733 if ($extension) { 734 $extension = '.' . strtolower($extension); 735 } 736 737 $nameWithoutExtension = substr($name, 0, \strlen($name) - \strlen($extension)); 738 739 return $nameWithoutExtension . $extension; 740 } 741 742 /** 743 * Performs various check if it is allowed to save the content with the given name. 744 * 745 * @param string $localPath The local path 746 * @param string $mediaContent The media content 747 * 748 * @return void 749 * 750 * @since 4.0.0 751 * @throws \Exception 752 */ 753 private function checkContent(string $localPath, string $mediaContent) 754 { 755 $name = $this->getFileName($localPath); 756 757 // The helper 758 $helper = new MediaHelper(); 759 760 // @todo find a better way to check the input, by not writing the file to the disk 761 $tmpFile = Path::clean(\dirname($localPath) . '/' . uniqid() . '.' . File::getExt($name)); 762 763 if (!File::write($tmpFile, $mediaContent)) { 764 throw new \Exception(Text::_('JLIB_MEDIA_ERROR_UPLOAD_INPUT'), 500); 765 } 766 767 $can = $helper->canUpload(['name' => $name, 'size' => \strlen($mediaContent), 'tmp_name' => $tmpFile], 'com_media'); 768 769 File::delete($tmpFile); 770 771 if (!$can) { 772 throw new \Exception(Text::_('JLIB_MEDIA_ERROR_UPLOAD_INPUT'), 403); 773 } 774 } 775 776 /** 777 * Returns the file name of the given path. 778 * 779 * @param string $path The path 780 * 781 * @return string 782 * 783 * @since 4.0.0 784 * @throws \Exception 785 */ 786 private function getFileName(string $path): string 787 { 788 $path = Path::clean($path); 789 790 // Basename does not work here as it strips out certain characters like upper case umlaut u 791 $path = explode(DIRECTORY_SEPARATOR, $path); 792 793 // Return the last element 794 return array_pop($path); 795 } 796 797 /** 798 * Returns the local filesystem path for the given path. 799 * 800 * Throws an InvalidPathException if the path is invalid. 801 * 802 * @param string $path The path 803 * 804 * @return string 805 * 806 * @since 4.0.0 807 * @throws InvalidPathException 808 */ 809 private function getLocalPath(string $path): string 810 { 811 try { 812 return Path::check($this->rootPath . '/' . $path); 813 } catch (\Exception $e) { 814 throw new InvalidPathException($e->getMessage()); 815 } 816 } 817 }
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 |