Completed
Push — master ( 572c91...f32499 )
by Vitaly
06:12
created

Router::replaceUrlCallback()   C

Complexity

Conditions 7
Paths 10

Size

Total Lines 36
Code Lines 15

Duplication

Lines 6
Ratio 16.67 %

Importance

Changes 2
Bugs 0 Features 2
Metric Value
cc 7
eloc 15
c 2
b 0
f 2
nc 10
nop 1
dl 6
loc 36
rs 6.7272
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
        Event::fire(self::EVENT_START_GENERATE_RESOURCES, array(&$moduleList));
71
72
        $this->generateResources($moduleList);
73
74
        // Subscribe to core rendered event
75
        $this->system->subscribe('core.rendered', array($this, 'renderer'));
76
    }
77
78
    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...
79
    {
80
        // we need to read all resources at specified path
81
        // we need to gather all CSS resources into one file
82
        // we need to gather all LESS resources into one file
83
        // we need to gather all SASS resources into one file
84
        // we need to gather all COFFEE resources into one file
85
        // we need to gather all JS resources into one file
86
        // we need to be able to include all files separately into template in development
87
        // we need handlers/events for each resource type gathered with less and our approach
88
        // we have problems that our variables are splitted around modules to make this work
89
        // we need to gather all files and then parse on come up with different solution
90
91
    }
92
93
    public function generateResources($moduleList, $templatePath = 'default')
94
    {
95
        $dir = str_replace(array('/', '.'), '_', $templatePath);
96
97
        // Cache main web resources
98
        foreach (array(array('js'), array('css', 'less'), array('coffee')) as $rts) {
99
            // Get first resource type as extension
100
            $rt = $rts[0];
101
102
            $hash_name = '';
103
104
            // Iterate gathered namespaces for their resources
105
            /** @var Module $module */
106
            foreach ($moduleList as $id => $module) {
107
                // If necessary resources has been collected
108
                foreach ($rts as $_rt) {
109
                    if (isset($module->resourceMap->$_rt)) {
110
                        foreach ($module->resourceMap->$_rt as $resource) {
111
                            // Created string with last resource modification time
112
                            $hash_name .= filemtime($resource);
113
                        }
114
                    }
115
                }
116
            }
117
118
            // Get hash that's describes resource status
119
            $hash_name = md5($hash_name) . '.' . $rt;
120
121
            $file = $hash_name;
122
123
            // If cached file does not exists
124
            if ($this->cache_refresh($file, true, $dir)) {
125
                // Read content of resource files
126
                $content = '';
127
                foreach ($moduleList as $id => $module) {
128
                    $this->currentModule = $module;
129
                    // If this ns has resources of specified type
130
                    foreach ($rts as $_rt) {
131
                        if (isset($module->resourceMap->$_rt)) {
132
                            foreach ($module->resourceMap->$_rt as $resource) {
133
                                // Store current processing resource
134
                                $this->currentResource = $resource;
135
                                // Read resource file
136
                                $c = file_get_contents($resource);
137
                                // Rewrite url in css
138
                                if ($rt === 'css') {
139
                                    $c = preg_replace_callback('/url\s*\(\s*(\'|\")?([^\)\s\'\"]+)(\'|\")?\s*\)/i',
140
                                        array($this, 'replaceUrlCallback'), $c);
141
                                }
142
                                // Gather processed resource text together
143
                                $content .= "\n\r" . $c;
144
                            }
145
                        }
146
                    }
147
                }
148
149
                // Fire event that new resource has been generated
150
                Event::fire(self::EVENT_CREATED, array($rt, &$content, &$file, &$this));
151
152
                // Fix updated resource file with new path to it
153
                $this->updated[$rt] = $file;
154
155
                // Create cache file
156
                file_put_contents($file, $content);
157
            }
158
159
            // Save path to resource cache
160
            $this->cached[$rt][$templatePath] = __SAMSON_CACHE_PATH . $this->id . '/' . $dir . '/' . $hash_name;
161
        }
162
    }
163
164
    /**
165
     * Core render handler for including CSS and JS resources to html
166
     *
167
     * @param string $view   View content
168
     * @param array  $data   View data
169
     * @param null   $module Module instance
170
     *
171
     * @return string Processed view content
172
     */
173
    public function renderer(&$view, $data = array(), $module = null)
174
    {
175
        $templateId = isset($this->cached['css'][$this->system->template()])
176
            ? $this->system->template()
177
            : 'default';
178
179
        // Define resource urls
180
        $css = Resource::getWebRelativePath($this->cached['css'][$templateId]);
181
        $js = Resource::getWebRelativePath($this->cached['js'][$templateId]);
182
183
        // TODO: Прорисовка зависит от текущего модуля, сделать єто через параметр прорисовщика
184
        // If called from compressor
185
        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...
186
            $templateId = isset($this->cached['css'][$data['file']]) ? $data['file'] : 'default';
187
            $css = url()->base() . basename($this->cached['css'][$templateId]);
188
            $js = url()->base() . basename($this->cached['js'][$templateId]);
189
        }
190
191
        // Inject resource links
192
        return $view = $this->injectCSS($this->injectJS($view, $js), $css);
193
    }
194
195
    /**
196
     * Inject CSS link into view.
197
     *
198
     * @param string $view View code
199
     * @param string $path Resource path
200
     *
201
     * @return string Modified view
202
     */
203
    protected function injectCSS($view, $path)
204
    {
205
        // Put css link at the end of <head> page block
206
        return str_ireplace(
207
            $this->cssMarker,
208
            "\n" . '<link type="text/css" rel="stylesheet" property="stylesheet" href="' . $path . '">' . "\n" . $this->cssMarker,
209
            $view
210
        );
211
    }
212
213
    /**
214
     * Inject JS 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 injectJS($view, $path)
222
    {
223
        // Put javascript link in the end of the document
224
        return str_ireplace(
225
            $this->jsMarker,
226
            "\n" . '<script async type="text/javascript" src="' . $path . '"></script>' . "\n" . $this->jsMarker,
227
            $view
228
        );
229
    }
230
231
    /**
232
     * Callback for CSS url(...) rewriting.
233
     *
234
     * @param array $matches Regular expression matches collection
235
     *
236
     * @return string Rewritten url(..) with static resource handler url
237
     * @throws ResourceNotFound
238
     */
239
    public function replaceUrlCallback($matches)
240
    {
241
        // If we have found static resource path definition and its not inline
242
        if (array_key_exists(2, $matches) && strpos($matches[2], 'data:') === false) {
243
            // Store static resource path
244
            $url = $matches[2];
245
246
            // Ignore preprocessor vars
247
            // TODO: This is totally wrong need to come up with decision
248
            if (strpos($url, '@') !== false) {
249
                return $matches[0];
250
            }
251
252
            // Remove possible GET parameters from resource path
253 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...
254
                $url = substr($url, 0, $getStart);
255
            }
256
257
            // Remove possible HASH parameters from resource path
258 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...
259
                $url = substr($url, 0, $getStart);
260
            }
261
262
            // Try to find resource and output full error
263
            try {
264
                $path = Resource::getProjectRelativePath($url, dirname($this->currentResource));
265
            } catch (ResourceNotFound $e) {
266
                throw new ResourceNotFound('Cannot find resource "'.$url.'" in "'.$this->currentResource.'"');
267
            }
268
269
            // Build path to static resource handler
270
            return 'url("/' . $this->id . '/?p=' . $path . '")';
271
        }
272
273
        return $matches[0];
274
    }
275
}
276