Completed
Pull Request — master (#145)
by
unknown
01:14
created

GoogleTokenGenerator::length()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
3
namespace Stichoza\GoogleTranslate\Tokens;
4
5
/**
6
 * Google token generator.
7
 *
8
 * @link https://github.com/Stichoza/google-translate-php/issues/32 Thanks to @helen5106 and @tehmaestro and few other cool guys
9
 * {@inheritDoc}
10
 */
11
class GoogleTokenGenerator implements TokenProviderInterface
12
{
13
    /**
14
     * @var array Token keys
15
     */
16
    protected const TKK = ['406398', 2087938574];
17
    
18
    /**
19
     * @var string Character encoding
20
     */
21
    protected $encoding;
22
    
23
    /**
24
     * @var string[] Generated tokens
25
     */
26
    protected $tokens = [];
27
    
28
    /**
29
     * Creates new instance.
30
     * 
31
     * @param string $encoding Character encoding
32
     * @return void
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
33
     */
34
    public function __construct(string $encoding = 'UTF-8')
35
    {
36
        $this->encoding = $encoding;
37
    }
38
    
39
    /**
40
     * Generate and return a token.
41
     *
42
     * @param string $source Source language
43
     * @param string $target Target language
44
     * @param string $text Text to translate
45
     * @return string Token
46
     */
47
    public function generateToken(string $source, string $target, string $text): string
48
    {
49
        $hash = md5($text);
50
        if (isset($this->tokens[$hash])) {
51
            return $this->tokens[$hash];
52
        }
53
        
54
        $b = static::TKK[0];
55
        for ($d = [], $e = 0, $f = 0; $f < $this->length($text); $f++) {
56
            $g = $this->charCodeAt($text, $f);
57
            if (128 > $g) {
58
                $d[$e++] = $g;
59
            } else {
60
                if (2048 > $g) {
61
                    $d[$e++] = $g >> 6 | 192;
62
                } else {
63
                    if (55296 === ($g & 64512) && $f + 1 < $this->length($text) && 56320 === ($this->charCodeAt($text, $f + 1) & 64512)) {
64
                        $g = 65536 + (($g & 1023) << 10) + ($this->charCodeAt($text, ++$f) & 1023);
65
                        $d[$e++] = $g >> 18 | 240;
66
                        $d[$e++] = $g >> 12 & 63 | 128;
67
                    } else {
68
                        $d[$e++] = $g >> 12 | 224;
69
                    }
70
                    $d[$e++] = $g >> 6 & 63 | 128;
71
                }
72
                $d[$e++] = $g & 63 | 128;
73
            }
74
        }
75
        $text = $b;
76
        for ($e = 0; $e < count($d); $e++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
77
            $text = $this->rl($text + $d[$e], '+-a^+6');
78
        }
79
        $text = $this->rl($text, '+-3^+b+-f');
80
        $text ^= static::TKK[1];
81
        if (0 > $text) {
82
            $text = ($text & 2147483647) + 2147483648;
83
        }
84
        $text = fmod($text, pow(10, 6));
85
        
86
        $this->tokens[$hash] = $text . '.' . ($text ^ $b);
87
        
88
        return $this->tokens[$hash];
89
    }
90
91
    /**
92
     * Process token data by applying multiple operations.
93
     * (Parameters are safe, no need for multibyte functions)
94
     *
95
     * @param int $a
96
     * @param string $b
97
     * @return int
98
     */
99
    private function rl(int $a, string $b): int
100
    {
101
        for ($c = 0; $c < strlen($b) - 2; $c += 3) {
102
            $d = $b[$c + 2];
103
            $d = 'a' <= $d ? ord($d[0]) - 87 : (int) $d;
104
            $d = '+' === $b[$c + 1] ? $this->unsignedRightShift($a, $d) : $a << $d;
105
            $a = '+' === $b[$c] ? ($a + $d & 4294967295) : $a ^ $d;
106
        }
107
        return $a;
108
    }
109
110
    /**
111
     * JS unsigned right shift(`>>>`) implementation.
112
     * 
113
     * @link https://msdn.microsoft.com/en-us/library/342xfs5s(v=vs.94).aspx
114
     * @link http://stackoverflow.com/a/43359819/2953830
115
     * @param int $a
116
     * @param int $b
117
     * @return int
118
     */
119
    private function unsignedRightShift($a, $b): int
120
    {
121
        if ($b >= 32 || $b < -32) {
122
            $b -= intval($b / 32) * 32;
123
        }
124
        if ($b < 0) {
125
            $b += 32;
126
        }
127
        
128
        if ($b === 0) {
129
            return (($a >> 1) & 0x7fffffff) * 2 + (($a >> $b) & 1);
130
        }
131
132
        if ($a < 0) {
133
            $a = $a >> 1;
134
            $a &= 2147483647;
135
            $a |= 0x40000000;
136
            $a = ($a >> ($b - 1));
137
        } else { 
138
            $a = $a >> $b;
139
        }
140
141
        return $a;
142
    }
143
144
    /**
145
     * Get JS `charCodeAt()` equivalent result.
146
     *
147
     * @param string $str
148
     * @param int    $index
149
     * @return int
150
     */
151
    private function charCodeAt(string $str, int $index): int
152
    {
153
        return mb_ord(mb_substr($str, $index, 1, $this->encoding), $this->encoding);
154
    }
155
156
    /**
157
     * Get JS equivalent string `length`.
158
     *
159
     * @param string $str
160
     * @return int
161
     */
162
    private function length(string $str): int
163
    {
164
        return mb_strlen($str, $this->encoding);
165
    }
166
}
167