/joomla/administrator/components/com_joomlapack/classes/core/domain/pack.php
https://github.com/joomla-example/joomla-repo · PHP · 643 lines · 402 code · 64 blank · 177 comment · 68 complexity · d5b8643ad460f3159ef35a9318c2b750 MD5 · raw file
- <?php
- /**
- * @package JoomlaPack
- * @copyright Copyright (C) 2006-2009 JoomlaPack Developers. All rights reserved.
- * @version $Id$
- * @license http://www.gnu.org/copyleft/gpl.html GNU/GPL
- * @since 1.2.1
- *
- * JoomlaPack is free software. This version may have been modified pursuant
- * to the GNU General Public License, and as distributed it includes or
- * is derivative of works licensed under the GNU General Public License or
- * other free or open source software licenses.
- **/
- defined('_JEXEC') or die('Restricted access');
- $config =& JoomlapackModelRegistry::getInstance();
- define('JPMaxFragmentSize', $config->get('mnMaxFragmentSize')); // Maximum bytes a fragment can have (default: 1Mb)
- define('JPMaxFragmentFiles', $config->get('mnMaxFragmentFiles')); // Maximum number of files a fragment can have (default: 50 files)
- /**
- * Packing engine. Takes care of putting gathered files (the file list) into
- * an archive.
- */
- class JoomlapackCUBEDomainPack extends JoomlapackCUBEParts {
- /**
- * @var array Directories to exclude
- */
- var $_ExcludeDirs;
- /**
- * @var array Files to exclude
- */
- var $_ExcludeFiles;
-
- /**
- * Directories to exclude their files from the backup
- *
- * @var array
- */
- var $_skipContainedFiles;
-
- /**
- * Directories to exclude their subdirectories from the backup
- *
- * @var array
- */
- var $_skipContainedDirectories;
- /**
- * @var array Directories left to be scanned
- */
- var $_directoryList;
- /**
- * @var array Files left to be put into the archive
- */
- var $_fileList;
- /**
- * Operation toggle. When it is true, files are added in the archive. When it is off, the
- * directories are scanned for files and directories.
- *
- * @var bool
- */
- var $_doneScanning = false;
-
- /**
- * Operation toggle #2. Scanning is separated in two sub-operations: scanning for
- * subdirectories (when this flag is false) and scanning for files (when this flag is
- * true).
- *
- * @var bool
- */
- var $_doneSubdirectoryScanning = false;
-
- /**
- * Operation toggle #3. Since the scanning of a folder for files might be interrupted
- * for some reason, when this variable is false the algorithm is forced NOT to skip
- * to the next item of the directory list.
- *
- * @var bool
- */
- var $_doneFileScanning = true;
- /**
- * Path to add to scanned files
- *
- * @var string
- */
- var $_addPath;
- /**
- * Path to remove from scanned files
- *
- * @var string
- */
- var $_removePath;
- /**
- * An array of EFF-defined directories
- *
- * @var array
- */
- var $_extraDirs = array();
-
- var $_processedFiles;
- var $_dirName;
- // ============================================================================================
- // IMPLEMENTATION OF JoomlapackEngineParts METHODS
- // ============================================================================================
- /**
- * Public constructor of the class
- *
- * @return JoomlapackCUBEDomainPack
- */
- function JoomlapackCUBEDomainPack(){
- $this->_DomainName = "Packing";
- JoomlapackLogger::WriteLog(_JP_LOG_DEBUG, "JoomlapackCUBEDomainPack :: new instance");
- }
- /**
- * Implements the _prepare() abstract method
- *
- */
- function _prepare()
- {
- JoomlapackLogger::WriteLog(_JP_LOG_DEBUG, "JoomlapackCUBEDomainPack :: Starting _prepare()");
-
- $cube =& JoomlapackCUBE::getInstance();
-
- // Grab the EFF filters
- JoomlapackLogger::WriteLog(_JP_LOG_DEBUG, "JoomlapackCUBEDomainPack :: Getting off-site directory inclusion filters (EFF)");
- jpimport('models.eff', true);
- $effModel = new JoomlapackModelEff();
- $this->_extraDirs =& $effModel->getMapping();
- // Add the mapping text file if there are EFFs defined!
- if(count($this->_extraDirs) > 0)
- {
- // We add a README.txt file in our virtual directory...
- JoomlapackLogger::WriteLog(_JP_LOG_DEBUG, "Creating README.txt in the EFF virtual folder");
- $virtualContents = JText::_('EFF_MAPTEXT_INTRO')."\n\n";
- foreach($this->_extraDirs as $dir)
- {
- $virtualContents .= JText::sprintf('EFF_MAPTEXT_LINE', $dir['vdir'], $dir['fsdir'])."\n";
- }
- // Add the file to our archive
- $registry =& JoomlapackModelRegistry::getInstance();
- $provisioning =& $cube->getProvisioning();
- $archiver =& $provisioning->getArchiverEngine();
- $archiver->addVirtualFile('README.txt', $registry->get('effvfolder'), $virtualContents);
- }
- // Get the directory exclusion filters - this only needs to be done once
- JoomlapackLogger::WriteLog(_JP_LOG_DEBUG, "JoomlapackCUBEDomainPack :: Getting exclusion filters");
- $this->_loadAndCacheFilters();
- if($this->getError()) return false;
- // FIX 1.1.0 $mosConfig_absolute_path may contain trailing slashes or backslashes incompatible with exclusion filters
- // FIX 1.2.2 Some hosts yield an empty string on realpath(JPATH_SITE)
- // FIX 2.2 On Windows, realpath might fail
- jpimport('helpers.utils', true);
- // FIX 2.4: Make an assumption (wild guess...)
- if(JPATH_BASE == '/administrator')
- {
- $cube->addWarning("Your site's root is an empty string. I am trying a workaround.");
- $jpath_site_real = '/';
- }
- else
- {
- // Fix 2.4: Make sure that $jpath_site_real contains something even if realpath fails
- $jpath_site_real = @realpath(trim(JPATH_SITE));
- $jpath_site_real = ($jpath_site_real === false) ? trim(JPATH_SITE) : $jpath_site_real;
- $jpath_site_real = JoomlapackHelperUtils::TranslateWinPath($jpath_site_real);
- }
-
- if( $jpath_site_real == '' )
- {
- // The JPATH_SITE is resolved to an empty string; attempt a workaround
-
- // Windows hosts
- if(DIRECTORY_SEPARATOR == '\\')
- {
- if( (trim(JPATH_SITE) != '') && (trim(JPATH_SITE) != '\\') && (trim(JPATH_SITE) != '/'))
- {
- $cube->addWarning("The site's root couldn't be normalized on a Windows host. Attempting workaround (filters might not work)");
- $jpath_site_real = JPATH_SITE; // Forcibly use the configured JPATH_SITE
- }
- else
- {
- $cube->addWarning("The normalized path to your site's root seems to be an empty string; I will attempt a workaround (Windows host)");
- $jpath_site_real = '/'; // Start scanning from filesystem root (workaround mode)
- }
- }
- // *NIX hosts
- else
- {
- $cube->addWarning("The normalized path to your site's root seems to be an empty string; I will attempt a workaround (*NIX host)");
- # Fix 2.1 Since JPATH_SITE is an empty string, shouldn't I begin scanning from the FS root, for crying out loud? What was I thinking putting JPATH_SITE there?
- $jpath_site_real = '/'; // Start scanning from filesystem root (workaround mode)
- }
- }
-
- // Fix 2.4.b1 : Add the trailing slash
- if( (substr($jpath_site_real,-1) != '/') && !empty($jpath_site_real) )
- {
- $jpath_site_real .= '/';
- }
- $this->_directoryList[] = $jpath_site_real; // Start scanning from Joomla! root, as decided above
- $this->_doneScanning = false; // Instruct the class to scan for files and directories
- $this->_doneSubdirectoryScanning = true;
- $this->_doneFileScanning = true;
- $this->_addPath = ''; // No added path for main site
- // Fix 2.4.b1 -- Since JPATH_SITE might have been post-processed, used the post-processed variable instead
- $this->_removePath = $jpath_site_real; // Remove absolute path to site's root for main site
- $this->setState('prepared');
- JoomlapackLogger::WriteLog(_JP_LOG_DEBUG, "JoomlapackCUBEDomainPack :: prepared");
- }
- function _run()
- {
- if ($this->_getState() == 'postrun') {
- JoomlapackLogger::WriteLog(_JP_LOG_DEBUG, "JoomlapackCUBEDomainPack :: Already finished");
- $this->_Step = "-";
- $this->_Substep = "";
- }
- else
- {
- if($this->_doneScanning)
- {
- $this->_packSomeFiles();
- if($this->getError()) return false;
- }
- else
- {
- $result = $this->_scanNextDirectory();
- if($this->getError()) return false;
- if(!$result)
- {
- // We have finished with our directory list. Hmm... Do we have extra directories?
- if(count($this->_extraDirs) > 0)
- {
- JoomlapackLogger::WriteLog(_JP_LOG_DEBUG, "More EFF definitions detected");
- $registry =& JoomlapackModelRegistry::getInstance();
- // Whack filters (not applicable for off-site directories)
- $this->_ExcludeDirs = array();
- $this->_ExcludeFiles = array();
- $this->_skipContainedDirectories = array();
- $this->_skipContainedFiles = array();
- // Calculate add/remove paths
- $myEntry = array_shift($this->_extraDirs);
- $this->_removePath = $myEntry['fsdir'];
- $this->_addPath = $registry->get('effvfolder').DS.$myEntry['vdir'];
- // Start the filelist building!
- $this->_directoryList[] = $this->_removePath;
- $this->_doneScanning = false; // Make sure we process this file list!
- JoomlapackLogger::WriteLog(_JP_LOG_INFO, "Including new off-site directory to ".$myEntry['vdir']);
- }
- else
- // Nope, we are completely done!
- $this->setState('postrun');
- }
- }
- }
- }
- /**
- * Implements the _finalize() abstract method
- *
- */
- function _finalize()
- {
- JoomlapackLogger::WriteLog(_JP_LOG_INFO, "Finalizing archive");
- $cube =& JoomlapackCUBE::getInstance();
- $provisioning =& $cube->getProvisioning();
- $archive =& $provisioning->getArchiverEngine();
- $archive->finalize();
- // Error propagation
- if($archive->getError())
- {
- $this->setError($archive->getError());
- return false;
- }
- JoomlapackLogger::WriteLog(_JP_LOG_DEBUG, "Archive is finalized");
- $this->setState('finished');
- }
- // ============================================================================================
- // PRIVATE METHODS
- // ============================================================================================
- /**
- * Loads the exclusion filters off the db and caches them inside the object
- */
- function _loadAndCacheFilters() {
- jpimport('core.utility.filtermanager');
- JoomlapackLogger::WriteLog(_JP_LOG_DEBUG, "JoomlapackCUBEDomainPack :: Initializing filter manager");
- $filterManager = new JoomlapackCUBEFilterManager();
- $filterManager->init();
- JoomlapackLogger::WriteLog(_JP_LOG_DEBUG, "JoomlapackCUBEDomainPack :: Getting Directory Exclusion Filters");
- $this->_ExcludeDirs = $filterManager->getFilters('folder');
- JoomlapackLogger::WriteLog(_JP_LOG_DEBUG, "JoomlapackCUBEDomainPack :: Getting Single File Filters");
- $this->_ExcludeFiles = $filterManager->getFilters('singlefile');
- JoomlapackLogger::WriteLog(_JP_LOG_DEBUG, "JoomlapackCUBEDomainPack :: Getting Contained Files Filters");
- $this->_skipContainedFiles = $filterManager->getFilters('containedfiles');
- JoomlapackLogger::WriteLog(_JP_LOG_DEBUG, "JoomlapackCUBEDomainPack :: Getting Contained Directories Filters");
- $this->_skipContainedDirectories = $filterManager->getFilters('containeddirectories');
- JoomlapackLogger::WriteLog(_JP_LOG_DEBUG, "JoomlapackCUBEDomainPack :: Done with filter manager");
- unset($filterManager);
- }
- /**
- * Scans a directory for files and directories, updating the _directoryList and _fileList
- * private fields
- *
- * @return bool True if more work has to be done, false if the dirextory stack is empty
- */
- function _scanNextDirectory( )
- {
- // Are we supposed to scan for more files?
- if( $this->_doneScanning ) return true;
- // Get the next directory to scan, if the folders and files of the last directory
- // have been scanned.
- if($this->_doneSubdirectoryScanning && $this->_doneFileScanning)
- {
- if( count($this->_directoryList) == 0 )
- {
- // No directories left to scan
- return false;
- }
- else
- {
- // Get and remove the last entry from the $_directoryList array
- $this->_dirName = array_pop($this->_directoryList);
- $this->_Step = $this->_dirName;
- $this->_doneSubdirectoryScanning = false;
- $this->_doneFileScanning = false;
- $this->_processedFiles = 0;
- }
- }
-
- $cube =& JoomlapackCUBE::getInstance();
- $provisioning =& $cube->getProvisioning();
- $engine =& $provisioning->getListerEngine();
- // Scan subdirectories, if they have not yet been scanned.
- if(!$this->_doneSubdirectoryScanning)
- {
- // Apply DEF (directory exclusion filters)
- if (in_array( $this->_dirName, $this->_ExcludeDirs )) {
- JoomlapackLogger::WriteLog(_JP_LOG_INFO, "Skipping directory ".$this->_dirName);
- $this->_doneSubdirectoryScanning = true;
- $this->_doneFileScanning = true;
- return true;
- }
-
- // Apply Skip Contained Directories Filters
- if (in_array( $this->_dirName, $this->_skipContainedDirectories )) {
- JoomlapackLogger::WriteLog(_JP_LOG_INFO, "Skipping subdirectories of directory ".$this->_dirName);
- $this->_doneSubdirectoryScanning = true;
- }
- else
- {
- JoomlapackLogger::WriteLog(_JP_LOG_INFO, "Scanning directories of ".$this->_dirName);
- // Get subdirectories
- $subdirs = $engine->getFolders($this->_dirName);
-
- // If the list contains "too many" items, please break this step!
- if($engine->BREAKFLAG)
- {
- // Unset the BREAKFLAG of the engine
- $engine->BREAKFLAG = false;
- // Set the BREAKFLAG of our class
- $this->setBreakFlag();
- // Log this decision, for debugging reasons
- JoomlapackLogger::WriteLog(_JP_LOG_INFO, "Large directory ".$this->_dirName." while scanning for subdirectories; I will resume scanning in next step.");
- // Return immediately, marking that we are not done yet!
- return true;
- }
-
- // Error propagation
- if($engine->getError())
- {
- $this->setError($engine->getError());
- return false;
- }
-
- if(!empty($subdirs) && is_array($subdirs))
- {
- $registry =& JoomlapackModelRegistry::getInstance();
- $dereferencesymlinks = $registry->get('dereferencesymlinks');
- if($dereferencesymlinks)
- {
- // Treat symlinks to directories as actual directories
- foreach($subdirs as $subdir)
- {
- $this->_directoryList[] = $subdir;
- }
- }
- else
- {
- // Treat symlinks to directories as simple symlink files (ONLY WORKS WITH CERTAIN ARCHIVERS!)
- foreach($subdirs as $subdir)
- {
- if(is_link($subdir))
- {
- JoomlapackLogger::WriteLog(_JP_LOG_DEBUG, 'Symlink found: '.$subdir);
- $this->_fileList[] = $subdir;
- }
- else
- {
- $this->_directoryList[] = $subdir;
- }
- }
- }
- }
- }
-
- $this->_doneSubdirectoryScanning = true;
- return true; // Break operation
- }
-
- // If we are here, we have not yet scanned the directory for files, so there
- // is no need to test for _doneFileScanning (saves a tiny amount of CPU time)
- // Apply Skipfiles, a.k.a. CFF (Contained Files Filter)
- if (in_array( $this->_dirName, $this->_skipContainedFiles )) {
- JoomlapackLogger::WriteLog(_JP_LOG_INFO, "Skipping files of directory ".$this->_dirName);
- // Try to find and include .htaccess and index.htm(l) files
- jimport('joomla.filesystem.file');
- // # Fix 2.4: Do not add DS if we are on the site's root and it's an empty string
- $ds = ($this->_dirName == '') || ($this->_dirName == '/') ? '' : DS;
- $checkForTheseFiles = array(
- $this->_dirName.$ds.'.htaccess',
- $this->_dirName.$ds.'index.html',
- $this->_dirName.$ds.'index.htm',
- $this->_dirName.$ds.'robots.txt'
- );
- $this->_processedFiles = 0;
- foreach($checkForTheseFiles as $fileName)
- {
- if(JFile::exists($fileName))
- {
- $this->_fileList[] = $fileName;
- $this->_processedFiles++;
- }
- }
- $this->_doneFileScanning = true;
- }
- else
- {
- JoomlapackLogger::WriteLog(_JP_LOG_INFO, "Scanning files of ".$this->_dirName);
- // Get file listing
- $fileList =& $engine->getFiles( $this->_dirName );
-
- // If the list contains "too many" items, please break this step!
- if($engine->BREAKFLAG)
- {
- // Unset the BREAKFLAG of the engine
- $engine->BREAKFLAG = false;
- // Set the BREAKFLAG of our class
- $this->setBreakFlag();
- // Log this decision, for debugging reasons
- JoomlapackLogger::WriteLog(_JP_LOG_INFO, "Large directory ".$this->_dirName." while scanning for files; I will resume scanning in next step.");
- // Return immediately, marking that we are not done yet!
- return true;
- }
-
- // Error propagation
- if($engine->getError())
- {
- $this->setError($engine->getError());
- return false;
- }
-
- $this->_processedFiles = 0;
-
- if (($fileList === false)) {
- // A non-browsable directory; however, it seems that I never get FALSE reported here?!
- $cube->addWarning(JText::sprintf('CUBE_WARN_UNREADABLEDIR', $this->_dirName));
- }
- else
- {
- if(is_array($fileList) && !empty($fileList))
- {
- // Scan all directory entries
- foreach($fileList as $fileName) {
- $skipThisFile = is_array($this->_ExcludeFiles) ? in_array( $fileName, $this->_ExcludeFiles ) : false;
- if ($skipThisFile) {
- JoomlapackLogger::WriteLog(_JP_LOG_INFO, "Skipping file $fileName");
- } else {
- $this->_fileList[] = $fileName;
- $this->_processedFiles++;
- }
- } // end foreach
- } // end if
- } // end filelist not false
-
- $this->_doneFileScanning = true;
- }
- // Check to see if there were no contents of this directory added to our search list
- if ( $this->_processedFiles == 0 ) {
- $archiver =& $provisioning->getArchiverEngine();
- $archiver->addFile($this->_dirName, $this->_removePath, $this->_addPath);
-
- if($archiver->getError())
- {
- $this->setError($archiver->getError());
- return false;
- }
-
- JoomlapackLogger::WriteLog(_JP_LOG_INFO, "Empty directory ".$this->_dirName);
- unset($archiver);
-
- $this->_doneScanning = false; // Because it was an empty dir $_fileList is empty and we have to scan for more files
- }
- else
- {
- // Next up, add the files to the archive!
- $this->_doneScanning = true;
- }
- // We're done listing the contents of this directory
- unset($engine);
- unset($provisioning);
- unset($cube);
- return true;
- }
- /**
- * Try to pack some files in the $_fileList, restraining ourselves not to reach the max
- * number of files or max fragment size while doing so. If this process is over and we are
- * left without any more files, reset $_doneScanning to false in order to instruct the class
- * to scan for more files.
- *
- * @return bool True if there were files packed, false otherwise (empty filelist)
- */
- function _packSomeFiles()
- {
- if( count($this->_fileList) == 0 )
- {
- // No files left to pack -- This should never happen! We catch this condition at the end of this method!
- $this->_doneScanning = false;
- return false;
- }
- else
- {
- $packedSize = 0;
- $numberOfFiles = 0;
- $cube =& JoomlapackCUBE::getInstance();
- $provisioning =& $cube->getProvisioning();
- $archiver =& $provisioning->getArchiverEngine();
- $algoRunner =& JoomlapackCUBEAlgorunner::getInstance();
-
- list($usec, $sec) = explode(" ", microtime());
- $opStartTime = ((float)$usec + (float)$sec);
-
- while( (count($this->_fileList) > 0) && ($packedSize <= JPMaxFragmentSize) && ($numberOfFiles <= JPMaxFragmentFiles) )
- {
- $file = @array_shift($this->_fileList);
- $size = @filesize($file);
- // JoomlaPack 2.2: Anticipatory fragment size algorithm
- if( ($packedSize + $size > JPMaxFragmentSize) && ($numberOfFiles > 0) )
- {
- // Adding this file exceeds the fragment's capacity. Furthermore, it's NOT
- // the first file we tried to pack. Therefore, push it back to the list.
- array_unshift($this->_fileList, $file);
- // If the file is bigger than a whole fragment's allowed size, break the step
- // to avoid potential timeouts
- if($size > JPMaxFragmentSize)
- {
- JoomlapackLogger::WriteLog(_JP_LOG_INFO, "Breaking step _before_ large file: ".$file." - size: ".$size);
- $this->setBreakFlag();
- }
- // Mark that we are not done packing files
- $this->_doneScanning = true;
- return true;
- }
-
- // JoomlaPack 2.2: Proactive potential timeout detection
- // Rough estimation of packing speed in bytes per second
- list($usec, $sec) = explode(" ", microtime());
- $opEndTime = ((float)$usec + (float)$sec);
- if( ($opEndTime - $opStartTime) == 0 )
- {
- $_packSpeed = 0;
- }
- else
- {
- $_packSpeed = $packedSize / ($opEndTime - $opStartTime);
- }
- // Estimate required time to pack next file. If it's the first file of this operation,
- // do not impose any limitations.
- $_reqTime = ($_packSpeed - 0.01) <= 0 ? 0 : $size / $_packSpeed;
- // Do we have enough time?
- if($algoRunner->getTimeLeft() < $_reqTime )
- {
- array_unshift($this->_fileList, $file);
- JoomlapackLogger::WriteLog(_JP_LOG_INFO, "Proactive step break - file: ".$file." - size: ".$size);
- $this->setBreakFlag();
- $this->_doneScanning = true;
- return true;
- }
- $packedSize += $size;
- $numberOfFiles++;
- $archiver->addFile($file, $this->_removePath, $this->_addPath);
- // Error propagation
- if($archiver->getError())
- {
- $this->setError($archiver->getError());
- return false;
- }
-
- // If this was the first file of the fragment and it exceeded the fragment's capacity,
- // break the step. Continuing with more operations after packing such a big file is
- // increasing the risk to hit a timeout.
- if( ($packedSize > JPMaxFragmentSize) && ($numberOfFiles == 1) )
- {
- JoomlapackLogger::WriteLog(_JP_LOG_INFO, "Breaking step *after* large file: ".$file." - size: ".$size);
- $this->setBreakFlag();
- return true;
- }
- }
-
- $this->_doneScanning = count($this->_fileList) > 0;
- return true;
- }
- }
-
- }