1 <?php
2
3 /**
4 * @copyright Copyright (c) 2016 Junaid Atari
5 * @link http://junaidatari.com Website
6 * @see http://www.github.com/blacksmoke26/yii2-cdn
7 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
8 */
9
10 namespace yii2cdn;
11
12 use yii\base\InvalidConfigException;
13 use yii\base\UnknownPropertyException;
14 use yii\base\InvalidParamException;
15 use yii\helpers\ArrayHelper;
16
17 defined('YII2CDN_OFFLINE') or define('YII2CDN_OFFLINE', false);
18
19 /**
20 * Yii2 CDN Component
21 *
22 * @package yii2cdn
23 * @author Junaid Atari <mj.atari@gmail.com>
24 *
25 * @access public
26 * @version 0.1
27 */
28 class Cdn extends \yii\base\Component {
29
30 /**
31 * Base url to cdn directory
32 * @var string
33 */
34 public $baseUrl = null;
35
36 /**
37 * base path to cdn directory
38 * @var string
39 */
40 public $basePath = null;
41
42 /**
43 * Custom url aliases, replaces with @alias(*) in files url
44 * Usage:
45 * ['xyz' => '/url/to', ...]
46 * @var array
47 */
48 public $aliases = [];
49
50 /**
51 * CDN component class
52 * default: \yii2cdn\Component
53 * @var string
54 */
55 public $componentClass = '\yii2cdn\Component';
56
57 /**
58 * CDN components configuration parser class
59 * default: \yii2cdn\ComponentConfigParser
60 * @var string
61 */
62 public $configParserClass = '\yii2cdn\ConfigParser';
63
64 /**
65 * CDN component section class
66 * default: \yii2cdn\ComponentSection
67 * @var string
68 */
69 public $sectionClass = '\yii2cdn\Section';
70
71 /**
72 * CDN Configuration File Class
73 * default: \yii2cdn\ConfigFile
74 * @var string
75 */
76 public $configFileClass = '\yii2cdn\ConfigFile';
77
78 /**
79 * CDN component section file class
80 * default: \yii2cdn\SectionFile
81 * @var string
82 */
83 public $fileClass = '\yii2cdn\File';
84
85 /**
86 * CDN component configuration loader class
87 * default: \yii2cdn\ConfigLoader
88 * @var string
89 */
90 public $configLoaderClass = '\yii2cdn\ConfigLoader';
91
92 /**
93 * CDN components configuration files list
94 * Usage:
95 * 1. 'path/to/cdn-config.php' : main file path
96 * 2. ['path/to/cdn-config.php'] : main file path
97 * 3. ['path/to/cdn-config.php', 'offline'=>false] : online cdn file path
98 * 4. ['path/to/cdn-config.php', 'offline'=>true] : offline cdn file path
99 * @var array
100 */
101 public $configs = [];
102
103 /**
104 * CDN components configuration
105 * @var array
106 */
107 public $components = [];
108
109 /**
110 * Sections name list
111 * default: (<code>css</code>, <code>js</code>)
112 * @var array
113 */
114 public $sections = ['js', 'css'];
115
116 /**
117 * Cache Key for caching built components configuration to load fast
118 * @var string
119 */
120 public $cacheKey = null;
121
122 /**
123 * Enable storing components configuration in cache
124 * @var boolean
125 */
126 public $enableCaching = false;
127
128 /**
129 * Components registered under cdn
130 *
131 * @var array
132 */
133 protected $_regComponents = [];
134
135 /**
136 * Component intializer
137 * @throws \yii\base\InvalidConfigException when property is empty
138 */
139 public function init () {
140 parent::init();
141
142 foreach ( ['baseUrl', 'basePath'] as $prop ) {
143 if ( empty($this->$prop) ) {
144 throw new InvalidConfigException("{$prop} property is empty");
145 }
146 }
147
148 if ( $this->enableCaching && empty($this->cacheKey) ) {
149 throw new InvalidConfigException("cacheKey property is empty");
150 }
151
152 // Build components
153 $this->buildComponentsCache();
154 }
155
156 /**
157 * Get a ConfigFile Object
158 * @param string $file
159 * @return ConfigFile
160 */
161 protected function getFileConfigObject ( $file = null ) {
162 /** @var ConfigFile $fileConfig */
163 return \Yii::createObject($this->configFileClass, [ [
164 'path' => $file,
165 'componentClass' => $this->componentClass,
166 'configParserClass' => $this->configParserClass,
167 'configLoaderClass' => $this->configLoaderClass,
168 'fileClass' => $this->fileClass,
169 'sectionClass' => $this->sectionClass,
170 'basePath' => $this->basePath,
171 'baseUrl' => $this->baseUrl,
172 'aliases' => $this->aliases,
173 'sections' => $this->sections
174 ]]);
175 }
176
177 /**
178 * Import components from configuration array
179 *
180 * @param array $config Components configuration
181 */
182 protected function loadComponents ( array $config ) {
183 /** @var ConfigFile $configFile */
184 $configFile = $this->getFileConfigObject ( null );
185
186 $this->_regComponents = ArrayHelper::merge (
187 $this->_regComponents,
188 $configFile->get ( ( $config ) )
189 );
190 }
191
192 /**
193 * Import components from configuration file
194 * @param string $path Components configuration file path
195 */
196 protected function loadComponentsFile ( $path ) {
197 /** @var ConfigFile $configFile */
198 $configFile = $this->getFileConfigObject( $path );
199
200 $this->_regComponents = ArrayHelper::merge(
201 $this->_regComponents,
202 $configFile->get()
203 );
204 }
205
206 /**
207 * Build a components list
208 */
209 protected function buildComponentsCache () {
210 if ( $this->enableCaching ) {
211
212 $cached = \Yii::$app->cache->get ( $this->cacheKey );
213
214 if ( $cached !== false ) {
215 $this->_regComponents = $cached;
216 return;
217 }
218 }
219
220 // @property `components` : load CDN components config
221 if ( is_array($this->components) && !empty($this->components) ) {
222 $this->loadComponents($this->components);
223 }
224
225 // @property `configs` : load CDN components config files
226 if ( is_array($this->configs) && !empty($this->configs) ) {
227
228 foreach ( $this->configs as $cfg ) {
229
230 if ( empty($cfg)) {
231 continue;
232 }
233
234 $this->loadComponentsFile($cfg);
235 }
236 }
237
238 if ( $this->enableCaching ) {
239 \Yii::$app->cache->set($this->cacheKey, $this->_regComponents);
240 }
241 }
242
243 /**
244 * Remove the cache and rebuild components list
245 */
246 public function refresh () {
247 \Yii::$app->cache->delete( $this->cacheKey );
248 $this->buildComponentsCache();
249 }
250
251 /**
252 * Check that Mode Live or Offline
253 *
254 * @return bool
255 */
256 public static function isOnline () {
257 return !defined ( 'YII2CDN_OFFLINE' ) ? true : !YII2CDN_OFFLINE;
258 }
259
260 /**
261 * Get cdn component by ID
262 * @see Cdn::exists()
263 * @param string $id Component ID
264 * @return Component|null Component Object
265 */
266 public function get ( $id, $throwException = true ) {
267 if ( !$this->exists($id, $throwException) ) {
268 return null;
269 }
270
271 return $this->_regComponents[$id];
272 }
273
274 /**
275 * Check that cdn component exists
276 * @param string $id Component ID
277 * @param boolean $throwException (optional) Throw exception when unknown component id given (default: false)
278 * @throws \yii\base\UnknownPropertyException When unknown component id given
279 * @return boolean True when exist, False when undefined
280 */
281 public function exists ( $id, $throwException = true ) {
282 if ( !array_key_exists($id, $this->_regComponents) ) {
283 if ( $throwException ) {
284 throw new UnknownPropertyException ("Unknown cdn component '{$id}'");
285 } else {
286 return false;
287 }
288 }
289
290 return true;
291 }
292
293 /**
294 * Get the file by root
295 * Root example : component-id/section
296 *
297 * @see Component::get()
298 * @see Section::getSection()
299 * @param string $root Root to file
300 * @param bool $throwException True will throw exception (default: true)
301 * @throws \yii\base\UnknownPropertyException When unknown component id given
302 * @throws \yii\base\InvalidParamException When null given as section
303 * @throws \yii\base\UnknownPropertyException When section name not found
304 * @return \yii2cdn\Section Section Object
305 */
306 public function getSectionByRoot ( $root, $throwException = true ) {
307 // validate the root
308 if ( !is_string ( $root ) || substr_count ( $root, '/' ) != 1 ) {
309 throw new InvalidParamException ( "Invalid section root '{$root}' given" );
310 }
311
312 list ( $componentId, $sectionId ) = explode ( '/', $root );
313
314 return $this->get ( $componentId, $throwException )->getSection ( $sectionId, $throwException );
315 }
316
317 /**
318 * Get the file by root
319 * Root example : component-id/section/file-id
320 * @see Component::get()
321 * @see Section::getSection()
322 * @see Section::getFileById()
323 * @param string $root Root to file
324 * @param bool $asUrl True will return file url instead of object (default: false)
325 * @param bool $throwException True will throw exception (default: true)
326 * @throws \yii\base\UnknownPropertyException When unknown component id given
327 * @throws \yii\base\InvalidParamException When null given as section
328 * @throws \yii\base\UnknownPropertyException When section name not found
329 * @throws \yii\base\UnknownPropertyException When file id not found
330 * @return \yii2cdn\File|string|null Section file | File Url | Null when not found
331 */
332 public function getFileByRoot ( $root, $asUrl = false, $throwException = true ) {
333 // validate the root
334 if ( !is_string($root) || substr_count($root, '/') != 2 ) {
335 throw new InvalidParamException ("Invalid file root '{$root}' given");
336 }
337
338 list ($componentId, $sectionId, $fileId) = explode('/', $root);
339
340 return $this->get($componentId, $throwException)->getFileByRoot( "$sectionId/$fileId", $asUrl, $throwException );
341 }
342
343 /**
344 * Perform a callback function when Offline mode is active
345 * @see Cdn::isOnline()
346 * @param callable $callback A callback function
347 * <code>
348 * function ( \yii2cdn\Cdn $cdn ) {
349 * // some logic here
350 * }
351 * </code>
352 * @param string $property (optional) Default component property name (default: cdn)
353 * @return Cdn
354 * @throws \yii\base\InvalidConfigException When the callback parameter is not a function
355 */
356 public function whenOffline ( $callback, $property = 'cdn' ) {
357
358 if ( !is_callable($callback) ) {
359 throw new InvalidParamException ("Parameter '{callback}' should be a function");
360 }
361
362 if ( !self::isOnline() ) {
363 call_user_func_array( $callback, [\Yii::$app->get($property)] );
364 }
365
366 return $this;
367 }
368
369 /**
370 * Perform a callback function when Online mode is active
371 * @see Cdn::isOnline()
372 * @param callable $callback A callback function
373 * <code>
374 * function ( \yii2cdn\Cdn $cdn ) {
375 * // some logic here
376 * }
377 * </code>
378 * @param string $property (optional) Default component property name (default: cdn)
379 * @return Cdn
380 * @throws \yii\base\InvalidConfigException When the callback parameter is not a function
381 */
382 public function whenOnline ( $callback, $property = 'cdn' ) {
383
384 if ( !is_callable($callback) ) {
385 throw new InvalidParamException ("Parameter '{callback}' should be a function");
386 }
387
388 if ( self::isOnline() ) {
389 call_user_func_array( $callback, [\Yii::$app->get($property)] );
390 }
391
392 return $this;
393 }
394 }
395