Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like CKFinder_Connector_CommandHandler_CreateZip often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use CKFinder_Connector_CommandHandler_CreateZip, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 509 | class CKFinder_Connector_CommandHandler_CreateZip extends CKFinder_Connector_CommandHandler_XmlCommandHandlerBase |
||
| 510 | { |
||
| 511 | protected $_config; |
||
| 512 | |||
| 513 | /** |
||
| 514 | * Get private zip plugin config |
||
| 515 | * |
||
| 516 | * @access protected |
||
| 517 | * @return array |
||
| 518 | */ |
||
| 519 | protected function getConfig(){ |
||
| 520 | $config = array(); |
||
| 521 | |||
| 522 | $config['zipMaxSize'] = 'default'; |
||
| 523 | if (isset($GLOBALS['config']['ZipMaxSize']) && (string)$GLOBALS['config']['ZipMaxSize']!='default' ){ |
||
| 524 | $config['zipMaxSize'] = CKFinder_Connector_Utils_Misc::returnBytes((string)$GLOBALS['config']['ZipMaxSize']); |
||
| 525 | } |
||
| 526 | |||
| 527 | return $config; |
||
| 528 | } |
||
| 529 | |||
| 530 | /** |
||
| 531 | * Checks given file for security |
||
| 532 | * |
||
| 533 | * @param SplFileInfo $file |
||
| 534 | * @access protected |
||
| 535 | * @return bool |
||
| 536 | */ |
||
| 537 | protected function checkOneFile($file) |
||
| 538 | { |
||
| 539 | $resourceTypeInfo = $this->_currentFolder->getResourceTypeConfig(); |
||
| 540 | $_aclConfig = $this->_config->getAccessControlConfig(); |
||
| 541 | $directory = str_replace('\\','/', $resourceTypeInfo->getDirectory()); |
||
| 542 | $fileName = CKFinder_Connector_Utils_FileSystem::convertToFilesystemEncoding($file->getFilename()); |
||
| 543 | |||
| 544 | if ($this->_config->forceAscii()) { |
||
| 545 | $fileName = CKFinder_Connector_Utils_FileSystem::convertToAscii($fileName); |
||
| 546 | } |
||
| 547 | $pathName = str_replace('\\','/', pathinfo($file->getPathname(), PATHINFO_DIRNAME) ); |
||
| 548 | $pathName = CKFinder_Connector_Utils_FileSystem::convertToFilesystemEncoding($pathName); |
||
| 549 | |||
| 550 | // acl |
||
| 551 | $aclMask = $_aclConfig->getComputedMask($this->_currentFolder->getResourceTypeName(), str_ireplace($directory,'',$pathName)); |
||
| 552 | $isAuthorized = (($aclMask & CKFINDER_CONNECTOR_ACL_FILE_VIEW) == CKFINDER_CONNECTOR_ACL_FILE_VIEW); |
||
| 553 | if ( !$isAuthorized ){ |
||
| 554 | return false; |
||
| 555 | } |
||
| 556 | |||
| 557 | // if it is a folder fileName represents the dir |
||
| 558 | if ( $file->isDir() && ( !CKFinder_Connector_Utils_FileSystem::checkFolderPath($fileName) || $resourceTypeInfo->checkIsHiddenPath($fileName) ) ){ |
||
| 559 | return false; |
||
| 560 | } |
||
| 561 | // folder name |
||
| 562 | if ( !CKFinder_Connector_Utils_FileSystem::checkFolderPath($pathName) ){ |
||
| 563 | return false; |
||
| 564 | } |
||
| 565 | |||
| 566 | // is hidden |
||
| 567 | if ( $resourceTypeInfo->checkIsHiddenPath($pathName) || $resourceTypeInfo->checkIsHiddenFile($fileName) ){ |
||
| 568 | return false; |
||
| 569 | } |
||
| 570 | |||
| 571 | // extension |
||
| 572 | if ( !$resourceTypeInfo->checkExtension($fileName) || !CKFinder_Connector_Utils_FileSystem::checkFileName($fileName) ){ |
||
| 573 | return false; |
||
| 574 | } |
||
| 575 | |||
| 576 | return true; |
||
| 577 | } |
||
| 578 | |||
| 579 | /** |
||
| 580 | * Get list of all files in given directory, including sub-directories |
||
| 581 | * |
||
| 582 | * @param string $directory |
||
| 583 | * @param int $zipMaxSize Maximum zip file size |
||
| 584 | * @return array $allFiles |
||
| 585 | */ |
||
| 586 | protected function getFilesRecursively( $directory, $zipMaxSize ) |
||
| 587 | { |
||
| 588 | $allFiles = array(); |
||
| 589 | $_zipFilesSize = 0; |
||
| 590 | $serverPath = str_replace('\\','/',$directory); |
||
| 591 | |||
| 592 | foreach(new RecursiveIteratorIterator(new RecursiveDirectoryIterator($directory), RecursiveIteratorIterator::CHILD_FIRST) as $file ) { |
||
| 593 | if ( !$this->checkOneFile($file) ){ |
||
| 594 | continue; |
||
| 595 | } |
||
| 596 | if ( !empty($zipMaxSize) ){ |
||
| 597 | clearstatcache(); |
||
| 598 | $_zipFilesSize += $file->getSize(); |
||
| 599 | if ( $_zipFilesSize > $zipMaxSize ) { |
||
| 600 | $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_CREATED_FILE_TOO_BIG); |
||
| 601 | } |
||
| 602 | } |
||
| 603 | $pathName = str_replace('\\','/',$file->getPathname()); |
||
| 604 | if ( $file->isDir() ){ |
||
| 605 | // skip dot folders on unix systems ( do not try to use isDot() as $file is not a DirectoryIterator obj ) |
||
| 606 | if ( in_array($file->getFilename(),array('..','.')) ){ |
||
| 607 | continue; |
||
| 608 | } |
||
| 609 | if ($pathName != rtrim($serverPath,'/')){ |
||
| 610 | $allFiles[ ltrim(str_ireplace(rtrim($serverPath,'/'),'',$pathName),'/') ] = ''; |
||
| 611 | } |
||
| 612 | } else { |
||
| 613 | $allFiles[$pathName] = str_ireplace($serverPath,'',$pathName); |
||
| 614 | } |
||
| 615 | } |
||
| 616 | |||
| 617 | return $allFiles; |
||
| 618 | } |
||
| 619 | |||
| 620 | /** |
||
| 621 | * Handle request and build XML |
||
| 622 | */ |
||
| 623 | public function buildXml() |
||
| 624 | { |
||
| 625 | if (!extension_loaded('zip')) { |
||
| 626 | $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_INVALID_COMMAND); |
||
| 627 | } |
||
| 628 | |||
| 629 | $this->checkConnector(); |
||
| 630 | $this->checkRequest(); |
||
| 631 | |||
| 632 | if ( !$this->_currentFolder->checkAcl(CKFINDER_CONNECTOR_ACL_FILE_UPLOAD)) { |
||
| 633 | $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_UNAUTHORIZED); |
||
| 634 | } |
||
| 635 | |||
| 636 | $this->_config =& CKFinder_Connector_Core_Factory::getInstance("Core_Config"); |
||
| 637 | $currentResourceTypeConfig = $this->_currentFolder->getResourceTypeConfig(); |
||
| 638 | $_sServerDir = $this->_currentFolder->getServerPath(); |
||
| 639 | |||
| 640 | $files = array(); |
||
| 641 | |||
| 642 | $_zipFilesSize = 0; |
||
| 643 | $config = $this->getConfig(); |
||
| 644 | $zipMaxSize = $config['zipMaxSize']; |
||
| 645 | if ( !empty($zipMaxSize) && $zipMaxSize == 'default' ){ |
||
| 646 | $zipMaxSize = $currentResourceTypeConfig->getMaxSize(); |
||
| 647 | } |
||
| 648 | |||
| 649 | $_isBasket = ( isset($_POST['basket']) && $_POST['basket'] == 'true' )? true : false; |
||
| 650 | |||
| 651 | if ( !empty($_POST['files'])) |
||
| 652 | { |
||
| 653 | |||
| 654 | $_aclConfig = $this->_config->getAccessControlConfig(); |
||
| 655 | $aclMasks = array(); |
||
| 656 | $_resourceTypeConfig = array(); |
||
| 657 | |||
| 658 | foreach ( $_POST['files'] as $arr ){ |
||
| 659 | if ( empty($arr['name']) || empty($arr['type']) || empty($arr['folder']) ) { |
||
| 660 | continue; |
||
| 661 | } |
||
| 662 | // file name |
||
| 663 | $name = CKFinder_Connector_Utils_FileSystem::convertToFilesystemEncoding($arr['name']); |
||
| 664 | // resource type |
||
| 665 | $type = $arr['type']; |
||
| 666 | // client path |
||
| 667 | $path = CKFinder_Connector_Utils_FileSystem::convertToFilesystemEncoding($arr['folder']); |
||
| 668 | |||
| 669 | // check #1 (path) |
||
| 670 | if (!CKFinder_Connector_Utils_FileSystem::checkFileName($name) || preg_match(CKFINDER_REGEX_INVALID_PATH, $path)) { |
||
| 671 | continue; |
||
| 672 | } |
||
| 673 | |||
| 674 | // get resource type config for current file |
||
| 675 | if (!isset($_resourceTypeConfig[$type])) { |
||
| 676 | $_resourceTypeConfig[$type] = $this->_config->getResourceTypeConfig($type); |
||
| 677 | } |
||
| 678 | |||
| 679 | // check #2 (resource type) |
||
| 680 | if (is_null($_resourceTypeConfig[$type])) { |
||
| 681 | continue; |
||
| 682 | } |
||
| 683 | |||
| 684 | // check #3 (extension) |
||
| 685 | if (!$_resourceTypeConfig[$type]->checkExtension($name, false)) { |
||
| 686 | continue; |
||
| 687 | } |
||
| 688 | |||
| 689 | // check #4 (extension) - when moving to another resource type, double check extension |
||
| 690 | if ($currentResourceTypeConfig->getName() != $type && !$currentResourceTypeConfig->checkExtension($name, false)) { |
||
| 691 | continue; |
||
| 692 | } |
||
| 693 | |||
| 694 | // check #5 (hidden folders) |
||
| 695 | // cache results |
||
| 696 | if (empty($checkedPaths[$path])) { |
||
| 697 | $checkedPaths[$path] = true; |
||
| 698 | |||
| 699 | if ($_resourceTypeConfig[$type]->checkIsHiddenPath($path)) { |
||
| 700 | continue; |
||
| 701 | } |
||
| 702 | } |
||
| 703 | |||
| 704 | // check #6 (hidden file name) |
||
| 705 | if ($currentResourceTypeConfig->checkIsHiddenFile($name)) { |
||
| 706 | continue; |
||
| 707 | } |
||
| 708 | |||
| 709 | // check #7 (Access Control, need file view permission to source files) |
||
| 710 | if (!isset($aclMasks[$type."@".$path])) { |
||
| 711 | $aclMasks[$type."@".$path] = $_aclConfig->getComputedMask($type, $path); |
||
| 712 | } |
||
| 713 | |||
| 714 | $isAuthorized = (($aclMasks[$type."@".$path] & CKFINDER_CONNECTOR_ACL_FILE_VIEW) == CKFINDER_CONNECTOR_ACL_FILE_VIEW); |
||
| 715 | if (!$isAuthorized) { |
||
| 716 | continue; |
||
| 717 | } |
||
| 718 | |||
| 719 | $sourceFilePath = CKFinder_Connector_Utils_FileSystem::combinePaths($_resourceTypeConfig[$type]->getDirectory().$path,$name); |
||
| 720 | // check #8 (invalid file name) |
||
| 721 | if (!file_exists($sourceFilePath) || !is_file($sourceFilePath)) { |
||
| 722 | continue; |
||
| 723 | } |
||
| 724 | |||
| 725 | // check #9 - max file size |
||
| 726 | if ( !empty($zipMaxSize) ){ |
||
| 727 | clearstatcache(); |
||
| 728 | $_zipFilesSize += filesize($sourceFilePath); |
||
| 729 | if ( $_zipFilesSize > $zipMaxSize ) { |
||
| 730 | $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_CREATED_FILE_TOO_BIG); |
||
| 731 | } |
||
| 732 | } |
||
| 733 | |||
| 734 | $zipPathPart = ( $_isBasket ) ? CKFinder_Connector_Utils_FileSystem::combinePaths($type,$path) : ''; |
||
| 735 | |||
| 736 | $files[$sourceFilePath] = $zipPathPart.pathinfo($sourceFilePath,PATHINFO_BASENAME); |
||
| 737 | } |
||
| 738 | } |
||
| 739 | else |
||
| 740 | { |
||
| 741 | if (!is_dir($_sServerDir)) { |
||
| 742 | $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_FOLDER_NOT_FOUND); |
||
| 743 | } |
||
| 744 | $files = $this->getFilesRecursively($_sServerDir,$zipMaxSize); |
||
| 745 | } |
||
| 746 | if ( sizeof($files)<1) { |
||
| 747 | $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_FILE_NOT_FOUND); |
||
| 748 | } |
||
| 749 | // default destination dir - temp |
||
| 750 | $dest_dir = CKFinder_Connector_Utils_FileSystem::getTmpDir(); |
||
| 751 | $resourceTypeInfo = $this->_currentFolder->getResourceTypeConfig(); |
||
| 752 | |||
| 753 | // default file name - hash |
||
| 754 | $zip_filename = substr(md5(serialize($files)), 0, 16).$resourceTypeInfo->getHash().'.zip'; |
||
| 755 | |||
| 756 | // compress files - do not download them |
||
| 757 | // change destination and name |
||
| 758 | if ( isset($_POST['download']) && $_POST['download'] == 'false'){ |
||
| 759 | $dest_dir = $_sServerDir; |
||
| 760 | if ( isset($_POST['zipName']) && !empty($_POST['zipName'])){ |
||
| 761 | $zip_filename = CKFinder_Connector_Utils_FileSystem::convertToFilesystemEncoding($_POST['zipName']); |
||
| 762 | if (!$resourceTypeInfo->checkExtension($zip_filename)) { |
||
| 763 | $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_INVALID_EXTENSION); |
||
| 764 | } |
||
| 765 | } |
||
| 766 | } |
||
| 767 | if (!CKFinder_Connector_Utils_FileSystem::checkFileName($zip_filename) || $resourceTypeInfo->checkIsHiddenFile($zip_filename)) { |
||
| 768 | $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_INVALID_NAME); |
||
| 769 | } |
||
| 770 | if ($this->_config->forceAscii()) { |
||
| 771 | $zip_filename = CKFinder_Connector_Utils_FileSystem::convertToAscii($zip_filename); |
||
| 772 | } |
||
| 773 | |||
| 774 | $zipFilePath = CKFinder_Connector_Utils_FileSystem::combinePaths($dest_dir, $zip_filename); |
||
| 775 | |||
| 776 | if (!is_writable(dirname($zipFilePath))) { |
||
| 777 | $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_ACCESS_DENIED); |
||
| 778 | } |
||
| 779 | |||
| 780 | // usually we would need to create zip? |
||
| 781 | $createZip = true; |
||
| 782 | |||
| 783 | // only if file already exists and we want download it |
||
| 784 | // do not create new one - because hash of previously created is the same - existing archive is ok |
||
| 785 | if ( file_exists($zipFilePath) && isset($_POST['download']) && $_POST['download'] == 'true' ){ |
||
| 786 | $createZip = false; |
||
| 787 | } |
||
| 788 | // if we only want to create archive |
||
| 789 | else |
||
| 790 | { |
||
| 791 | if ( file_exists($zipFilePath) && ( !isset($_POST['fileExistsAction']) || !in_array($_POST['fileExistsAction'], array('autorename','overwrite')) ) ){ |
||
| 792 | $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_ALREADY_EXIST); |
||
| 793 | } |
||
| 794 | |||
| 795 | if ( !$this->_currentFolder->checkAcl( CKFINDER_CONNECTOR_ACL_FILE_UPLOAD )) { |
||
| 796 | $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_UNAUTHORIZED); |
||
| 797 | } |
||
| 798 | // check how to deal with existing file |
||
| 799 | if ( isset($_POST['fileExistsAction']) && $_POST['fileExistsAction'] == 'autorename' ) |
||
| 800 | { |
||
| 801 | if ( !$this->_currentFolder->checkAcl(CKFINDER_CONNECTOR_ACL_FILE_UPLOAD | CKFINDER_CONNECTOR_ACL_FILE_RENAME )) { |
||
| 802 | $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_UNAUTHORIZED); |
||
| 803 | } |
||
| 804 | $zip_filename = CKFinder_Connector_Utils_FileSystem::autoRename($dest_dir, $zip_filename); |
||
| 805 | $zipFilePath = CKFinder_Connector_Utils_FileSystem::combinePaths($dest_dir, $zip_filename); |
||
| 806 | } |
||
| 807 | elseif ( isset($_POST['fileExistsAction']) && $_POST['fileExistsAction'] == 'overwrite' ) |
||
| 808 | { |
||
| 809 | if ( !$this->_currentFolder->checkAcl(CKFINDER_CONNECTOR_ACL_FILE_RENAME | CKFINDER_CONNECTOR_ACL_FILE_DELETE)) { |
||
| 810 | $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_UNAUTHORIZED); |
||
| 811 | } |
||
| 812 | if (!CKFinder_Connector_Utils_FileSystem::unlink($zipFilePath)){ |
||
| 813 | $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_ACCESS_DENIED); |
||
| 814 | } |
||
| 815 | } |
||
| 816 | } |
||
| 817 | |||
| 818 | if ( $createZip ){ |
||
| 819 | $zip = new ZipArchive(); |
||
| 820 | $result = $zip->open( $zipFilePath, ZIPARCHIVE::CREATE); |
||
| 821 | if ( $result !== TRUE ) { |
||
| 822 | $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_UNKNOWN); |
||
| 823 | } |
||
| 824 | foreach ( $files as $pathname => $filename ){ |
||
| 825 | if ( !empty($filename) ){ |
||
| 826 | if ( file_exists($pathname) && is_readable($pathname) ){ |
||
| 827 | $zip->addFile( $pathname, $filename ); |
||
| 828 | } |
||
| 829 | } else { |
||
| 830 | $zip->addEmptyDir( $pathname ); |
||
| 831 | } |
||
| 832 | } |
||
| 833 | $zip->close(); |
||
| 834 | } |
||
| 835 | |||
| 836 | $file = new CKFinder_Connector_Utils_XmlNode("ZipFile"); |
||
| 837 | $file->addAttribute("name", $zip_filename); |
||
| 838 | $this->_connectorNode->addChild($file); |
||
| 839 | } |
||
| 840 | |||
| 841 | public function onBeforeExecuteCommand( &$command ) |
||
| 842 | { |
||
| 843 | if ( $command == 'CreateZip'){ |
||
| 844 | $this->sendResponse(); |
||
| 845 | return false; |
||
| 846 | } |
||
| 847 | return true ; |
||
| 848 | } |
||
| 849 | |||
| 850 | } // end of CKFinder_Connector_CommandHandler_DownloadZip class |
||
| 851 | |||
| 946 |