Completed
Push — master ( 103b88...00e38a )
by Iurii
01:13
created

Extract::isSupportedFile()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
rs 10
cc 2
eloc 2
nc 2
nop 1
1
<?php
2
3
/**
4
 * @package Extractor
5
 * @author Iurii Makukh <[email protected]>
6
 * @copyright Copyright (c) 2015, Iurii Makukh
7
 * @license https://www.gnu.org/licenses/gpl.html GNU/GPLv3
8
 */
9
10
namespace gplcart\modules\extractor\models;
11
12
use gplcart\core\Model;
13
14
/**
15
 * Methods to extract translatable strings from various source files
16
 */
17
class Extract extends Model
18
{
19
20
    /**
21
     * Max parsing width (in columns)
22
     */
23
    const MAX_COLS = 500;
24
25
    /**
26
     * Max parsing depth (in rows)
27
     */
28
    const MAX_LINES = 5000;
29
30
    /**
31
     * An array of REGEXP patterns keyed by file extension
32
     * @var array
33
     */
34
    protected $patterns = array(
35
        'twig' => '/text\s*\(\s*([\'"])(.+?)\1\s*([\),])/s', // {{ text('Text to translate') }}
0 ignored issues
show
Unused Code Comprehensibility introduced by
64% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
36
        'js' => '/GplCart.text\s*\(\s*([\'"])(.+?)\1\s*([\),])/s', // GplCart.text('Text to translate');
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
37
        'php' => array(
38
            '/->text\s*\(\s*([\'"])(.+?)\1\s*([\),])/s', // $this->language->text('Text to translate');
0 ignored issues
show
Unused Code Comprehensibility introduced by
70% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
39
            '/\/\*(?:\*|\s)+@text(?:\*|\s)+\*\/\s*([\'"])(.+?)\1\s*/', // /* @text */ 'Text to translate'
40
        )
41
    );
42
43
    /**
44
     * Constructor
45
     */
46
    public function __construct()
47
    {
48
        parent::__construct();
49
    }
50
51
    /**
52
     * Returns a pattern by a file extension or an array of pattern keyed by extension
53
     * @param null|string $extension
54
     * @return array|string
55
     */
56
    public function getPattern($extension = null)
57
    {
58
        $pattern = &gplcart_static(__METHOD__ . "$extension");
59
60
        if (isset($pattern)) {
61
            return $pattern;
62
        }
63
64
        if (isset($extension)) {
65
            $pattern = empty($this->patterns[$extension]) ? '' : $this->patterns[$extension];
66
        } else {
67
            $pattern = $this->patterns;
68
        }
69
70
        $this->hook->attach('module.extractor.pattern', $pattern, $extension, $this);
71
        return $pattern;
72
    }
73
74
    /**
75
     * Returns an array of extracted strings from a file
76
     * @param string $file
77
     * @return array
78
     */
79
    public function extractFromFile($file)
80
    {
81
        $pattern = $this->getPattern(pathinfo($file, PATHINFO_EXTENSION));
82
83
        if (empty($pattern)) {
84
            return array();
85
        }
86
87
        $handle = fopen($file, 'r');
88
89
        if (!is_resource($handle)) {
90
            return array();
91
        }
92
93
        $lines = self::MAX_LINES;
94
95
        $extracted = array();
96
        while ($lines && $line = fgets($handle, self::MAX_COLS)) {
97
            $extracted = array_merge($extracted, $this->extractFromString($line, $pattern));
98
            $lines--;
99
        }
100
101
        fclose($handle);
102
        return $extracted;
103
    }
104
105
    /**
106
     * Returns an array of extracted strings from a source string
107
     * @param string $string
108
     * @param string|array $patterns
109
     * @return array
110
     */
111
    public function extractFromString($string, $patterns)
112
    {
113
        $result = null;
114
        $this->hook->attach('module.extractor.extract', $string, $patterns, $result, $this);
115
116
        if (isset($result)) {
117
            return $result;
118
        }
119
120
        foreach ((array) $patterns as $pattern) {
121
            $matches = array();
122
            preg_match_all($pattern, $string, $matches);
123
            if (!empty($matches[2])) {
124
                return $this->clean($matches[2]);
125
            }
126
        }
127
128
        return array();
129
    }
130
131
    /**
132
     * Clean up an array of extracted strings or a single string
133
     * @param array|string $items
134
     * @return array
135
     */
136
    protected function clean($items)
137
    {
138
        $cleaned = array();
139
        foreach ((array) $items as $item) {
140
            // Remove double/single quotes and whitespaces from the beginning and end of a string
141
            $str = trim(preg_replace('/^(\'(.*)\'|"(.*)")$/', '$2$3', $item));
142
            if ($str !== '') {
143
                $cleaned[] = $str;
144
            }
145
        }
146
147
        return $cleaned;
148
    }
149
150
    /**
151
     * Returns an array of scanned files to extract from or counts them
152
     * @param array $options
153
     * @return integer|array
154
     */
155
    public function scan(array $options)
156
    {
157
        $result = null;
158
        $this->hook->attach('module.extractor.scan', $options, $result, $this);
159
160
        if (isset($result)) {
161
            return $result;
162
        }
163
164
        $scanned = array();
165
        foreach ((array) $options['directory'] as $directory) {
166
            $scanned = array_merge($scanned, $this->scanRecursive($directory));
167
        }
168
169
        $files = array_filter($scanned, function($file) {
170
            return $this->isSupportedFile($file);
171
        });
172
173
        if (!empty($options['count'])) {
174
            return count($files);
175
        }
176
177
        sort($files);
178
        return $files;
179
    }
180
181
    /**
182
     * Whether the file can be parsed
183
     * @param string $file
184
     * @return bool
185
     */
186
    public function isSupportedFile($file)
187
    {
188
        return is_file($file) && in_array(pathinfo($file, PATHINFO_EXTENSION), array_keys($this->getPattern()));
189
    }
190
191
    /**
192
     * Returns an array of directories to be scanned
193
     * @return array
194
     */
195
    public function getScannedDirectories()
196
    {
197
        $directories = array(
198
            GC_CORE_DIR,
199
            GC_CONFIG_DEFAULT_DIR,
200
            $this->config->getModuleDirectory('frontend'),
201
            $this->config->getModuleDirectory('backend')
202
        );
203
204
        $this->hook->attach('module.extractor.directories', $directories, $this);
205
        return $directories;
206
    }
207
208
    /**
209
     * Recursive scans files in a directory
210
     * @param string $directory
211
     * @param array $results
212
     * @return array
213
     */
214
    protected function scanRecursive($directory, &$results = array())
215
    {
216
        if (strpos($directory, 'override') !== false) {
217
            return $results; // Exclude "override" directories
218
        }
219
220
        foreach (scandir($directory) as $file) {
221
            $path = "$directory/$file";
222
            if (!is_dir($path)) {
223
                $results[] = $path;
224
            } else if ($file != "." && $file != "..") {
225
                $this->scanRecursive($path, $results);
226
                $results[] = $path;
227
            }
228
        }
229
230
        return $results;
231
    }
232
233
}
234