Completed
Push — master ( ec3811...4e7a65 )
by Nikita
03:00
created

CSS   A

Complexity

Total Complexity 16

Size/Duplication

Total Lines 131
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 100%

Importance

Changes 7
Bugs 0 Features 3
Metric Value
wmc 16
c 7
b 0
f 3
lcom 1
cbo 4
dl 0
loc 131
ccs 27
cts 27
cp 1
rs 10

5 Methods

Rating   Name   Duplication   Size   Complexity  
A prepare() 0 9 1
A compile() 0 15 2
C deCompile() 0 24 8
B rewriteUrls() 0 28 3
A getOnlyUrl() 0 9 2
1
<?php
2
/**
3
 * Created by Vitaly Iegorov <[email protected]>.
4
 * on 07.07.16 at 18:19
5
 */
6
namespace samsonphp\css;
7
8
use samson\core\ExternalModule;
9
use samsonphp\event\Event;
10
use samsonphp\resource\exception\ResourceNotFound;
11
use samsonphp\resource\ResourceValidator;
12
use samsonphp\resource\Router;
13
use samsonphp\resource\ResourceManager;
14
15
/**
16
 * CSS assets handling class
17
 *
18
 * @author Vitaly Iegorov <[email protected]>
19
 * @package samsonphp\resource
20
 * TODO: Remove ResourceValidator as it is unnecessary
21
 */
22
class CSS extends ExternalModule
23
{
24
    /** @var string Module identifer */
25
    protected $id = 'resource_css';
26
27
    /** Pattern for matching CSS url */
28
    const P_URL = '/url\s*\(\s*(\'|\")?([^\)\s\'\"]+)(\'|\")?\s*\)/i';
29
30
    /** Event for firing before handling CSS resource */
31
    const E_BEFORE_HANDLER = 'samsonphp.css.before_handle';
32
33
    /** Event for firing after handling CSS resource */
34
    const E_AFTER_HANDLER = 'samsonphp.css.after_handle';
35
36
    /** @var string Path to current resource file */
37
    protected $currentResource;
38
39
    /** Module preparation stage handler */
40 7
    public function prepare(array $params = [])
41
    {
42
        // Subscribe for CSS handling
43 7
        Event::subscribe(Router::E_RESOURCE_COMPILE, [$this, 'compile']);
44
        
45 7
        Event::subscribe(Compressor::E_RESOURCE_COMPRESS, [$this, 'deCompile']);
46
47
        return parent::prepare($params);
0 ignored issues
show
Unused Code introduced by
The call to ExternalModule::prepare() has too many arguments starting with $params.

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...
48
    }
49
50
    /**
51
     * LESS resource compiler.
52
     *
53
     * @param string $resource  Resource full path
54
     * @param string $extension Resource extension
55 7
     * @param string $content   Compiled output resource content
56
     */
57 7
    public function compile($resource, $extension, &$content)
58 7
    {
59
        if (in_array($extension, [ResourceManager::T_CSS, ResourceManager::T_LESS, ResourceManager::T_SASS, ResourceManager::T_SCSS])) {
60
            $this->currentResource = $resource;
61 7
62
            // Fire event
63
            Event::fire(self::E_BEFORE_HANDLER, [&$content, $resource]);
64 7
65
            // Rewrite Urls
66
            $content = preg_replace_callback(self::P_URL, [$this, 'rewriteUrls'], $content);
67 6
68 6
            // Fire event
69 6
            Event::fire(self::E_AFTER_HANDLER, [&$content, $resource]);
70
        }
71
    }
72
    
73
    public function deCompile($extension, &$content)
74
    {
75
        if (in_array($extension, [ResourceManager::T_CSS, ResourceManager::T_LESS, ResourceManager::T_SASS, ResourceManager::T_SCSS])) {
76
            if (preg_match_all('/url\s*\(\s*(\'|\")*(?<url>[^\'\"\)]+)\s*(\'|\")*\)/i', $content, $matches)) {
77
                if (isset($matches['url'])) {
78
                    foreach ($matches['url'] as $url) {
0 ignored issues
show
Bug introduced by
The expression $matches['url'] of type string|array<integer,string> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
79 7
                        if (preg_match('/' . STATIC_RESOURCE_HANDLER . '\/\?p=(((\/src\/|\/vendor\/samson[^\/]+\/)(?<module>[^\/]+)(?<path>.+))|((?<local>.+)))/ui', $url, $matches)) {
80
                            if (array_key_exists('local', $matches)) {
81
                                $module = 'local';
82 7
                                $path = $matches['local'];
83
                            } else {
84
                                $module = $matches['module'];
85 7
                                $path = $matches['path'];
86 7
                            }
87 7
                            // Always remove first public path /www/
88
                            $path = ltrim(str_replace(__SAMSON_PUBLIC_PATH, '', $path), '/');
89
                            // Replace url in file
90 7
                            $content = str_replace($url, url()->base() . ($module == 'local' ? '' : $module . '/www/') . $path, $content);
91
                        }
92 4
                    }
93
                }
94
            }
95
        }
96 4
    }
97 4
98 1
    /**
99
     * Callback for CSS url(...) rewriting.
100
     *
101
     * @param array $matches Regular expression matches collection
102 3
     *
103
     * @return string Rewritten url(..) with static resource handler url
104
     * @throws ResourceNotFound
105 3
     */
106
    public function rewriteUrls($matches)
107
    {
108
        // Store static resource path
109
        $url = $matches[2];
110
111
        // Validate url for restricted protocols and inline images
112
        $validation  = array_filter(['data/', 'data:', 'http:', 'https:'], function ($item) use ($url) {
113
            return strpos($url, $item) !== false;
114
        });
115
116 4
        // Ignore inline resources
117
        if (!count($validation)) {
118
            // Remove possible GET, HASH parameters from resource path
119 4
            $url = $this->getOnlyUrl($this->getOnlyUrl($url, '#'), '?');
120 3
121
            // Try to find resource and output full error
122
            try {
123 4
                $path = ResourceValidator::getProjectRelativePath($url, dirname($this->currentResource));
124
            } catch (ResourceNotFound $e) {
125
                throw new ResourceNotFound('Cannot find resource "' . $url . '" in "' . $this->currentResource . '"');
126
            }
127
128
            // Build path to static resource handler
129
            return 'url("/' . STATIC_RESOURCE_HANDLER . '/?p=' . $path . '")';
130
        }
131
132
        return $matches[0];
133
    }
134
135
    /**
136
     * Get only path or URL before marker.
137
     *
138
     * @param string $path   Full URL with possible unneeded data
139
     * @param string $marker Marker for separation
140
     *
141
     * @return string Filtered asset URL
142
     */
143
    protected function getOnlyUrl($path, $marker)
144
    {
145
        // Remove possible GET parameters from resource path
146
        if (($getStart = strpos($path, $marker)) !== false) {
147
            return substr($path, 0, $getStart);
148
        }
149
150
        return $path;
151
    }
152
}
153