Completed
Pull Request — master (#1)
by Olexandr
02:45
created

Router::parseURL()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 2
Metric Value
cc 3
eloc 11
c 3
b 0
f 2
nc 3
nop 3
dl 0
loc 16
rs 9.4285
1
<?php
2
namespace samsonphp\resource;
3
4
use samson\core\ExternalModule;
5
use samsonphp\event\Event;
6
use samsonphp\resource\exception\ResourceNotFound;
7
8
/**
9
 * Resource router for serving static resource from unreachable web-root paths.
10
 *
11
 * @author Vitaly Iegorov <[email protected]>
12
 * @author Nikita Kotenko <[email protected]>
13
 */
14
class Router extends ExternalModule
15
{
16
    /** Event showing that new gather resource file was created */
17
    const EVENT_CREATED = 'resource.created';
18
    /** Event showing that new gather resource file was created */
19
    const EVENT_START_GENERATE_RESOURCES = 'resource.start.generate.resources';
20
21
    /** @var string Marker for inserting generated JS link in template */
22
    public $jsMarker = '</body>';
23
    /** @var string Marker for inserting generated CSS link in template */
24
    public $cssMarker = '</head>';
25
    /** Cached resources path collection */
26
    public $cached = array();
27
    /** Collection of updated cached resources for notification of changes */
28
    public $updated = array();
29
30
    /** Identifier */
31
    protected $id = STATIC_RESOURCE_HANDLER;
32
33
    /** Pointer to processing module */
34
    private $currentModule;
35
    /** @var string Current processed resource */
36
    private $currentResource;
37
38
    /**
39
     * Parse URL to get module name and relative path to resource
40
     *
41
     * @param string $url String for parsing
42
     *
43
     * @return array Array [0] => module name, [1]=>relative_path
44
     */
45
    public static function parseURL($url, &$module = null, &$path = null)
46
    {
47
        // If we have URL to resource router
48
        if (preg_match('/'.STATIC_RESOURCE_HANDLER.'\/\?p=((\/src\/(?<module>[^\/]+)(?<path>.+))|((?<local>.+)))/ui', $url, $matches)) {
49
            if (array_key_exists('local', $matches)) {
50
                $module = 'local';
51
                $path = $matches['local'];
52
            } else {
53
                $module = $matches['module'];
54
                $path = $matches['path'];
55
            }
56
            return true;
57
        } else {
58
            return false;
59
        }
60
    }
61
62
    /** @see ModuleConnector::init() */
63
    public function init(array $params = array())
64
    {
65
        parent::init($params);
66
67
        $moduleList = $this->system->module_stack;
68
69
        Event::fire(self::EVENT_START_GENERATE_RESOURCES, array(&$moduleList));
70
71
        $this->generateResources($moduleList);
72
73
        // Subscribe to core rendered event
74
        $this->system->subscribe('core.rendered', array($this, 'renderer'));
75
    }
76
77
    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...
78
    {
79
        // we need to read all resources at specified path
80
        // we need to gather all CSS resources into one file
81
        // we need to gather all LESS resources into one file
82
        // we need to gather all SASS resources into one file
83
        // we need to gather all COFFEE resources into one file
84
        // we need to gather all JS resources into one file
85
        // we need to be able to include all files separately into template in development
86
        // we need handlers/events for each resource type gathered with less and our approach
87
        // we have problems that our variables are splitted around modules to make this work
88
        // we need to gather all files and then parse on come up with different solution
89
90
    }
91
92
    public function generateResources($moduleList, $templatePath = 'default')
93
    {
94
        $dir = str_replace(array('/', '.'), '_', $templatePath);
95
96
        // Cache main web resources
97
        foreach (array(array('js'), array('css', 'less'), array('coffee')) as $rts) {
98
            // Get first resource type as extension
99
            $rt = $rts[0];
100
101
            $hash_name = '';
102
103
            // Iterate gathered namespaces for their resources
104
            /** @var Module $module */
105
            foreach ($moduleList as $id => $module) {
106
                // If necessary resources has been collected
107
                foreach ($rts as $_rt) {
108
                    if (isset($module->resourceMap->$_rt)) {
109
                        foreach ($module->resourceMap->$_rt as $resource) {
110
                            // Created string with last resource modification time
111
                            $hash_name .= filemtime($resource);
112
                        }
113
                    }
114
                }
115
            }
116
117
            // Get hash that's describes resource status
118
            $hash_name = md5($hash_name) . '.' . $rt;
119
120
            $file = $hash_name;
121
122
            // If cached file does not exists
123
            if ($this->cache_refresh($file, true, $dir)) {
0 ignored issues
show
Unused Code introduced by
The call to Router::cache_refresh() has too many arguments starting with $dir.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
124
                // Read content of resource files
125
                $content = '';
126
                foreach ($moduleList as $id => $module) {
127
                    $this->currentModule = $module;
128
                    // If this ns has resources of specified type
129
                    foreach ($rts as $_rt) {
130
                        if (isset($module->resourceMap->$_rt)) {
131
                            foreach ($module->resourceMap->$_rt as $resource) {
132
                                // Store current processing resource
133
                                $this->currentResource = $resource;
134
                                // Read resource file
135
                                $c = file_get_contents($resource);
136
                                // Rewrite url in css
137
                                if ($rt === 'css') {
138
                                    $c = preg_replace_callback('/url\s*\(\s*(\'|\")?([^\)\s\'\"]+)(\'|\")?\s*\)/i',
139
                                        array($this, 'replaceUrlCallback'), $c);
140
                                }
141
                                // Gather processed resource text together
142
                                $content .= "\n\r" . $c;
143
                            }
144
                        }
145
                    }
146
                }
147
148
                // Fire event that new resource has been generated
149
                Event::fire(self::EVENT_CREATED, array($rt, &$content, &$file, &$this));
150
151
                // Fix updated resource file with new path to it
152
                $this->updated[$rt] = $file;
153
154
                // Create cache file
155
                file_put_contents($file, $content);
156
            }
157
158
            // Save path to resource cache
159
            $this->cached[$rt][$templatePath] = __SAMSON_CACHE_PATH . $this->id . '/' . $dir . '/' . $hash_name;
160
        }
161
    }
162
163
    /**
164
     * Core render handler for including CSS and JS resources to html
165
     *
166
     * @param string $view   View content
167
     * @param array  $data   View data
168
     * @param null   $module Module instance
169
     *
170
     * @return string Processed view content
171
     */
172
    public function renderer(&$view, $data = array(), $module = null)
173
    {
174
        $templateId = isset($this->cached['css'][$this->system->template()])
175
            ? $this->system->template()
176
            : 'default';
177
178
        // Define resource urls
179
        $css = Resource::getWebRelativePath($this->cached['css'][$templateId]);
180
        $js = Resource::getWebRelativePath($this->cached['js'][$templateId]);
181
182
        // TODO: Прорисовка зависит от текущего модуля, сделать єто через параметр прорисовщика
183
        // If called from compressor
184
        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...
185
            $templateId = isset($this->cached['css'][$data['file']]) ? $data['file'] : 'default';
186
            $css = url()->base() . basename($this->cached['css'][$templateId]);
187
            $js = url()->base() . basename($this->cached['js'][$templateId]);
188
        }
189
190
        // Inject resource links
191
        return $view = $this->injectCSS($this->injectJS($view, $js), $css);
192
    }
193
194
    /**
195
     * Inject CSS link into view.
196
     *
197
     * @param string $view View code
198
     * @param string $path Resource path
199
     *
200
     * @return string Modified view
201
     */
202
    protected function injectCSS($view, $path)
203
    {
204
        // Put css link at the end of <head> page block
205
        return str_ireplace(
206
            $this->cssMarker,
207
            "\n" . '<link type="text/css" rel="stylesheet" href="' . $path . '">' . "\n" . $this->cssMarker,
208
            $view
209
        );
210
    }
211
212
    /**
213
     * Inject JS link into view.
214
     *
215
     * @param string $view View code
216
     * @param string $path Resource path
217
     *
218
     * @return string Modified view
219
     */
220
    protected function injectJS($view, $path)
221
    {
222
        // Put javascript link in the end of the document
223
        return str_ireplace(
224
            $this->jsMarker,
225
            "\n" . '<script async type="text/javascript" src="' . $path . '"></script>' . "\n" . $this->jsMarker,
226
            $view
227
        );
228
    }
229
230
    /**
231
     * Callback for CSS url(...) rewriting.
232
     *
233
     * @param array $matches Regular expression matches collection
234
     *
235
     * @return string Rewritten url(..) with static resource handler url
236
     * @throws ResourceNotFound
237
     */
238
    public function replaceUrlCallback($matches)
239
    {
240
        // If we have found static resource path definition and its not inline
241
        if (array_key_exists(2, $matches) && strpos($matches[2], 'data:') === false) {
242
            // Store static resource path
243
            $url = $matches[2];
244
245
            // Ignore preprocessor vars
246
            // TODO: This is totally wrong need to come up with decision
247
            if (strpos($url, '@') !== false) {
248
                return $matches[0];
249
            }
250
251
            // Remove possible GET parameters from resource path
252 View Code Duplication
            if (($getStart = stripos($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...
253
                $url = substr($url, 0, $getStart);
254
            }
255
256
            // Remove possible HASH parameters from resource path
257 View Code Duplication
            if (($getStart = stripos($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...
258
                $url = substr($url, 0, $getStart);
259
            }
260
261
            // Build path to static resource handler
262
            return 'url("/' . $this->id . '/?p='
263
            . Resource::getProjectRelativePath($url, dirname($this->currentResource))
264
            . '")';
265
        }
266
267
        return $matches[0];
268
    }
269
}
270