Overview
  • Namespace
  • Class

Namespaces

  • yii2cdn

Classes

  • yii2cdn\Cdn
  • yii2cdn\Component
  • yii2cdn\ConfigFile
  • yii2cdn\ConfigLoader
  • yii2cdn\ConfigParser
  • yii2cdn\File
  • yii2cdn\Section
  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\InvalidParamException;
 14 
 15 /**
 16  * Class ComponentConfigParser
 17  * Parse the component configuration array into components
 18  * 
 19  * @package common\yii2cdn
 20  * @author Junaid Atari <mj.atari@gmail.com>
 21  *
 22  * @access public
 23  * @version 0.1
 24  */
 25 class ConfigParser {
 26 
 27     /**
 28      * List of sections name
 29      * @var array
 30      */
 31     protected static $sections = [];
 32 
 33     /**
 34      * Component ID
 35      * @var string
 36      */
 37     protected $_id;
 38 
 39     /**
 40      * CDN Base URL
 41      * @var string
 42      */
 43     protected $baseUrl;
 44 
 45     /**
 46      * Component Configuration
 47      * @var array
 48      */
 49     protected $config = [];
 50 
 51     /**
 52      * CDN Custom aliases
 53      * @var array
 54      */
 55     protected $aliases = [];
 56 
 57     /**
 58      * Component file name id Configuration
 59      * @var array
 60      */
 61     protected $fileIds = [];
 62 
 63     /**
 64      * Files attributes
 65      * @var array [ID=>Mixed-Value]
 66      */
 67     protected $filesAttrs = [];
 68 
 69     /**
 70      * Files attributes
 71      * @var array [ID=>Mixed-Value]
 72      */
 73     protected $_props = [];
 74 
 75     /**
 76      * Predefined file attributes
 77      * @var array
 78      */
 79     protected $defFileAttrs = ['id', 'cdn', 'offline', 'options'];
 80 
 81     /**
 82      * ComponentConfigParser constructor.
 83      * @param $config Component Configuration
 84      */
 85     public function __construct ( array $config ) {
 86         $this->_id = $config['id'];
 87         $this->baseUrl = $config['baseUrl'];
 88         $this->config = $config['config'];
 89         self::$sections = $config['sections'];
 90         $this->_props['fileClass'] = $config['fileClass'];
 91         $this->_props['sectionClass'] = $config['sectionClass'];
 92     }
 93 
 94     /**
 95      * Replaces @component* tags from the components
 96      *
 97      * @see ComponentConfigParser::replaceComponentTagsFromFileName()
 98      * @param array $components Pre Build Components data
 99      * @return array Post components object
100      */
101     public static function touchComponentTags ( $components ) {
102         if ( !count ( $components ) ) {
103             return $components;
104         }
105 
106         # $reListed['filesId'], $reListed['componentsUrl']
107         $reListed = self::listFilesByRoot ( $components );
108 
109         foreach ( $components as $componentId => $sections ) {
110             foreach ( $sections as $sectionId => $data ) {
111                 if ( !in_array ( $sectionId, self::$sections ) || !count ( $data ) ) {
112                     continue;
113                 }
114 
115                 foreach ( $data as $fileId => $fileName ) {
116 
117                     $nFileName = !preg_match ( '/^@component([A-Za-z]+)/i', $fileName )
118                         ? $fileName
119                         : self::replaceComponentTagsFromFileName ( $fileName, $reListed );
120 
121                     $components[$componentId][$sectionId][$fileId] = $nFileName; //str_replace('//', '/', $nFileName);
122                 }
123             }
124         }
125 
126         return $components;
127     }
128 
129     /**
130      * Get the components files list<br>
131      * Key=Value pair of [COMPONENT_ID/SECTION_ID/FILE_ID]=>FILE_URL
132      * @param array $components Pre Build Components data
133      * @return array
134      */
135     protected static function listFilesByRoot ( $components ) {
136         if ( !count ( $components ) ) {
137             return $components;
138         }
139 
140         $filesId = [ ];
141         $componentsUrl = [ ];
142 
143         foreach ( $components as $componentId => $sections ) {
144 
145             $componentsUrl[$componentId] = $sections['baseUrl'];
146 
147             foreach ( $sections as $sectionId => $data ) {
148                 if ( !in_array ( $sectionId, self::$sections ) || !count ( $data ) ) {
149                     continue;
150                 }
151 
152                 foreach ( $data as $fileId => $fileName ) {
153 
154                     if ( strstr ( $fileId, '*' ) !== false ) {
155                         continue;
156                     }
157 
158                     // File unique id
159                     $uid = "{$componentId}/{$sectionId}/" . $fileId;
160 
161                     $filesId[$uid] = $fileName;
162                 }
163             }
164         }
165 
166         return [
167             'filesId' => $filesId,
168             'componentsUrl' => $componentsUrl
169         ];
170     }
171 
172     /**
173      * Replaces @component* tags (case insensitive) from given filename
174      * Tags (starts with @component)<br>
175      * <code>
176      *    > componentUrl(ID)
177      *    > componentFile(ID/SECTION/FILE_ID)
178      * </code>
179      *
180      * @param string $fileName File name replace from
181      * @param array $indexed Indexed data object
182      * @return array Replaced tags object
183      */
184     protected static function replaceComponentTagsFromFileName ( $fileName, array $indexed ) {
185         $patterns = [
186 
187             // tag: componentUrl(ID)
188             '/^@(?i)componentUrl(?-i)\(([^\)]+)\)(.+)$/' => function ( $match ) use ( $indexed ) {
189 
190                 if ( !array_key_exists ( $match[1], $indexed['componentsUrl'] ) ) {
191                     throw new InvalidConfigException ( "Unknown CDN component id '{$match[1]}' given" );
192                 }
193 
194                 return $indexed['componentsUrl'][$match[1]]
195                 . ( substr ( $match[2], 0, 1 ) !== '/' ? '/' . $match[2] : $match[2] );
196             },
197 
198             // tag: componentFile(ID/SECTION/FILE_ID)
199             '/^@(?i)componentFile(?-i)\(([^\)]+)\)$/' => function ( $match ) use ( $indexed ) {
200 
201                 if ( !array_key_exists ( $match[1], $indexed['filesId'] ) ) {
202                     throw new InvalidConfigException ( "Unknown CDN component file id '{$match[1]}' given" );
203                 }
204 
205                 return $indexed['filesId'][$match[1]];
206             },
207         ];
208 
209         return preg_replace_callback_array ( $patterns, $fileName );
210     }
211 
212     /**
213      * Get the parsed configuration
214      *
215      * @return array|null Component config | null when skipped
216      */
217     public function getParsed () {
218         if ( $this->getAttrOffline () === true && Cdn::isOnline () ) {
219             return null;
220         }
221 
222         $config = [
223             'id' => $this->_id,
224             'baseUrl' => $this->getUrl (),
225             'sectionClass' => $this->_props['sectionClass'],
226             'fileClass' => $this->_props['fileClass'],
227             'sections' => self::$sections,
228         ];
229 
230         // Validate section names if given
231         if ( count ( $offlineSections = $this->getAttrOfflineSections () ) ) {
232             foreach ( $offlineSections as $sect ) {
233                 if ( !in_array ( $sect, self::$sections ) ) {
234                     throw new InvalidConfigException ( "Offline Section '{$sect}' name doesn't exist" );
235                 }
236             }
237         }
238 
239         foreach ( self::$sections as $section ) {
240 
241             if ( in_array ( $section, $offlineSections ) && Cdn::isOnline () ) {
242                 continue;
243             }
244 
245             $config[$section] = $this->getFilesBySection ( $section );
246         }
247 
248         $config['fileAttrs'] = $this->filesAttrs;
249 
250         return $config;
251     }
252 
253     /**
254      * Get @offline attribute value (empty when not exist/null)
255      * @return string
256      */
257     protected function getAttrOffline () {
258         return array_key_exists('@offline', $this->config) && !empty($this->config['@offline'])
259             ? boolval($this->config['@offline'])
260             : false;
261     }
262 
263     /**
264      * Get @src attribute value (empty when not exist/null)
265      * @return string
266      */
267     protected function getUrl () {
268         $attrBaseUrl = $this->getAttrBaseUrl ();
269         $attrSrc = $this->getAttrSrc ();
270 
271         $baseUrl = empty( $attrBaseUrl ) ? $this->baseUrl : $attrBaseUrl;
272         $baseUrl .= empty( $attrSrc ) ? '/' . $this->_id : '/' . $attrSrc;
273 
274         return $baseUrl;
275     }
276 
277     /**
278      * Get @baseUrl attribute value (empty when not exist/null)
279      * @return string
280      */
281     protected function getAttrBaseUrl () {
282         return array_key_exists('@baseUrl', $this->config) && !empty($this->config['@baseUrl'])
283             ? trim($this->config['@baseUrl'])
284             : '';
285     }
286 
287     /**
288      * Get @src attribute value (empty when not exist/null)
289      * @return string
290      */
291     protected function getAttrSrc () {
292         return array_key_exists ( '@src', $this->config ) && !empty( $this->config['@src'] )
293             ? trim ( $this->config['@src'] )
294             : '';
295     }
296 
297     /**
298      * Get @offlineSections attribute value (empty when not exist/null)
299      *
300      * @return array
301      */
302     protected function getAttrOfflineSections () {
303         if ( !array_key_exists ( '@offlineSections', $this->config ) ) {
304             return [ ];
305         }
306 
307         $lst = $this->config['@offlineSections'];
308 
309         if ( !is_array ( $lst ) ) {
310             throw new InvalidParamException ( 'Parameter @offlineSections must be an array' );
311         }
312 
313         return $this->config['@offlineSections'];
314     }
315 
316     /**
317      * Get the files of section by name
318      * @param $type string Section name to get
319      * @return array
320      */
321     protected function getFilesBySection( $type ) {
322         if ( !in_array($type, self::$sections) || !isset($this->config[$type])
323             || !is_array($this->config[$type]) || empty($this->config[$type]) ) {
324             return [];
325         }
326 
327         $list = [];
328 
329         foreach ( $this->config[$type] as $file ) {
330 
331             $op = $this->getFileName($file, $type);
332 
333             if ( $op === null ) {
334                 continue;
335             }
336 
337             $_id = key($op);
338 
339             $list[$_id] = $op[$_id];
340         }
341 
342         return $list;
343     }
344 
345     /**
346      * Get the file id and name
347      * @param string|array $file File name | file object
348      * @param string $type Section name
349      * @throws \yii\base\InvalidParamException when File first param must not string or empty
350      * @throws \yii\base\InvalidParamException when File attribute param not string or empty
351      * @return array|null Key=>Value pair (ID=>FILENAME) / File skipped
352      */
353     protected function getFileName ( $file, $type ) {
354         if ( !is_array($file) || is_string($file) ) {
355             return [  uniqid('*') => $this->replaceFileNameTags($file) ];
356         }
357 
358         if ( empty($file[0]) || !is_string($file[0]) ) {
359             throw new InvalidParamException ('File first param must be string and not empty');
360         }
361 
362         $params = ['cdn', 'id'];
363 
364         foreach ($params as $p ) {
365             if ( !empty($file['@'.$p]) && !is_string($file['@'.$p]) ) {
366                 throw new InvalidParamException ("File @{$p} param must be string and not empty");
367             }
368         }
369 
370         if ( array_key_exists('@offline', $file) && $file['@offline'] !== false && Cdn::isOnline() ) {
371             return null;
372         }
373 
374         // Check file @cdn exist, use that version,
375         $filename = array_key_exists('@cdn', $file) && Cdn::isOnline()
376             ? $file['@cdn']
377             : $this->replaceFileNameTags($file[0]); // use offline version
378 
379         // Check file ID, if doesn't exist, assign a unique id
380         $fileId = array_key_exists('@id', $file)
381             ? trim($file['@id'])
382             : (string)uniqid('*');
383 
384         if ( array_key_exists('@options', $file) ) {
385             if ( !is_array($file['@options']) || !count($file['@options']) ){
386                 throw new InvalidParamException ( "File @options param must be an array and should not be empty" );
387             }
388 
389             $this->filesAttrs[$type]["@options/$fileId"] = $file['@options'];
390         }
391 
392         $attributes = preg_grep( '/^[a-zA-Z]+$/i', array_keys($file) );
393         
394         if ( count($attributes) ) {
395             foreach ( $attributes as $attr => $val ) {
396                 if ( in_array($attr, $this->defFileAttrs)) {
397                     continue;
398                 }
399 
400                 $this->filesAttrs[$type]["$val/$fileId"] = $file[$val];
401             }
402         }
403 
404         return [ $fileId => $filename ];
405     }
406 
407     /**
408      * Replaces tags (starts with @) from file name<br><br>
409      * Rules: (no url appends at beginning if)<br>
410      * <code>
411      *    > An actual url
412      *    > starts with //
413      * </code><br>
414      * Supported tags (case insensitive)<br>
415      * <code>
416      *    > appUrl : current application url
417      *    > baseUrl : component base url
418      *    > url (*) : * = any url ends with /
419      *    > alias (*) : * = CDN custom alias name
420      *    > yiiAlias (*) : * = Yii alias name
421      * </code><br>
422      * @param string $fileName Replace tags from filename
423      * @return string
424      */
425     protected function replaceFileNameTags ( $fileName ) {
426         if ( \substr( $fileName, 0, 2 ) === '//'
427             || \filter_var($fileName, \FILTER_VALIDATE_URL )) {
428             return $fileName;
429         }
430 
431         // Replace tags
432         if ( strstr($fileName, '@') !== false ) {
433 
434             $patterns = [
435 
436                 // tag: @alias(*)
437                 '/^(?i)@alias(?-i)\(([^\)]+)\)(.+)$/' => function ($match) {
438                     if (!array_key_exists($match[1], $this->aliases) ) {
439                         throw new InvalidConfigException ("Invalid custom url alias '{$match[1]}' given");
440                     }
441 
442                     return \Yii::getAlias($match[1]). (substr($match[2],0,1) !== '/' ? '/'.$match[2] : $match[2]);
443                 },
444 
445                 // tag: @yiiAlias(*)
446                 '/^(?i)@yiiAlias(?-i)\(([^\)]+)\)(.+)$/' => function ($match) {
447                     return \Yii::getAlias($match[1]). (substr($match[2],0,1) !== '/' ? '/'.$match[2] : $match[2]);
448                 },
449 
450                 // tag: @url(*)
451                 '/^(?i)@url(?-i)\(([^\)]+)\)(.+)$/' => function ($match) {
452                     return $match[1]. (substr($match[2],0,1) !== '/' ? '/'.$match[2] : $match[2]);
453                 },
454 
455                 // tag: @appUrl
456                 '/^((?i)@appUrl(?-i))(.+)$/' => function ($match) {
457                     return \Yii::$app->request->baseUrl . $match[2];
458                 },
459 
460                 // tag: @baseUrl
461                 '/^((?i)@baseUrl(?-i))(.+)$/' => function ($match) {
462                     return $this->getUrl() . $match[2];
463                 },
464             ];
465 
466             return preg_replace_callback_array($patterns, $fileName);
467         }
468 
469         return rtrim($this->getUrl(), '/') . "/" . ltrim($fileName, '/');
470     }
471 }
472 
API documentation generated by ApiGen