Completed
Push — master ( f32499...fe2624 )
by Vitaly
06:07
created

Router::gatherResources()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 31
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 3
Metric Value
cc 1
eloc 1
c 3
b 0
f 3
nc 1
nop 1
dl 0
loc 31
rs 8.8571
1
<?php
2
namespace samsonphp\resource;
3
4
use Aws\CloudFront\Exception\Exception;
5
use samson\core\ExternalModule;
6
use samsonphp\event\Event;
7
use samsonphp\resource\exception\ResourceNotFound;
8
9
/**
10
 * Resource router for serving static resource from unreachable web-root paths.
11
 *
12
 * @author Vitaly Iegorov <[email protected]>
13
 * @author Nikita Kotenko <[email protected]>
14
 */
15
class Router extends ExternalModule
16
{
17
    /** Event showing that new gather resource file was created */
18
    const EVENT_CREATED = 'resource.created';
19
    /** Event showing that new gather resource file was created */
20
    const EVENT_START_GENERATE_RESOURCES = 'resource.start.generate.resources';
21
22
    /** @var string Marker for inserting generated JS link in template */
23
    public $jsMarker = '</body>';
24
    /** @var string Marker for inserting generated CSS link in template */
25
    public $cssMarker = '</head>';
26
    /** Cached resources path collection */
27
    public $cached = array();
28
    /** Collection of updated cached resources for notification of changes */
29
    public $updated = array();
30
31
    /** Identifier */
32
    protected $id = STATIC_RESOURCE_HANDLER;
33
34
    /** Pointer to processing module */
35
    private $currentModule;
36
    /** @var string Current processed resource */
37
    private $currentResource;
38
39
    /**
40
     * Parse URL to get module name and relative path to resource
41
     *
42
     * @param string $url String for parsing
43
     *
44
     * @return array Array [0] => module name, [1]=>relative_path
45
     */
46
    public static function parseURL($url, &$module = null, &$path = null)
47
    {
48
        // If we have URL to resource router
49
        if (preg_match('/'.STATIC_RESOURCE_HANDLER.'\/\?p=(((\/src\/|\/vendor\/samson[^\/]+\/)(?<module>[^\/]+)(?<path>.+))|((?<local>.+)))/ui', $url, $matches)) {
50
            if (array_key_exists('local', $matches)) {
51
                $module = 'local';
52
                $path = $matches['local'];
53
            } else {
54
                $module = $matches['module'];
55
                $path = $matches['path'];
56
            }
57
            return true;
58
        } else {
59
            return false;
60
        }
61
    }
62
63
    /** @see ModuleConnector::init() */
64
    public function init(array $params = array())
65
    {
66
        parent::init($params);
67
68
        $moduleList = $this->system->module_stack;
0 ignored issues
show
Bug introduced by
Accessing module_stack on the interface samsonframework\core\SystemInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
69
70
        // TODO: SamsonCMS does not remove its modules from this collection
71
        Event::fire(self::EVENT_START_GENERATE_RESOURCES, array(&$moduleList));
72
73
        $this->generateResources($moduleList);
74
75
        // Subscribe to core rendered event
76
        $this->system->subscribe('core.rendered', array($this, 'renderer'));
77
    }
78
79
    public function gatherResources($path)
0 ignored issues
show
Unused Code introduced by
The parameter $path is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
80
    {
81
        // we need to read all resources at specified path
82
        // we need to gather all CSS resources into one file
83
        // we need to gather all LESS resources into one file
84
        // we need to gather all SASS resources into one file
85
        // we need to gather all COFFEE resources into one file
86
        // we need to gather all JS resources into one file
87
        // we need to be able to include all files separately into template in development
88
        // we need handlers/events for each resource type gathered with less and our approach
89
        // we have problems that our variables are split around modules to make this work
90
        // we need to gather all files and then parse on come up with different solution
91
92
        /**
93
         * Workaround for fetching different LESS variables in different files:
94
         * 1. We iterate all LESS files in this modules list.
95
         * 2. We parse all variables and all values from this files(probably recursively) to
96
         * count values for nested variables.
97
         * 3. We iterate normally all files and create cache for each file in project cache by
98
         * module/folder structure.
99
         * 4. We insert values for calculated LESS variables in this compiled files by passing
100
         * collection of LESS values to transpiller.
101
         * 5. In dev mode we do no need to gather all in one file just output a list of compiled
102
         * css files in template in gathering order. All url are "/cache/" relative.
103
         * 6. We create event/handler and give other module ability to gather everything into one file.
104
         * 7. We give ability to other module to minify/optimize css files.
105
         * 8. We rewrite paths to static resources using current logic with validation.
106
         * 9. We give other modules ability to upload this static files to 3rd party storage.
107
         */
108
109
    }
110
111
    public function generateResources($moduleList, $templatePath = 'default')
112
    {
113
        $dir = str_replace(array('/', '.'), '_', $templatePath);
114
115
        // Cache main web resources
116
        foreach (array(array('js'), array('css', 'less'), array('coffee')) as $rts) {
117
            // Get first resource type as extension
118
            $rt = $rts[0];
119
120
            $hash_name = '';
121
122
            // Iterate gathered namespaces for their resources
123
            /** @var Module $module */
124
            foreach ($moduleList as $id => $module) {
125
                // If necessary resources has been collected
126
                foreach ($rts as $_rt) {
127
                    if (isset($module->resourceMap->$_rt)) {
128
                        foreach ($module->resourceMap->$_rt as $resource) {
129
                            // Created string with last resource modification time
130
                            $hash_name .= filemtime($resource);
131
                        }
132
                    }
133
                }
134
            }
135
136
            // Get hash that's describes resource status
137
            $hash_name = md5($hash_name) . '.' . $rt;
138
139
            $file = $hash_name;
140
141
            // If cached file does not exists
142
            if ($this->cache_refresh($file, true, $dir)) {
143
                // Read content of resource files
144
                $content = '';
145
                foreach ($moduleList as $id => $module) {
146
                    $this->currentModule = $module;
147
                    // If this ns has resources of specified type
148
                    foreach ($rts as $_rt) {
149
                        if (isset($module->resourceMap->$_rt)) {
150
                            foreach ($module->resourceMap->$_rt as $resource) {
151
                                // Store current processing resource
152
                                $this->currentResource = $resource;
153
                                // Read resource file
154
                                $c = file_get_contents($resource);
155
                                // Rewrite url in css
156
                                if ($rt === 'css') {
157
                                    $c = preg_replace_callback('/url\s*\(\s*(\'|\")?([^\)\s\'\"]+)(\'|\")?\s*\)/i',
158
                                        array($this, 'replaceUrlCallback'), $c);
159
                                }
160
                                // Gather processed resource text together
161
                                $content .= "\n\r" . $c;
162
                            }
163
                        }
164
                    }
165
                }
166
167
                // Fire event that new resource has been generated
168
                Event::fire(self::EVENT_CREATED, array($rt, &$content, &$file, &$this));
169
170
                // Fix updated resource file with new path to it
171
                $this->updated[$rt] = $file;
172
173
                // Create cache file
174
                file_put_contents($file, $content);
175
            }
176
177
            // Save path to resource cache
178
            $this->cached[$rt][$templatePath] = __SAMSON_CACHE_PATH . $this->id . '/' . $dir . '/' . $hash_name;
179
        }
180
    }
181
182
    /**
183
     * Core render handler for including CSS and JS resources to html
184
     *
185
     * @param string $view   View content
186
     * @param array  $data   View data
187
     * @param null   $module Module instance
188
     *
189
     * @return string Processed view content
190
     */
191
    public function renderer(&$view, $data = array(), $module = null)
192
    {
193
        $templateId = isset($this->cached['css'][$this->system->template()])
194
            ? $this->system->template()
195
            : 'default';
196
197
        // Define resource urls
198
        $css = Resource::getWebRelativePath($this->cached['css'][$templateId]);
199
        $js = Resource::getWebRelativePath($this->cached['js'][$templateId]);
200
201
        // TODO: Прорисовка зависит от текущего модуля, сделать єто через параметр прорисовщика
202
        // If called from compressor
203
        if ($module->id() === 'compressor') {
0 ignored issues
show
Bug introduced by
The method id cannot be called on $module (of type null).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
204
            $templateId = isset($this->cached['css'][$data['file']]) ? $data['file'] : 'default';
205
            $css = url()->base() . basename($this->cached['css'][$templateId]);
206
            $js = url()->base() . basename($this->cached['js'][$templateId]);
207
        }
208
209
        // Inject resource links
210
        return $view = $this->injectCSS($this->injectJS($view, $js), $css);
211
    }
212
213
    /**
214
     * Inject CSS link into view.
215
     *
216
     * @param string $view View code
217
     * @param string $path Resource path
218
     *
219
     * @return string Modified view
220
     */
221
    protected function injectCSS($view, $path)
222
    {
223
        // Put css link at the end of <head> page block
224
        return str_ireplace(
225
            $this->cssMarker,
226
            "\n" . '<link type="text/css" rel="stylesheet" property="stylesheet" href="' . $path . '">' . "\n" . $this->cssMarker,
227
            $view
228
        );
229
    }
230
231
    /**
232
     * Inject JS link into view.
233
     *
234
     * @param string $view View code
235
     * @param string $path Resource path
236
     *
237
     * @return string Modified view
238
     */
239
    protected function injectJS($view, $path)
240
    {
241
        // Put javascript link in the end of the document
242
        return str_ireplace(
243
            $this->jsMarker,
244
            "\n" . '<script async type="text/javascript" src="' . $path . '"></script>' . "\n" . $this->jsMarker,
245
            $view
246
        );
247
    }
248
249
    /**
250
     * Callback for CSS url(...) rewriting.
251
     *
252
     * @param array $matches Regular expression matches collection
253
     *
254
     * @return string Rewritten url(..) with static resource handler url
255
     * @throws ResourceNotFound
256
     */
257
    public function replaceUrlCallback($matches)
258
    {
259
        // If we have found static resource path definition and its not inline
260
        if (array_key_exists(2, $matches) && strpos($matches[2], 'data:') === false) {
261
            // Store static resource path
262
            $url = $matches[2];
263
264
            // Ignore preprocessor vars
265
            // TODO: This is totally wrong need to come up with decision
266
            if (strpos($url, '@') !== false) {
267
                return $matches[0];
268
            }
269
270
            // Remove possible GET parameters from resource path
271 View Code Duplication
            if (($getStart = strpos($url, '?')) !== false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
272
                $url = substr($url, 0, $getStart);
273
            }
274
275
            // Remove possible HASH parameters from resource path
276 View Code Duplication
            if (($getStart = strpos($url, '#')) !== false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
277
                $url = substr($url, 0, $getStart);
278
            }
279
280
            // Try to find resource and output full error
281
            try {
282
                $path = Resource::getProjectRelativePath($url, dirname($this->currentResource));
283
            } catch (ResourceNotFound $e) {
284
                throw new ResourceNotFound('Cannot find resource "'.$url.'" in "'.$this->currentResource.'"');
285
            }
286
287
            // Build path to static resource handler
288
            return 'url("/' . $this->id . '/?p=' . $path . '")';
289
        }
290
291
        return $matches[0];
292
    }
293
}
294