1 | <?php |
||
2 | /** |
||
3 | * @link https://dukt.net/social/ |
||
4 | * @copyright Copyright (c) Dukt |
||
5 | * @license https://github.com/dukt/social/blob/v2/LICENSE.md |
||
6 | */ |
||
7 | |||
8 | namespace dukt\social; |
||
9 | |||
10 | use Craft; |
||
11 | use craft\elements\User; |
||
12 | use craft\events\ModelEvent; |
||
13 | use craft\events\RegisterElementTableAttributesEvent; |
||
14 | use craft\events\RegisterUrlRulesEvent; |
||
15 | use craft\events\SetElementTableAttributeHtmlEvent; |
||
16 | use craft\helpers\UrlHelper; |
||
17 | use craft\services\Plugins; |
||
18 | use craft\web\twig\variables\CraftVariable; |
||
19 | use craft\web\UrlManager; |
||
20 | use dukt\social\base\PluginTrait; |
||
21 | use dukt\social\elements\LoginAccount; |
||
22 | use dukt\social\models\Settings; |
||
23 | use dukt\social\web\assets\login\LoginAsset; |
||
24 | use dukt\social\web\twig\variables\SocialVariable; |
||
25 | use dukt\social\web\assets\social\SocialAsset; |
||
26 | use yii\base\Event; |
||
27 | |||
28 | /** |
||
29 | * Social plugin class. |
||
30 | * |
||
31 | * @author Dukt <[email protected]> |
||
32 | * @since 1.0 |
||
33 | */ |
||
34 | class Plugin extends \craft\base\Plugin |
||
35 | { |
||
36 | // Traits |
||
37 | // ========================================================================= |
||
38 | |||
39 | use PluginTrait; |
||
40 | |||
41 | // Properties |
||
42 | // ========================================================================= |
||
43 | |||
44 | /** |
||
45 | * @var bool |
||
46 | */ |
||
47 | public $hasCpSettings = true; |
||
48 | |||
49 | /** |
||
50 | * @inheritdoc |
||
51 | */ |
||
52 | public $minVersionRequired = '1.1.0'; |
||
53 | |||
54 | // Public Methods |
||
55 | // ========================================================================= |
||
56 | |||
57 | /** |
||
58 | * @inheritdoc |
||
59 | */ |
||
60 | public function init() |
||
61 | { |
||
62 | parent::init(); |
||
63 | |||
64 | $this->_setPluginComponents(); |
||
65 | $this->_registerCpRoutes(); |
||
66 | $this->_registerVariable(); |
||
67 | $this->_registerEventHandlers(); |
||
68 | $this->_registerTableAttributes(); |
||
69 | $this->_initLoginAccountsUserPane(); |
||
70 | } |
||
71 | |||
72 | /** |
||
73 | * @inheritdoc |
||
74 | */ |
||
75 | public function getSettingsResponse() |
||
76 | { |
||
77 | $url = UrlHelper::cpUrl('settings/social/loginproviders'); |
||
78 | |||
79 | Craft::$app->controller->redirect($url); |
||
80 | |||
81 | return ''; |
||
82 | } |
||
83 | |||
84 | /** |
||
85 | * Get OAuth provider config. |
||
86 | * |
||
87 | * @param $handle |
||
88 | * @param bool $parse |
||
89 | * @return array |
||
90 | * @throws \yii\base\InvalidConfigException |
||
91 | */ |
||
92 | public function getOauthProviderConfig(string $handle, bool $parse = true): array |
||
93 | { |
||
94 | $config = [ |
||
95 | 'options' => $this->getOauthConfigItem($handle, 'options', $parse), |
||
96 | 'scope' => $this->getOauthConfigItem($handle, 'scope'), |
||
97 | 'authorizationOptions' => $this->getOauthConfigItem($handle, 'authorizationOptions'), |
||
98 | ]; |
||
99 | |||
100 | $provider = $this->getLoginProviders()->getLoginProvider($handle); |
||
101 | |||
102 | if ($provider && !isset($config['options']['redirectUri'])) { |
||
103 | $config['options']['redirectUri'] = $provider->getRedirectUri(); |
||
104 | } |
||
105 | |||
106 | return $config; |
||
107 | } |
||
108 | |||
109 | /** |
||
110 | * Get login provider config. |
||
111 | * |
||
112 | * @param $handle |
||
113 | * |
||
114 | * @return array |
||
115 | */ |
||
116 | public function getLoginProviderConfig($handle) |
||
117 | { |
||
118 | $configSettings = Craft::$app->config->getConfigFromFile($this->id); |
||
119 | |||
120 | if (isset($configSettings['loginProviders'][$handle])) { |
||
121 | return $configSettings['loginProviders'][$handle]; |
||
122 | } |
||
123 | |||
124 | return []; |
||
125 | } |
||
126 | |||
127 | /** |
||
128 | * Save plugin settings. |
||
129 | * |
||
130 | * @param array $settings |
||
131 | * |
||
132 | * @param Plugin|null $plugin |
||
133 | * @return bool |
||
134 | */ |
||
135 | public function savePluginSettings(array $settings, Plugin $plugin = null) |
||
136 | { |
||
137 | if ($plugin === null) { |
||
138 | $plugin = Craft::$app->getPlugins()->getPlugin('social'); |
||
139 | |||
140 | if ($plugin === null) { |
||
141 | throw new NotFoundHttpException('Plugin not found'); |
||
142 | } |
||
143 | } |
||
144 | |||
145 | $storedSettings = Craft::$app->plugins->getStoredPluginInfo('social')['settings']; |
||
146 | |||
147 | $settings['loginProviders'] = []; |
||
148 | |||
149 | if (isset($storedSettings['loginProviders'])) { |
||
150 | $settings['loginProviders'] = $storedSettings['loginProviders']; |
||
151 | } |
||
152 | |||
153 | return Craft::$app->getPlugins()->savePluginSettings($plugin, $settings); |
||
154 | } |
||
155 | |||
156 | /** |
||
157 | * Save login provider settings. |
||
158 | * |
||
159 | * @param $handle |
||
160 | * @param $providerSettings |
||
161 | * |
||
162 | * @return bool |
||
163 | */ |
||
164 | public function saveLoginProviderSettings($handle, $providerSettings) |
||
165 | { |
||
166 | $settings = (array)self::getInstance()->getSettings(); |
||
167 | $storedSettings = Craft::$app->plugins->getStoredPluginInfo('social')['settings']; |
||
168 | |||
169 | $settings['loginProviders'] = []; |
||
170 | |||
171 | if (isset($storedSettings['loginProviders'])) { |
||
172 | $settings['loginProviders'] = $storedSettings['loginProviders']; |
||
173 | } |
||
174 | |||
175 | $settings['loginProviders'][$handle] = $providerSettings; |
||
176 | |||
177 | $plugin = Craft::$app->getPlugins()->getPlugin('social'); |
||
178 | |||
179 | return Craft::$app->getPlugins()->savePluginSettings($plugin, $settings); |
||
180 | } |
||
181 | |||
182 | // Protected Methods |
||
183 | // ========================================================================= |
||
184 | |||
185 | /** |
||
186 | * @inheritdoc |
||
187 | */ |
||
188 | protected function createSettingsModel() |
||
189 | { |
||
190 | return new Settings(); |
||
191 | } |
||
192 | |||
193 | // Private Methods |
||
194 | // ========================================================================= |
||
195 | |||
196 | /** |
||
197 | * Social login for the control panel. |
||
198 | * |
||
199 | * @return null |
||
200 | * @throws \craft\errors\MissingComponentException |
||
201 | * @throws \yii\base\InvalidConfigException |
||
202 | */ |
||
203 | private function initCpSocialLogin() |
||
204 | { |
||
205 | if (!Craft::$app->getRequest()->getIsConsoleRequest() && $this->getSettings()->enableCpLogin && Craft::$app->getRequest()->getIsCpRequest() && Craft::$app->getRequest()->getSegment(1) === 'login') { |
||
206 | |||
207 | $loginProviders = $this->loginProviders->getLoginProviders(); |
||
208 | $jsLoginProviders = []; |
||
209 | |||
210 | foreach ($loginProviders as $loginProvider) { |
||
211 | $jsLoginProvider = [ |
||
212 | 'name' => $loginProvider->getName(), |
||
213 | 'handle' => $loginProvider->getHandle(), |
||
214 | 'url' => $this->getLoginAccounts()->getLoginUrl($loginProvider->getHandle()), |
||
215 | 'iconUrl' => $loginProvider->getIconUrl(), |
||
216 | ]; |
||
217 | |||
218 | $jsLoginProviders[] = $jsLoginProvider; |
||
219 | } |
||
220 | |||
221 | $error = Craft::$app->getSession()->getFlash('error'); |
||
222 | |||
223 | Craft::$app->getView()->registerAssetBundle(LoginAsset::class); |
||
224 | |||
225 | Craft::$app->getView()->registerJs('var socialLoginForm = new Craft.SocialLoginForm(' . json_encode($jsLoginProviders) . ', ' . json_encode($error) . ');'); |
||
226 | } |
||
227 | } |
||
228 | |||
229 | /** |
||
230 | * Initialize login accounts user pane. |
||
231 | * |
||
232 | * @return null |
||
233 | */ |
||
234 | private function _initLoginAccountsUserPane() |
||
235 | { |
||
236 | Craft::$app->getView()->hook('cp.users.edit.details', function(&$context) { |
||
237 | if ($context['user'] && $context['user']->id) { |
||
238 | $context['loginAccounts'] = $this->loginAccounts->getLoginAccountsByUserId($context['user']->id); |
||
239 | $context['loginProviders'] = $this->loginProviders->getLoginProviders(); |
||
240 | |||
241 | Craft::$app->getView()->registerAssetBundle(SocialAsset::class); |
||
242 | |||
243 | return Craft::$app->getView()->renderTemplate('social/_components/users/login-accounts-pane', $context); |
||
244 | } |
||
245 | }); |
||
246 | } |
||
247 | |||
248 | /** |
||
249 | * Get OAuth config item |
||
250 | * |
||
251 | * @param string $providerHandle |
||
252 | * @param string $key |
||
253 | * |
||
254 | * @return array |
||
255 | */ |
||
256 | private function getOauthConfigItem(string $providerHandle, string $key, bool $parse = true): array |
||
257 | { |
||
258 | $configSettings = Craft::$app->config->getConfigFromFile($this->id); |
||
259 | |||
260 | if (isset($configSettings['loginProviders'][$providerHandle]['oauth'][$key])) { |
||
261 | return $this->parseOauthConfigItemEnv($key, $configSettings['loginProviders'][$providerHandle]['oauth'][$key], $parse); |
||
262 | } |
||
263 | |||
264 | $storedSettings = Craft::$app->plugins->getStoredPluginInfo($this->id)['settings']; |
||
265 | |||
266 | if (isset($storedSettings['loginProviders'][$providerHandle]['oauth'][$key])) { |
||
267 | return $this->parseOauthConfigItemEnv($key, $storedSettings['loginProviders'][$providerHandle]['oauth'][$key], $parse); |
||
268 | } |
||
269 | |||
270 | return []; |
||
271 | } |
||
272 | |||
273 | /** |
||
274 | * Parse OAuth config item environment variables. |
||
275 | * |
||
276 | * @param string $key |
||
277 | * @param array $configItem |
||
278 | * @param bool $parse |
||
279 | * @return array |
||
280 | */ |
||
281 | private function parseOauthConfigItemEnv(string $key, array $configItem, bool $parse = true): array |
||
282 | { |
||
283 | // Parse config item options environment variables |
||
284 | if ($parse && $key === 'options') { |
||
285 | return array_map('Craft::parseEnv', $configItem); |
||
286 | } |
||
287 | |||
288 | return $configItem; |
||
289 | } |
||
290 | |||
291 | /** |
||
292 | * Set plugin components. |
||
293 | */ |
||
294 | private function _setPluginComponents() |
||
295 | { |
||
296 | $this->setComponents([ |
||
297 | 'loginAccounts' => \dukt\social\services\LoginAccounts::class, |
||
298 | 'loginProviders' => \dukt\social\services\LoginProviders::class, |
||
299 | ]); |
||
300 | } |
||
301 | |||
302 | /** |
||
303 | * Register CP routes. |
||
304 | */ |
||
305 | private function _registerCpRoutes() |
||
306 | { |
||
307 | Event::on(UrlManager::class, UrlManager::EVENT_REGISTER_CP_URL_RULES, function(RegisterUrlRulesEvent $event): void { |
||
308 | $rules = [ |
||
309 | 'social' => 'social/login-accounts/index', |
||
310 | |||
311 | 'social/loginaccounts' => 'social/loginAccounts/index', |
||
312 | 'social/loginaccounts/<userId:\d+>' => 'social/login-accounts/edit', |
||
313 | |||
314 | 'settings/social' => 'social/login-providers/index', |
||
315 | 'settings/social/loginproviders' => 'social/login-providers/index', |
||
316 | 'settings/social/loginproviders/<handle:{handle}>' => 'social/login-providers/oauth', |
||
317 | 'settings/social/loginproviders/<handle:{handle}>/user-field-mapping' => 'social/login-providers/user-field-mapping', |
||
318 | 'settings/social/settings' => 'social/settings/settings', |
||
319 | ]; |
||
320 | |||
321 | $event->rules = array_merge($event->rules, $rules); |
||
322 | }); |
||
323 | } |
||
324 | |||
325 | /** |
||
326 | * Register Social template variable. |
||
327 | */ |
||
328 | private function _registerVariable() |
||
329 | { |
||
330 | Event::on(CraftVariable::class, CraftVariable::EVENT_INIT, function(Event $event): void { |
||
331 | /** @var CraftVariable $variable */ |
||
332 | $variable = $event->sender; |
||
333 | $variable->set('social', SocialVariable::class); |
||
334 | }); |
||
335 | } |
||
336 | |||
337 | /** |
||
338 | * Register Social user table attributes. |
||
339 | */ |
||
340 | private function _registerTableAttributes() |
||
341 | { |
||
342 | Event::on(User::class, User::EVENT_REGISTER_TABLE_ATTRIBUTES, function(RegisterElementTableAttributesEvent $event): void { |
||
343 | $event->tableAttributes['loginAccounts'] = Craft::t('social', 'Login Accounts'); |
||
344 | }); |
||
345 | |||
346 | Event::on(User::class, User::EVENT_SET_TABLE_ATTRIBUTE_HTML, function(SetElementTableAttributeHtmlEvent $event): void { |
||
347 | if ($event->attribute === 'loginAccounts') { |
||
348 | Craft::$app->getView()->registerAssetBundle(SocialAsset::class); |
||
349 | |||
350 | $user = $event->sender; |
||
351 | |||
352 | $loginAccounts = LoginAccount::find() |
||
353 | ->userId($user->id) |
||
354 | ->trashed($user->trashed) |
||
355 | ->all(); |
||
356 | |||
357 | if ($loginAccounts) { |
||
0 ignored issues
–
show
|
|||
358 | $event->html = Craft::$app->getView()->renderTemplate('social/_components/users/login-accounts-table-attribute', [ |
||
359 | 'loginAccounts' => $loginAccounts, |
||
360 | ]); |
||
361 | } else { |
||
362 | $event->html = ''; |
||
363 | } |
||
364 | } |
||
365 | }); |
||
366 | } |
||
367 | |||
368 | /** |
||
369 | * Register event handlers. |
||
370 | */ |
||
371 | private function _registerEventHandlers() |
||
372 | { |
||
373 | Event::on(User::class, User::EVENT_AFTER_SAVE, function(ModelEvent $event): void { |
||
374 | $user = $event->sender; |
||
375 | $loginAccounts = Plugin::getInstance()->getLoginAccounts()->getLoginAccountsByUserId($user->id); |
||
376 | |||
377 | foreach ($loginAccounts as $loginAccount) { |
||
378 | Craft::$app->elements->saveElement($loginAccount); |
||
379 | } |
||
380 | }); |
||
381 | |||
382 | // Soft delete the related login accounts after deleting a user |
||
383 | Event::on(User::class, User::EVENT_AFTER_DELETE, function(Event $event): void { |
||
384 | $user = $event->sender; |
||
385 | |||
386 | $loginAccounts = LoginAccount::find() |
||
387 | ->userId($user->id) |
||
388 | ->all(); |
||
389 | |||
390 | foreach($loginAccounts as $loginAccount) { |
||
391 | Craft::$app->getElements()->deleteElement($loginAccount); |
||
392 | } |
||
393 | }); |
||
394 | |||
395 | // Make sure there’s no duplicate login account before restoring the user |
||
396 | Event::on(User::class, User::EVENT_BEFORE_RESTORE, function(ModelEvent $event) { |
||
397 | $user = $event->sender; |
||
398 | |||
399 | // Get the login accounts of the user that’s being restored |
||
400 | $loginAccounts = LoginAccount::find() |
||
401 | ->userId($user->id) |
||
402 | ->trashed(true) |
||
403 | ->all(); |
||
404 | |||
405 | $conflicts = false; |
||
406 | |||
407 | // Check that those login accounts don’t conflict with existing login accounts from other users |
||
408 | foreach ($loginAccounts as $loginAccount) { |
||
409 | // Check if there is another user with a login account using the same providerHandle/socialUid combo |
||
410 | $existingAccount = LoginAccount::find()->one(); |
||
411 | |||
412 | if ($existingAccount) { |
||
413 | $conflicts = true; |
||
414 | } |
||
415 | } |
||
416 | |||
417 | // Mark the event as invalid is there are conflicts |
||
418 | if ($conflicts) { |
||
419 | $event->isValid = false; |
||
420 | return false; |
||
421 | } |
||
422 | |||
423 | // Restore login account elements |
||
424 | foreach($loginAccounts as $loginAccount) { |
||
425 | Craft::$app->getElements()->restoreElement($loginAccount); |
||
426 | } |
||
427 | }); |
||
428 | |||
429 | // Initialize Social Login for CP after loading the plugins |
||
430 | Event::on(Plugins::class, Plugins::EVENT_AFTER_LOAD_PLUGINS, function(): void { |
||
431 | $this->initCpSocialLogin(); |
||
432 | }); |
||
433 | } |
||
434 | } |
||
435 |
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.