1 <?php
2
3 4 5 6 7 8
9
10 namespace yii2cdn;
11
12 use yii\base\InvalidConfigException;
13 use yii\base\InvalidParamException;
14
15 16 17 18 19 20 21 22 23 24
25 class ConfigParser {
26
27 28 29 30
31 protected static $sections = [];
32
33 34 35 36
37 protected $_id;
38
39 40 41 42
43 protected $baseUrl;
44
45 46 47 48
49 protected $config = [];
50
51 52 53 54
55 protected $aliases = [];
56
57 58 59 60
61 protected $fileIds = [];
62
63 64 65 66
67 protected $filesAttrs = [];
68
69 70 71 72
73 protected $_props = [];
74
75 76 77 78
79 protected $defFileAttrs = ['id', 'cdn', 'offline', 'options'];
80
81 82 83 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 96 97 98 99 100
101 public static function touchComponentTags ( $components ) {
102 if ( !count ( $components ) ) {
103 return $components;
104 }
105
106
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;
122 }
123 }
124 }
125
126 return $components;
127 }
128
129 130 131 132 133 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
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 174 175 176 177 178 179 180 181 182 183
184 protected static function replaceComponentTagsFromFileName ( $fileName, array $indexed ) {
185 $patterns = [
186
187
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
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 214 215 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
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 255 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 265 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 279 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 289 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 299 300 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 318 319 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 347 348 349 350 351 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
375 $filename = array_key_exists('@cdn', $file) && Cdn::isOnline()
376 ? $file['@cdn']
377 : $this->replaceFileNameTags($file[0]);
378
379
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 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 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
432 if ( strstr($fileName, '@') !== false ) {
433
434 $patterns = [
435
436
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
446 '/^(?i)@yiiAlias(?-i)\(([^\)]+)\)(.+)$/' => function ($match) {
447 return \Yii::getAlias($match[1]). (substr($match[2],0,1) !== '/' ? '/'.$match[2] : $match[2]);
448 },
449
450
451 '/^(?i)@url(?-i)\(([^\)]+)\)(.+)$/' => function ($match) {
452 return $match[1]. (substr($match[2],0,1) !== '/' ? '/'.$match[2] : $match[2]);
453 },
454
455
456 '/^((?i)@appUrl(?-i))(.+)$/' => function ($match) {
457 return \Yii::$app->request->baseUrl . $match[2];
458 },
459
460
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