From 8f9cfa2c304f347ac66ba32c7dfdea00feae0bb3 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Tue, 16 Apr 2013 21:26:19 -0400 Subject: [PATCH] draft of script/asset management is done. --- framework/helpers/base/FileHelper.php | 52 +++++++++++++++- framework/web/AssetBundle.php | 40 ++++++------ framework/web/AssetManager.php | 111 +++++++++++++++------------------- 3 files changed, 116 insertions(+), 87 deletions(-) diff --git a/framework/helpers/base/FileHelper.php b/framework/helpers/base/FileHelper.php index 5bab36f..d3804fb 100644 --- a/framework/helpers/base/FileHelper.php +++ b/framework/helpers/base/FileHelper.php @@ -86,9 +86,12 @@ class FileHelper { if (function_exists('finfo_open')) { $info = finfo_open(FILEINFO_MIME_TYPE, $magicFile); - if ($info && ($result = finfo_file($info, $file)) !== false) { + if ($info) { + $result = finfo_file($info, $file); finfo_close($info); - return $result; + if ($result !== false) { + return $result; + } } } @@ -122,4 +125,49 @@ class FileHelper } + /** + * Copies a whole directory as another one. + * The files and sub-directories will also be copied over. + * @param string $src the source directory + * @param string $dst the destination directory + * @param array $options options for directory copy. Valid options are: + * + * - dirMode: integer, the permission to be set for newly copied directories. Defaults to 0777. + * - fileMode: integer, the permission to be set for newly copied files. Defaults to the current environment setting. + * - filter: callback, a PHP callback that is called for every sub-directory and file to + * determine if it should be copied. The signature of the callback should be: + * + * ~~~ + * // $path is the file/directory path to be copied + * function ($path) { + * // return a boolean indicating if $path should be copied + * } + * ~~~ + */ + public static function copyDirectory($src, $dst, $options = array()) + { + if (!is_dir($dst)) { + mkdir($dst, isset($options['dirMode']) ? $options['dirMode'] : 0777, true); + } + + $handle = opendir($src); + while (($file = readdir($handle)) !== false) { + if ($file === '.' || $file === '..') { + continue; + } + $srcPath = $src . DIRECTORY_SEPARATOR . $file; + if (!isset($options['filter']) || call_user_func($options['filter'], $srcPath)) { + $dstPath = $dst . DIRECTORY_SEPARATOR . $file; + if (is_file($srcPath)) { + copy($srcPath, $dstPath); + if (isset($options['fileMode'])) { + chmod($dstPath, $options['fileMode']); + } + } else { + static::copyDirectory($srcPath, $dstPath, $options); + } + } + } + closedir($handle); + } } \ No newline at end of file diff --git a/framework/web/AssetBundle.php b/framework/web/AssetBundle.php index d3e29d8..a6b69d2 100644 --- a/framework/web/AssetBundle.php +++ b/framework/web/AssetBundle.php @@ -11,27 +11,6 @@ use Yii; use yii\base\Object; /** - * Each asset bundle should be declared with the following structure: - * - * ~~~ - * array( - * 'basePath' => '...', - * 'baseUrl' => '...', // if missing, the bundle will be published to the "www/assets" folder - * 'js' => array( - * 'js/main.js', - * 'js/menu.js', - * 'js/base.js' => self::POS_HEAD, - * 'css' => array( - * 'css/main.css', - * 'css/menu.css', - * ), - * 'depends' => array( - * 'jquery', - * 'yii', - * 'yii/treeview', - * ), - * ) - * ~~~ * @author Qiang Xue * @since 2.0 */ @@ -61,9 +40,26 @@ class AssetBundle extends Object * * Note that you should not use backward slashes "\" to specify JavaScript files. * - * A JavaScript file can be associated with the options: // todo + * Each JavaScript file may be associated with options. In this case, the array key + * should be the JavaScript file path, while the corresponding array value should + * be the option array. The options will be passed to [[ViewContent::registerJsFile()]]. */ public $js = array(); + /** + * @var array list of CSS files that this bundle contains. Each CSS file can + * be specified in one of the three formats: + * + * - a relative path: a path relative to [[basePath]] if [[basePath]] is set, + * or a URL relative to [[baseUrl]] if [[basePath]] is not set; + * - an absolute URL; + * - a path alias that can be resolved into a relative path or an absolute URL. + * + * Note that you should not use backward slashes "\" to specify CSS files. + * + * Each CSS file may be associated with options. In this case, the array key + * should be the CSS file path, while the corresponding array value should + * be the option array. The options will be passed to [[ViewContent::registerCssFile()]]. + */ public $css = array(); /** * @var array list of the bundle names that this bundle depends on diff --git a/framework/web/AssetManager.php b/framework/web/AssetManager.php index 1390f47..c06d6b2 100644 --- a/framework/web/AssetManager.php +++ b/framework/web/AssetManager.php @@ -11,6 +11,7 @@ use Yii; use yii\base\Component; use yii\base\InvalidConfigException; use yii\base\InvalidParamException; +use yii\helpers\FileHelper; /** * @@ -58,24 +59,22 @@ class AssetManager extends Component **/ public $excludeFiles = array('.svn', '.gitignore'); /** - * @var integer the permission to be set for newly generated asset files. - * This value will be used by PHP chmod function. - * Defaults to 0666, meaning the file is read-writable by all users. - * @since 1.1.8 + * @var integer the permission to be set for newly published asset files. + * This value will be used by PHP chmod() function. + * If not set, the permission will be determined by the current environment. */ - public $newFileMode = 0666; + public $fileMode; /** * @var integer the permission to be set for newly generated asset directories. - * This value will be used by PHP chmod function. + * This value will be used by PHP chmod() function. * Defaults to 0777, meaning the directory can be read, written and executed by all users. - * @since 1.1.8 */ - public $newDirMode = 0777; + public $dirMode = 0777; + /** - * @var array published assets + * Initializes the component. + * @throws InvalidConfigException if [[basePath]] is invalid */ - private $_published = array(); - public function init() { parent::init(); @@ -136,16 +135,22 @@ class AssetManager extends Component } /** + * @var array published assets + */ + private $_published = array(); + + /** * Publishes a file or a directory. - * This method will copy the specified asset to a web accessible directory - * and return the URL for accessing the published asset. - * + * + * This method will copy the specified file or directory to [[basePath]] so that + * it can be accessed via the Web server. + * + * If the asset is a file, its file modification time will be checked to avoid + * unnecessary file copying. + * + * If the asset is a directory, all files and subdirectories under it will be published recursively. + * Note, in case $forceCopy is false the method only checks the existence of the target + * directory to avoid repetitive copying (which is very expensive). * * Note: On rare scenario, a race condition can develop that will lead to a * one-time-manifestation of a non-critical problem in the creation of the directory @@ -155,23 +160,14 @@ class AssetManager extends Component * discussion: http://code.google.com/p/yii/issues/detail?id=2579 * * @param string $path the asset (file or directory) to be published - * @param boolean $hashByName whether the published directory should be named as the hashed basename. - * If false, the name will be the hash taken from dirname of the path being published and path mtime. - * Defaults to false. Set true if the path being published is shared among - * different extensions. - * @param integer $level level of recursive copying when the asset is a directory. - * Level -1 means publishing all subdirectories and files; - * Level 0 means publishing only the files DIRECTLY under the directory; - * level N means copying those directories that are within N levels. - * @param boolean $forceCopy whether we should copy the asset file or directory even if it is already published before. - * This parameter is set true mainly during development stage when the original - * assets are being constantly changed. The consequence is that the performance + * @param boolean $forceCopy whether the asset should ALWAYS be copied even if it is found + * in the target directory. This parameter is mainly useful during the development stage + * when the original assets are being constantly changed. The consequence is that the performance * is degraded, which is not a concern during development, however. - * This parameter has been available since version 1.1.2. * @return string an absolute URL to the published asset - * @throws CException if the asset to be published does not exist. + * @throws InvalidParamException if the asset to be published does not exist. */ - public function publish($path, $hashByName = false, $level = -1, $forceCopy = false) + public function publish($path, $forceCopy = false) { if (isset($this->_published[$path])) { return $this->_published[$path]; @@ -183,13 +179,13 @@ class AssetManager extends Component } if (is_file($src)) { - $dir = $this->hash($hashByName ? basename($src) : dirname($src) . filemtime($src)); + $dir = $this->hash(dirname($src) . filemtime($src)); $fileName = basename($src); $dstDir = $this->basePath . DIRECTORY_SEPARATOR . $dir; $dstFile = $dstDir . DIRECTORY_SEPARATOR . $fileName; if (!is_dir($dstDir)) { - @mkdir($dstDir, $this->newDirMode, true); + @mkdir($dstDir, $this->dirMode, true); } @@ -197,29 +193,26 @@ class AssetManager extends Component if (!is_file($dstFile)) { symlink($src, $dstFile); } - } elseif (@filemtime($dstFile) < @filemtime($src)) { + } elseif (@filemtime($dstFile) < @filemtime($src) || $forceCopy) { copy($src, $dstFile); - @chmod($dstFile, $this->newFileMode); + if ($this->fileMode !== null) { + @chmod($dstFile, $this->fileMode); + } } $url = $this->baseUrl . "/$dir/$fileName"; } else { - $dir = $this->hash($hashByName ? basename($src) : $src . filemtime($src)); + $dir = $this->hash($src . filemtime($src)); $dstDir = $this->basePath . DIRECTORY_SEPARATOR . $dir; - if ($this->linkAssets) { if (!is_dir($dstDir)) { symlink($src, $dstDir); } - } else { - if (!is_dir($dstDir) || $forceCopy) { - FileHelper::copyDirectory($src, $dstDir, array( - 'exclude' => $this->excludeFiles, - 'level' => $level, - 'newDirMode' => $this->newDirMode, - 'newFileMode' => $this->newFileMode, - )); - } + } elseif (!is_dir($dstDir) || $forceCopy) { + FileHelper::copyDirectory($src, $dstDir, array( + 'dirMode' => $this->dirMode, + 'fileMode' => $this->fileMode, + )); } $url = $this->baseUrl . '/' . $dir; @@ -232,20 +225,16 @@ class AssetManager extends Component * This method does not perform any publishing. It merely tells you * if the file or directory is published, where it will go. * @param string $path directory or file path being published - * @param boolean $hashByName whether the published directory should be named as the hashed basename. - * If false, the name will be the hash taken from dirname of the path being published and path mtime. - * Defaults to false. Set true if the path being published is shared among - * different extensions. * @return string the published file path. False if the file or directory does not exist */ - public function getPublishedPath($path, $hashByName = false) + public function getPublishedPath($path) { if (($path = realpath($path)) !== false) { $base = $this->basePath . DIRECTORY_SEPARATOR; if (is_file($path)) { - return $base . $this->hash($hashByName ? basename($path) : dirname($path) . filemtime($path)) . DIRECTORY_SEPARATOR . basename($path); + return $base . $this->hash(dirname($path) . filemtime($path)) . DIRECTORY_SEPARATOR . basename($path); } else { - return $base . $this->hash($hashByName ? basename($path) : $path . filemtime($path)); + return $base . $this->hash($path . filemtime($path)); } } else { return false; @@ -257,22 +246,18 @@ class AssetManager extends Component * This method does not perform any publishing. It merely tells you * if the file path is published, what the URL will be to access it. * @param string $path directory or file path being published - * @param boolean $hashByName whether the published directory should be named as the hashed basename. - * If false, the name will be the hash taken from dirname of the path being published and path mtime. - * Defaults to false. Set true if the path being published is shared among - * different extensions. * @return string the published URL for the file or directory. False if the file or directory does not exist. */ - public function getPublishedUrl($path, $hashByName = false) + public function getPublishedUrl($path) { if (isset($this->_published[$path])) { return $this->_published[$path]; } if (($path = realpath($path)) !== false) { if (is_file($path)) { - return $this->baseUrl . '/' . $this->hash($hashByName ? basename($path) : dirname($path) . filemtime($path)) . '/' . basename($path); + return $this->baseUrl . '/' . $this->hash(dirname($path) . filemtime($path)) . '/' . basename($path); } else { - return $this->baseUrl . '/' . $this->hash($hashByName ? basename($path) : $path . filemtime($path)); + return $this->baseUrl . '/' . $this->hash($path . filemtime($path)); } } else { return false;