Completed
Push — master ( 505aa7...0e8bf0 )
by Vitaly
02:15
created

Router::injectCSS()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 5
c 1
b 0
f 0
nc 1
nop 2
dl 0
loc 9
rs 9.6666
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
    /** @see ModuleConnector::init() */
39
    public function init(array $params = array())
40
    {
41
        parent::init($params);
42
43
        $moduleList = $this->system->module_stack;
44
45
        Event::fire(self::EVENT_START_GENERATE_RESOURCES, array(&$moduleList));
46
47
        $this->generateResources($moduleList);
48
49
        // Subscribe to core rendered event
50
        $this->system->subscribe('core.rendered', array($this, 'renderer'));
51
    }
52
53
    public function generateResources($moduleList, $templatePath = 'default')
54
    {
55
        // Cache main web resources
56
        foreach (array(array('js'), array('css', 'less'), array('coffee')) as $rts) {
57
            // Get first resource type as extension
58
            $rt = $rts[0];
59
60
            $hash_name = '';
61
62
            // Iterate gathered namespaces for their resources
63
            /** @var Module $module */
64
            foreach ($moduleList as $id => $module) {
65
                // If necessary resources has been collected
66
                foreach ($rts as $_rt) {
67
                    if (isset($module->resourceMap->$_rt)) {
68
                        foreach ($module->resourceMap->$_rt as $resource) {
69
                            // Created string with last resource modification time
70
                            $hash_name .= filemtime($resource);
71
                        }
72
                    }
73
                }
74
            }
75
76
            // Get hash that's describes resource status
77
            $hash_name = md5($hash_name) . '.' . $rt;
78
79
            $file = $hash_name;
80
81
            $dir = str_replace(array('/', '.'), '_', $templatePath);
82
83
            // If cached file does not exists
84
            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...
85
                // Read content of resource files
86
                $content = '';
87
                foreach ($moduleList as $id => $module) {
88
                    $this->currentModule = $module;
89
                    // If this ns has resources of specified type
90
                    foreach ($rts as $_rt) {
91
                        if (isset($module->resourceMap->$_rt)) {
92
                            //TODO: If you will remove & from iterator - system will fail at last element
93
                            foreach ($module->resourceMap->$_rt as $resource) {
94
                                // Store current processing resource
95
                                $this->currentResource = $resource;
96
                                // Read resource file
97
                                $c = file_get_contents($resource);
98
                                // Rewrite url in css
99
                                if ($rt === 'css') {
100
                                    $c = preg_replace_callback('/url\s*\(\s*(\'|\")?([^\)\s\'\"]+)(\'|\")?\s*\)/i',
101
                                        array($this, 'src_replace_callback'), $c);
102
                                }
103
                                // Gather processed resource text together
104
                                $content .= "\n\r" . $c;
105
                            }
106
                        }
107
                    }
108
                }
109
110
                // Fire event that new resource has been generated
111
                Event::fire(self::EVENT_CREATED, array($rt, &$content, &$file, &$this));
112
113
                // Fix updated resource file with new path to it
114
                $this->updated[$rt] = $file;
115
116
                // Запишем содержание нового "собранного" ресурса
117
                file_put_contents($file, $content);
118
            }
119
120
            // Save path to resource cache
121
            $this->cached[$rt][$templatePath] = __SAMSON_CACHE_PATH . $this->id . '/' . $dir . '/' . $hash_name;
122
        }
123
    }
124
125
    /**
126
     * Core render handler for including CSS and JS resources to html
127
     *
128
     * @param string $view   View content
129
     * @param array  $data   View data
130
     * @param null   $module Module instance
131
     *
132
     * @return string Processed view content
133
     */
134
    public function renderer(&$view, $data = array(), $module = null)
135
    {
136
        $templateId = isset($this->cached['css'][$this->system->template()])
137
            ? $this->system->template()
138
            : 'default';
139
140
        // Define resource urls
141
        $css = Resource::getWebRelativePath($this->cached['css'][$templateId]);
142
        $js = Resource::getWebRelativePath($this->cached['js'][$templateId]);
143
144
        // TODO: Прорисовка зависит от текущего модуля, сделать єто через параметр прорисовщика
145
        // If called from compressor
146
        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...
147
            $templateId = isset($this->cached['css'][$data['file']]) ? $data['file'] : 'default';
148
            $css = url()->base() . basename($this->cached['css'][$templateId]);
149
            $js = url()->base() . basename($this->cached['js'][$templateId]);
150
        }
151
152
        // Inject resource links
153
        return $this->injectCSS($this->injectJS($view, $js), $css);
154
    }
155
156
    /**
157
     * Inject CSS link into view.
158
     *
159
     * @param string $view View code
160
     * @param string $path Resource path
161
     *
162
     * @return string Modified view
163
     */
164
    protected function injectCSS($view, $path)
165
    {
166
        // Put css link at the end of <head> page block
167
        return str_ireplace(
168
            $this->cssMarker,
169
            "\n" . '<link type="text/css" rel="stylesheet" href="' . $path . '">' . "\n" . $this->cssMarker,
170
            $view
171
        );
172
    }
173
174
    /**
175
     * Inject JS link into view.
176
     *
177
     * @param string $view View code
178
     * @param string $path Resource path
179
     *
180
     * @return string Modified view
181
     */
182
    protected function injectJS($view, $path)
183
    {
184
        // Put javascript link in the end of the document
185
        return str_ireplace(
186
            $this->jsMarker,
187
            "\n" . '<script async type="text/javascript" src="' . $path . '"></script>' . "\n" . $this->jsMarker,
188
            $view
189
        );
190
    }
191
192
    /**
193
     * Callback for CSS url(...) rewriting.
194
     *
195
     * @param array $matches Regular expression matches collection
196
     *
197
     * @return string Rewritten url(..) with static resource handler url
198
     * @throws ResourceNotFound
199
     */
200
    public function src_replace_callback($matches)
0 ignored issues
show
Coding Style introduced by
Method name "Router::src_replace_callback" is not in camel caps format
Loading history...
201
    {
202
        // If we have found static resource path definition and its not inline
203
        if (array_key_exists(2, $matches) && strpos($matches[2], 'data:') === false) {
204
            // Store static resource path
205
            $url = $matches[2];
206
207
            // Ignore preprocessor vars
208
            // TODO: This is totally wrong need to come up with decision
209
            if (strpos($url, '@') !== false) {
210
                return $matches[0];
211
            }
212
213
            // Remove possible GET parameters from resource path
214 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...
215
                $url = substr($url, 0, $getStart);
216
            }
217
218
            // Remove possible HASH parameters from resource path
219 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...
220
                $url = substr($url, 0, $getStart);
221
            }
222
223
            // Build path to static resource handler
224
            return 'url("/' . $this->id . '/?p='
225
            . Resource::getProjectRelativePath($url, dirname($this->currentResource))
226
            . '")';
227
        }
228
229
        return $matches[0];
230
    }
231
}
232