-
-
Notifications
You must be signed in to change notification settings - Fork 107
/
Util.php
249 lines (217 loc) · 7.3 KB
/
Util.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
<?php
namespace Osiset\ShopifyApp;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Str;
use LogicException;
use Osiset\ShopifyApp\Objects\Enums\FrontendEngine;
use Osiset\ShopifyApp\Objects\Values\Hmac;
/**
* Utilities and helpers used in various parts of the package.
*/
class Util
{
/**
* HMAC creation helper.
*
* @param array $opts The options for building the HMAC.
* @param string $secret The app secret key.
*
* @return Hmac
*/
public static function createHmac(array $opts, string $secret): Hmac
{
// Setup defaults
$data = $opts['data'];
$raw = $opts['raw'] ?? false;
$buildQuery = $opts['buildQuery'] ?? false;
$buildQueryWithJoin = $opts['buildQueryWithJoin'] ?? false;
$encode = $opts['encode'] ?? false;
if ($buildQuery) {
//Query params must be sorted and compiled
ksort($data);
$queryCompiled = [];
foreach ($data as $key => $value) {
$queryCompiled[] = "{$key}=".(is_array($value) ? implode(',', $value) : $value);
}
$data = implode(
$buildQueryWithJoin ? '&' : '',
$queryCompiled
);
}
// Create the hmac all based on the secret
$hmac = hash_hmac('sha256', $data, $secret, $raw);
// Return based on options
$result = $encode ? base64_encode($hmac) : $hmac;
return Hmac::fromNative($result);
}
/**
* Parse query strings the same way as Rack::Until in Ruby. (This is a port from Rack 2.3.0.).
*
* From Shopify docs, they use Rack::Util.parse_query, which does *not* parse array parameters properly.
* Array parameters such as `name[]=value1&name[]=value2` becomes `['name[]' => ['value1', 'value2']] in Shopify.
* See: https://github.com/rack/rack/blob/f9ad97fd69a6b3616d0a99e6bedcfb9de2f81f6c/lib/rack/query_parser.rb#L36
*
* @param string $queryString The query string.
* @param string|null $delimiter The delimiter.
*
* @return mixed
*/
public static function parseQueryString(string $queryString, string $delimiter = null): array
{
$commonSeparator = [';' => '/[;]\s*/', ';,' => '/[;,]\s*/', '&' => '/[&]\s*/'];
$defaultSeparator = '/[&;]\s*/';
$params = [];
$split = preg_split(
$delimiter ? $commonSeparator[$delimiter] || '/['.$delimiter.']\s*/' : $defaultSeparator,
$queryString ?? ''
);
foreach ($split as $part) {
if (! $part) {
continue;
}
[$key, $value] = strpos($part, '=') !== false ? explode('=', $part, 2) : [$part, null];
$key = urldecode($key);
$value = $value !== null ? urldecode($value) : $value;
if (isset($params[$key])) {
$cur = $params[$key];
if (is_array($cur)) {
$params[$key][] = $value;
} else {
$params[$key] = [$cur, $value];
}
} else {
$params[$key] = $value;
}
}
return $params;
}
/**
* URL-safe Base64 encoding.
*
* Replaces `+` with `-` and `/` with `_` and trims padding `=`.
*
* @param string $data The data to be encoded.
*
* @return string
*/
public static function base64UrlEncode($data)
{
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
/**
* URL-safe Base64 decoding.
*
* Replaces `-` with `+` and `_` with `/`.
*
* Adds padding `=` if needed.
*
* @param string $data The data to be decoded.
*
* @return string
*/
public static function base64UrlDecode($data)
{
return base64_decode(str_pad(strtr($data, '-_', '+/'), strlen($data) % 4, '=', STR_PAD_RIGHT));
}
/**
* Checks if the route should be registered or not.
*
* @param string $routeToCheck The route name to check.
* @param bool|array $routesToExclude The routes which are to be excluded.
*
* @return bool
*/
public static function registerPackageRoute(string $routeToCheck, $routesToExclude): bool
{
if ($routesToExclude === false) {
return true;
}
if (is_array($routesToExclude) === false) {
throw new LogicException('Excluded routes must be an array');
}
return in_array($routeToCheck, $routesToExclude, true) === false;
}
/**
* Get the config value for a key.
* Used as a helper function so it is accessible in Blade.
* The second param of `shop` is important for `config_api_callback`.
*
* @param string $key The key to lookup.
* @param mixed $shop The shop domain (string, ShopDomain, etc).
*
* @return mixed
*/
public static function getShopifyConfig(string $key, $shop = null)
{
$config = Config::get('shopify-app', []);
$config['user_model'] = Config::get("auth.providers.{$config['shop_auth_provider']}.model", Config::get('auth.providers.users.model'));
if (Str::is('route_names.*', $key)) {
// scope the Arr::get() call to the "route_names" array
// to allow for dot-notation keys like "authenticate.oauth"
// this is necessary because Arr::get() only finds dot-notation keys
// if they are at the top level of the given array
return Arr::get(
$config['route_names'],
Str::after($key, '.')
);
}
// Check if config API callback is defined
if (Str::startsWith($key, 'api')
&& Arr::exists($config, 'config_api_callback')
&& is_callable($config['config_api_callback'])) {
// It is, use this to get the config value
return call_user_func(
Arr::get($config, 'config_api_callback'),
$key,
$shop
);
}
return Arr::get($config, $key);
}
/**
* Convert a REST-format webhook topic ("resource/event")
* to a GraphQL-format webhook topic ("RESOURCE_EVENT").
*
* @param string $topic
*
* @return string
*/
public static function getGraphQLWebhookTopic(string $topic): string
{
return Str::of($topic)
->upper()
->replaceMatches('/[^A-Z_]/', '_');
}
/**
* Get the table name for shop
*
* @return string
*/
public static function getShopsTable(): string
{
return self::getShopifyConfig('table_names.shops') ?? 'users';
}
/**
* Get the table foreign key for shop
*
* @return string
*/
public static function getShopsTableForeignKey(): string
{
return Str::singular(self::getShopsTable()).'_id';
}
/**
* Checking to see if you need to use the native App Bridge
*
* @return bool
*/
public static function useNativeAppBridge(): bool
{
$frontendEngine = FrontendEngine::fromNative(
self::getShopifyConfig('frontend_engine') ?? 'BLADE'
);
$reactEngine = FrontendEngine::fromNative('REACT');
return !$frontendEngine->isSame($reactEngine);
}
}