>
30 | */
31 | private const FILTERS = [AuthenticationFilters::class];
32 |
33 | /**
34 | * @var mixed[]
35 | */
36 | private array $registry = [];
37 |
38 | public function __construct(
39 | private ?Auth0 $auth0,
40 | private ?SdkConfiguration $sdkConfiguration,
41 | ) {
42 | }
43 |
44 | /**
45 | * Returns a singleton instance of Hooks configured for working with actions.
46 | */
47 | public function actions(): Hooks
48 | {
49 | static $instance = null;
50 |
51 | if (null === $instance) {
52 | $instance = new Hooks(Hooks::CONST_ACTION_HOOK);
53 | }
54 |
55 | return $instance;
56 | }
57 |
58 | /**
59 | * Returns a singleton instance of Database.
60 | */
61 | public function database(): Database
62 | {
63 | static $instance = null;
64 |
65 | if (null === $instance) {
66 | $instance = new Database();
67 | }
68 |
69 | return $instance;
70 | }
71 |
72 | /**
73 | * Returns a singleton instance of Hooks configured for working with filters.
74 | */
75 | public function filters(): Hooks
76 | {
77 | static $instance = null;
78 |
79 | if (null === $instance) {
80 | $instance = new Hooks(Hooks::CONST_ACTION_FILTER);
81 | }
82 |
83 | return $instance;
84 | }
85 |
86 | public function getClassInstance(string $class): mixed
87 | {
88 | if (! array_key_exists($class, $this->registry)) {
89 | $this->registry[$class] = new $class($this);
90 | }
91 |
92 | return $this->registry[$class];
93 | }
94 |
95 | /**
96 | * Returns a singleton instance of SdkConfiguration.
97 | */
98 | public function getConfiguration(): SdkConfiguration
99 | {
100 | $this->sdkConfiguration ??= $this->importConfiguration();
101 |
102 | return $this->sdkConfiguration;
103 | }
104 |
105 | /**
106 | * @psalm-param 0|null $default
107 | *
108 | * @param string $group
109 | * @param string $key
110 | * @param ?int $default
111 | * @param string $prefix
112 | */
113 | public function getOption(string $group, string $key, ?int $default = null, string $prefix = 'auth0_'): mixed
114 | {
115 | $options = get_option($prefix . $group, []);
116 |
117 | if (is_array($options) && isset($options[$key])) {
118 | return $options[$key];
119 | }
120 |
121 | return $default;
122 | }
123 |
124 | public function getOptionBoolean(string $group, string $key, string $prefix = 'auth0_'): ?bool
125 | {
126 | $result = $this->getOption($group, $key, null, $prefix);
127 |
128 | if (is_string($result)) {
129 | return 'true' === $result || '1' === $result;
130 | }
131 |
132 | return null;
133 | }
134 |
135 | public function getOptionInteger(string $group, string $key, string $prefix = 'auth0_'): ?int
136 | {
137 | $result = $this->getOption($group, $key, null, $prefix);
138 |
139 | if (is_int($result)) {
140 | return $result;
141 | }
142 |
143 | if (is_numeric($result)) {
144 | return (int) $result;
145 | }
146 |
147 | return null;
148 | }
149 |
150 | public function getOptionString(string $group, string $key, string $prefix = 'auth0_'): ?string
151 | {
152 | $result = $this->getOption($group, $key, null, $prefix);
153 |
154 | if (is_string($result)) {
155 | return $result;
156 | }
157 |
158 | return null;
159 | }
160 |
161 | /**
162 | * Returns a singleton instance of the Auth0 SDK.
163 | */
164 | public function getSdk(): Auth0
165 | {
166 | $this->auth0 ??= new Auth0($this->getConfiguration());
167 |
168 | return $this->auth0;
169 | }
170 |
171 | /**
172 | * Returns true if the plugin has been enabled.
173 | */
174 | public function isEnabled(): bool
175 | {
176 | return 'true' === $this->getOptionString('state', 'enable');
177 | }
178 |
179 | /**
180 | * Returns true if the plugin has a minimum viable configuration.
181 | */
182 | public function isReady(): bool
183 | {
184 | try {
185 | $config = $this->getConfiguration();
186 | } catch (Throwable) {
187 | return false;
188 | }
189 |
190 | if (! $config->hasClientId()) {
191 | return false;
192 | }
193 |
194 | if ('' === (string) $config->getClientId()) {
195 | return false;
196 | }
197 |
198 | if (! $config->hasClientSecret()) {
199 | return false;
200 | }
201 |
202 | if ('' === (string) $config->getClientSecret()) {
203 | return false;
204 | }
205 |
206 | if (! $config->hasDomain()) {
207 | return false;
208 | }
209 |
210 | if ('' === $config->getDomain()) {
211 | return false;
212 | }
213 |
214 | if (! $config->hasCookieSecret()) {
215 | return false;
216 | }
217 |
218 | return '' !== (string) $config->getCookieSecret();
219 | }
220 |
221 | /**
222 | * Main plugin functionality.
223 | */
224 | public function run(): self
225 | {
226 | foreach (self::FILTERS as $filter) {
227 | $callback = [$this->getClassInstance($filter), 'register'];
228 |
229 | /**
230 | * @var callable $callback
231 | */
232 | $callback();
233 | }
234 |
235 | foreach (self::ACTIONS as $action) {
236 | $callback = [$this->getClassInstance($action), 'register'];
237 |
238 | /**
239 | * @var callable $callback
240 | */
241 | $callback();
242 | }
243 |
244 | return $this;
245 | }
246 |
247 | /**
248 | * Assign a Auth0\SDK\Configuration\SdkConfiguration instance for the plugin to use.
249 | *
250 | * @param SdkConfiguration $sdkConfiguration
251 | */
252 | public function setConfiguration(SdkConfiguration $sdkConfiguration): self
253 | {
254 | $this->sdkConfiguration = $sdkConfiguration;
255 |
256 | return $this;
257 | }
258 |
259 | /**
260 | * Assign a Auth0\SDK\Auth0 instance for the plugin to use.
261 | *
262 | * @param Auth0 $auth0
263 | */
264 | public function setSdk(Auth0 $auth0): self
265 | {
266 | $this->auth0 = $auth0;
267 |
268 | return $this;
269 | }
270 |
271 | /**
272 | * Import configuration settings from database.
273 | */
274 | private function importConfiguration(): SdkConfiguration
275 | {
276 | $audiences = $this->getOptionString('client_advanced', 'apis') ?? '';
277 | $organizations = $this->getOptionString('client_advanced', 'organizations') ?? '';
278 | $caching = $this->getOption('tokens', 'caching');
279 |
280 | $audiences = array_filter(array_values(array_unique(explode("\n", trim($audiences)))));
281 | $organizations = array_filter(array_values(array_unique(explode("\n", trim($organizations)))));
282 | $secure = $this->getOptionBoolean('cookies', 'secure') ?? is_ssl();
283 | $expires = $this->getOptionInteger('cookies', 'ttl') ?? 0;
284 |
285 | if (defined('DOING_CRON')) {
286 | // When invoked from a WP_Cron task, just use a minimum configuration for those needs (namely, no sessions invoked.)
287 | $sdkConfiguration = new SdkConfiguration(
288 | strategy: SdkConfiguration::STRATEGY_NONE,
289 | httpRequestFactory: Factory::getRequestFactory(),
290 | httpResponseFactory: Factory::getResponseFactory(),
291 | httpStreamFactory: Factory::getStreamFactory(),
292 | httpClient: Factory::getClient(),
293 | domain: $this->getOptionString('client', 'domain'),
294 | clientId: $this->getOptionString('client', 'id'),
295 | clientSecret: $this->getOptionString('client', 'secret'),
296 | audience: [] !== $audiences ? $audiences : null,
297 | organization: [] !== $organizations ? $organizations : null,
298 | );
299 | } else {
300 | $sdkConfiguration = new SdkConfiguration(
301 | strategy: SdkConfiguration::STRATEGY_REGULAR,
302 | httpRequestFactory: Factory::getRequestFactory(),
303 | httpResponseFactory: Factory::getResponseFactory(),
304 | httpStreamFactory: Factory::getStreamFactory(),
305 | httpClient: Factory::getClient(),
306 | domain: $this->getOptionString('client', 'domain'),
307 | clientId: $this->getOptionString('client', 'id'),
308 | clientSecret: $this->getOptionString('client', 'secret'),
309 | customDomain: $this->getOptionString('client_advanced', 'custom_domain'),
310 | audience: [] !== $audiences ? $audiences : null,
311 | organization: [] !== $organizations ? $organizations : null,
312 | cookieSecret: $this->getOptionString('cookies', 'secret'),
313 | cookieDomain: $this->getOptionString('cookies', 'domain'),
314 | cookiePath: $this->getOptionString('cookies', 'path') ?? '/',
315 | cookieExpires: $expires,
316 | cookieSecure: (bool) $secure,
317 | cookieSameSite: $this->getOptionString('cookies', 'samesite'),
318 | redirectUri: get_site_url(null, 'wp-login.php'),
319 | );
320 | }
321 |
322 | if ('disable' !== $caching) {
323 | $wpObjectCachePool = new WpObjectCachePool();
324 | $sdkConfiguration->setTokenCache($wpObjectCachePool);
325 | $sdkConfiguration->setBackchannelLogoutCache($wpObjectCachePool);
326 | }
327 |
328 | return $sdkConfiguration;
329 | }
330 | }
331 |
--------------------------------------------------------------------------------
/src/Actions/Sync.php:
--------------------------------------------------------------------------------
1 | |string>
38 | */
39 | protected array $registry = [
40 | self::CONST_JOB_BACKGROUND_SYNC => 'onBackgroundSync',
41 | self::CONST_JOB_BACKGROUND_MAINTENANCE => 'onBackgroundMaintenance',
42 | 'cron_schedules' => 'updateCronSchedule',
43 | ];
44 |
45 | /**
46 | * In the event of an issue during the WP account deletion hooks, connections might be left in the accounts table that point to missing WP accounts.
47 | * This clears out those 'orphaned' connections for re-use by other WP accounts, or for use in creating a new WP account.
48 | */
49 | public function cleanupOrphanedConnections(): void
50 | {
51 | $database = $this->getPlugin()->database();
52 | $table = $database->getTableName(Database::CONST_TABLE_ACCOUNTS);
53 | $network = get_current_network_id();
54 | $blog = get_current_blog_id();
55 |
56 | $this->getPlugin()->database()->createTable(Database::CONST_TABLE_ACCOUNTS);
57 |
58 | $users = $database->selectDistinctResults('user', $table, 'WHERE `site` = %d AND `blog` = %d', [$network, $blog]);
59 | if (! is_array($users)) {
60 | return;
61 | }
62 |
63 | if ([] === $users) {
64 | return;
65 | }
66 |
67 | foreach ($users as $user) {
68 | $found = get_user_by('ID', $user->user);
69 |
70 | if (! $found) {
71 | $this->authentication()->deleteAccountConnections((int) $user->user);
72 | }
73 | }
74 | }
75 |
76 | public function eventUserCreated(string $dbConnection, array $event): void
77 | {
78 | if (isset($event['user'])) {
79 | $user = $event['user'] ?? null;
80 |
81 | if (null === $user) {
82 | return;
83 | }
84 |
85 | $user = get_user_by('ID', $user);
86 |
87 | if ($user) {
88 | $exists = $this->getResults($this->getSdk()->management()->usersByEmail()->get($user->user_email));
89 |
90 | if (! is_array($exists) || [] === $exists) {
91 | $dbConnectionName = $this->getDatabaseName($dbConnection);
92 |
93 | $response = $this->getSdk()->management()->users()->create($dbConnectionName, [
94 | 'name' => $user->display_name,
95 | 'nickname' => $user->nickname,
96 | 'given_name' => $user->user_firstname,
97 | 'family_name' => $user->user_lastname,
98 | 'email' => $user->user_email,
99 | 'password' => wp_generate_password(random_int(12, 123), true, true),
100 | ]);
101 |
102 | $response = $this->getResults($response, 201);
103 |
104 | if (null !== $response) {
105 | // Trigger a password change email to let them set their password
106 | $this->getSdk()->management()->tickets()->createPasswordChange([
107 | 'user_id' => $response['user_id'],
108 | ]);
109 |
110 | $this->authentication()->createAccountConnection($user, $response['user_id']);
111 | }
112 | }
113 | }
114 | }
115 | }
116 |
117 | public function eventUserDeleted(string $dbConnection, array $event): void
118 | {
119 | if (isset($event['user'])) {
120 | $user = $event['user'] ?? null;
121 | $connection = $event['connection'] ?? null;
122 |
123 | if (null !== $user && null !== $connection) {
124 | // Verify that the connection has not been claimed by another account already
125 | $wpUser = $this->authentication()->getAccountByConnection($connection);
126 |
127 | if (! $wpUser instanceof WP_User) {
128 | // Determine if the Auth0 counterpart account still exists
129 | $api = $this->getResults($this->getSdk()->management()->users()->get($connection));
130 |
131 | if (null !== $api) {
132 | // Delete the Auth0 counterpart account
133 | $this->getSdk()->management()->users()->delete($connection);
134 | }
135 | }
136 | }
137 | }
138 | }
139 |
140 | public function eventUserUpdated(string $dbConnection, array $event): void
141 | {
142 | if (isset($event['user'])) {
143 | $user = $event['user'] ?? null;
144 | $connection = $event['connection'] ?? null;
145 |
146 | if (null === $user && null === $connection) {
147 | return;
148 | }
149 |
150 | $user = get_user_by('ID', $user);
151 |
152 | if (! $user) {
153 | return;
154 | }
155 |
156 | $connections = $this->authentication()->getAccountConnections($user->ID);
157 |
158 | if (null !== $connections) {
159 | foreach ($connections as $connection) {
160 | $api = $this->getResults($this->getSdk()->management()->users()->get($connection->auth0));
161 |
162 | if (null !== $api) {
163 | $connectionId = $api['user_id'] ?? null;
164 |
165 | if (null === $connectionId) {
166 | continue;
167 | }
168 |
169 | $currentEmail = $api['email'] ?? '';
170 |
171 | $this->getSdk()->management()->users()->update($connectionId, [
172 | 'name' => $user->display_name,
173 | 'nickname' => $user->nickname,
174 | 'given_name' => $user->user_firstname,
175 | 'family_name' => $user->user_lastname,
176 | 'email' => $user->user_email,
177 | ]);
178 |
179 | if ($user->user_email !== $currentEmail) {
180 | $this->getSdk()->management()->tickets()->createEmailVerification($connectionId);
181 | }
182 | }
183 | }
184 | }
185 | }
186 | }
187 |
188 | public function getDatabaseName(?string $dbConnection): ?string
189 | {
190 | static $dbConnectionName = [];
191 |
192 | if (isset($dbConnectionName[$dbConnection])) {
193 | return $dbConnectionName[$dbConnectionName];
194 | }
195 |
196 | if (null !== $dbConnection) {
197 | $response = $this->getResults($this->getSdk()->management()->connections()->get($dbConnection));
198 |
199 | if ($response) {
200 | $dbConnectionName[$dbConnection] = $response['name'];
201 |
202 | return $response['name'] ?? $dbConnection;
203 | }
204 | }
205 |
206 | return null;
207 | }
208 |
209 | public function onBackgroundMaintenance(): void
210 | {
211 | $this->cleanupOrphanedConnections();
212 | }
213 |
214 | public function onBackgroundSync(): void
215 | {
216 | $database = $this->getPlugin()->database();
217 | $table = $database->getTableName(Database::CONST_TABLE_SYNC);
218 | $network = get_current_network_id();
219 | $blog = get_current_blog_id();
220 |
221 | $this->getPlugin()->database()->createTable(Database::CONST_TABLE_SYNC);
222 |
223 | $queue = $database->selectResults('*', $table, 'WHERE `site` = %d AND `blog` = %d ORDER BY created LIMIT 10', [$network, $blog]);
224 |
225 | $enabledEvents = [
226 | 'wp_user_created' => $this->getPlugin()->getOptionBoolean('sync_events', 'user_creation') ?? true,
227 | 'wp_user_deleted' => $this->getPlugin()->getOptionBoolean('sync_events', 'user_deletion') ?? true,
228 | 'wp_user_updated' => $this->getPlugin()->getOptionBoolean('sync_events', 'user_updates') ?? true,
229 | ];
230 |
231 | $dbConnection = $this->getPlugin()->getOptionString('sync', 'database');
232 |
233 | foreach ($queue as $singleQueue) {
234 | if (null !== $dbConnection) {
235 | $payload = json_decode($singleQueue->payload, true, 512, JSON_THROW_ON_ERROR);
236 |
237 | if (isset($payload['event'])) {
238 | if ('wp_user_created' === $payload['event'] && $enabledEvents['wp_user_created']) {
239 | $this->eventUserCreated($dbConnection, $payload);
240 | }
241 |
242 | if ('wp_user_deleted' === $payload['event'] && $enabledEvents['wp_user_deleted']) {
243 | $this->eventUserDeleted($dbConnection, $payload);
244 | }
245 |
246 | if ('wp_user_updated' === $payload['event'] && $enabledEvents['wp_user_updated']) {
247 | $this->eventUserUpdated($dbConnection, $payload);
248 | }
249 | }
250 | }
251 |
252 | $database->deleteRow($table, ['id' => $singleQueue->id], ['%d']);
253 | }
254 | }
255 |
256 | /**
257 | * @param mixed $schedules
258 | *
259 | * @return mixed[]
260 | */
261 | public function updateCronSchedule($schedules): array
262 | {
263 | $schedules[self::CONST_SCHEDULE_BACKGROUND_SYNC] = ['interval' => $this->getPlugin()->getOptionInteger('sync', 'schedule') ?? 3600, 'display' => 'Plugin Configuration'];
264 |
265 | $schedules[self::CONST_SCHEDULE_BACKGROUND_MAINTENANCE] = ['interval' => 300, 'display' => 'Every 5 Minutes'];
266 |
267 | return $schedules;
268 | }
269 |
270 | private function authentication(): Authentication
271 | {
272 | return $this->getPlugin()->getClassInstance(Authentication::class);
273 | }
274 |
275 | private function getResults(ResponseInterface $response, int $expectedStatusCode = 200): ?array
276 | {
277 | if (HttpResponse::wasSuccessful($response, $expectedStatusCode)) {
278 | return HttpResponse::decodeContent($response);
279 | }
280 |
281 | return null;
282 | }
283 | }
284 |
--------------------------------------------------------------------------------
/.php-cs-fixer.dist.php:
--------------------------------------------------------------------------------
1 | setRiskyAllowed(true)
7 | ->setRules([
8 | 'array_indentation' => true,
9 | 'array_push' => true,
10 | 'array_syntax' => ['syntax' => 'short'],
11 | 'assign_null_coalescing_to_coalesce_equal' => true,
12 | 'backtick_to_shell_exec' => true,
13 | 'binary_operator_spaces' => true,
14 | 'blank_line_after_namespace' => true,
15 | 'blank_line_after_opening_tag' => true,
16 | 'blank_line_before_statement' => true,
17 | 'blank_line_between_import_groups' => true,
18 | 'braces' => true,
19 | 'cast_spaces' => true,
20 | 'class_attributes_separation' => ['elements' => ['const' => 'one', 'method' => 'one', 'property' => 'one', 'trait_import' => 'one', 'case' => 'one']],
21 | 'class_definition' => ['multi_line_extends_each_single_line' => true, 'single_line' => true, 'single_item_single_line' => true, 'space_before_parenthesis' => false, 'inline_constructor_arguments' => false],
22 | 'class_reference_name_casing' => true,
23 | 'clean_namespace' => true,
24 | 'combine_consecutive_issets' => true,
25 | 'combine_consecutive_unsets' => true,
26 | 'combine_nested_dirname' => true,
27 | 'comment_to_phpdoc' => ['ignored_tags' => ['codeCoverageIgnoreStart', 'codeCoverageIgnoreEnd', 'phpstan-ignore-next-line']],
28 | 'compact_nullable_typehint' => true,
29 | 'concat_space' => ['spacing' => 'one'],
30 | 'constant_case' => ['case' => 'lower'],
31 | 'curly_braces_position' => ['control_structures_opening_brace' => 'same_line', 'functions_opening_brace' => 'next_line_unless_newline_at_signature_end', 'anonymous_functions_opening_brace' => 'same_line', 'classes_opening_brace' => 'next_line_unless_newline_at_signature_end', 'anonymous_classes_opening_brace' => 'same_line', 'allow_single_line_empty_anonymous_classes' => true, 'allow_single_line_anonymous_functions' => true],
32 | 'date_time_create_from_format_call' => true,
33 | 'date_time_immutable' => true,
34 | 'declare_equal_normalize' => ['space' => 'none'],
35 | 'declare_parentheses' => true,
36 | 'declare_strict_types' => true,
37 | 'dir_constant' => true,
38 | 'doctrine_annotation_array_assignment' => true,
39 | 'doctrine_annotation_braces' => true,
40 | 'doctrine_annotation_indentation' => true,
41 | 'doctrine_annotation_spaces' => true,
42 | 'echo_tag_syntax' => ['format' => 'long'],
43 | 'elseif' => true,
44 | 'empty_loop_body' => true,
45 | 'empty_loop_condition' => true,
46 | 'encoding' => true,
47 | 'ereg_to_preg' => true,
48 | 'error_suppression' => true,
49 | 'escape_implicit_backslashes' => true,
50 | 'explicit_indirect_variable' => true,
51 | 'explicit_string_variable' => true,
52 | 'final_class' => true,
53 | 'final_internal_class' => true,
54 | 'final_public_method_for_abstract_class' => true,
55 | 'fopen_flag_order' => true,
56 | 'fopen_flags' => true,
57 | 'full_opening_tag' => true,
58 | 'fully_qualified_strict_types' => true,
59 | 'function_declaration' => true,
60 | 'function_to_constant' => true,
61 | 'function_typehint_space' => true,
62 | 'general_phpdoc_annotation_remove' => true,
63 | 'general_phpdoc_tag_rename' => true,
64 | 'get_class_to_class_keyword' => true,
65 | 'global_namespace_import' => ['import_classes' => true, 'import_constants' => true, 'import_functions' => true],
66 | 'group_import' => true,
67 | 'heredoc_indentation' => true,
68 | 'heredoc_to_nowdoc' => true,
69 | 'implode_call' => true,
70 | 'include' => true,
71 | 'increment_style' => ['style' => 'pre'],
72 | 'indentation_type' => true,
73 | 'integer_literal_case' => true,
74 | 'is_null' => true,
75 | 'lambda_not_used_import' => true,
76 | 'line_ending' => true,
77 | 'linebreak_after_opening_tag' => true,
78 | 'list_syntax' => ['syntax' => 'short'],
79 | 'logical_operators' => true,
80 | 'lowercase_cast' => true,
81 | 'lowercase_keywords' => true,
82 | 'lowercase_static_reference' => true,
83 | 'magic_constant_casing' => true,
84 | 'magic_method_casing' => true,
85 | 'mb_str_functions' => false,
86 | 'method_argument_space' => ['on_multiline' => 'ensure_fully_multiline', 'after_heredoc' => true],
87 | 'method_chaining_indentation' => true,
88 | 'modernize_strpos' => true,
89 | 'modernize_types_casting' => true,
90 | 'multiline_comment_opening_closing' => true,
91 | 'multiline_whitespace_before_semicolons' => true,
92 | 'native_function_casing' => true,
93 | 'native_function_invocation' => true,
94 | 'native_function_type_declaration_casing' => true,
95 | 'new_with_braces' => true,
96 | 'no_alias_functions' => true,
97 | 'no_alias_language_construct_call' => true,
98 | 'no_alternative_syntax' => true,
99 | 'no_binary_string' => true,
100 | 'no_blank_lines_after_class_opening' => true,
101 | 'no_blank_lines_after_phpdoc' => true,
102 | 'no_break_comment' => true,
103 | 'no_closing_tag' => true,
104 | 'no_empty_comment' => true,
105 | 'no_empty_phpdoc' => true,
106 | 'no_empty_statement' => true,
107 | 'no_extra_blank_lines' => true,
108 | 'no_homoglyph_names' => true,
109 | 'no_leading_import_slash' => true,
110 | 'no_leading_namespace_whitespace' => true,
111 | 'no_mixed_echo_print' => true,
112 | 'no_multiline_whitespace_around_double_arrow' => true,
113 | 'no_multiple_statements_per_line' => true,
114 | 'no_php4_constructor' => true,
115 | 'no_short_bool_cast' => true,
116 | 'no_singleline_whitespace_before_semicolons' => true,
117 | 'no_space_around_double_colon' => true,
118 | 'no_spaces_after_function_name' => true,
119 | 'no_spaces_around_offset' => true,
120 | 'no_spaces_inside_parenthesis' => true,
121 | 'no_superfluous_elseif' => true,
122 | 'no_trailing_comma_in_singleline' => true,
123 | 'no_trailing_whitespace_in_comment' => true,
124 | 'no_trailing_whitespace_in_string' => true,
125 | 'no_trailing_whitespace' => true,
126 | 'no_unneeded_control_parentheses' => true,
127 | 'no_unneeded_curly_braces' => true,
128 | 'no_unneeded_final_method' => true,
129 | 'no_unneeded_import_alias' => true,
130 | 'no_unreachable_default_argument_value' => true,
131 | 'no_unset_cast' => true,
132 | 'no_unused_imports' => true,
133 | 'no_useless_concat_operator' => true,
134 | 'no_useless_else' => true,
135 | 'no_useless_nullsafe_operator' => true,
136 | 'no_useless_return' => true,
137 | 'no_useless_sprintf' => true,
138 | 'no_whitespace_before_comma_in_array' => true,
139 | 'no_whitespace_in_blank_line' => true,
140 | 'non_printable_character' => true,
141 | 'normalize_index_brace' => true,
142 | 'not_operator_with_successor_space' => true,
143 | 'nullable_type_declaration_for_default_null_value' => true,
144 | 'object_operator_without_whitespace' => true,
145 | 'octal_notation' => true,
146 | 'operator_linebreak' => true,
147 | 'ordered_class_elements' => ['sort_algorithm' => 'alpha', 'order' => ['use_trait', 'case', 'constant', 'constant_private', 'constant_protected', 'constant_public', 'property_private', 'property_private_readonly', 'property_private_static', 'property_protected', 'property_protected_readonly', 'property_protected_static', 'property_public', 'property_public_readonly', 'property_public_static', 'property_static', 'protected', 'construct', 'destruct', 'magic', 'method', 'public', 'method_public', 'method_abstract', 'method_public_abstract', 'method_public_abstract_static', 'method_public_static', 'method_static', 'method_private', 'method_private_abstract', 'method_private_abstract_static', 'method_private_static', 'method_protected', 'method_protected_abstract', 'method_protected_abstract_static', 'method_protected_static', 'phpunit', 'private', 'property']],
148 | 'ordered_imports' => ['sort_algorithm' => 'alpha', 'imports_order' => ['const', 'class', 'function']],
149 | 'ordered_interfaces' => true,
150 | 'ordered_traits' => true,
151 | 'php_unit_fqcn_annotation' => true,
152 | 'phpdoc_add_missing_param_annotation' => ['only_untyped' => false],
153 | 'phpdoc_align' => ['align' => 'vertical'],
154 | 'phpdoc_indent' => true,
155 | 'phpdoc_inline_tag_normalizer' => true,
156 | 'phpdoc_line_span' => true,
157 | 'phpdoc_no_access' => true,
158 | 'phpdoc_no_empty_return' => true,
159 | 'phpdoc_no_package' => true,
160 | 'phpdoc_no_useless_inheritdoc' => true,
161 | 'phpdoc_order_by_value' => true,
162 | 'phpdoc_order' => true,
163 | 'phpdoc_return_self_reference' => ['replacements' => ['this' => 'self']],
164 | 'phpdoc_scalar' => true,
165 | 'phpdoc_separation' => true,
166 | 'phpdoc_single_line_var_spacing' => true,
167 | 'phpdoc_summary' => true,
168 | 'phpdoc_tag_type' => true,
169 | 'phpdoc_to_comment' => ['ignored_tags' => ['var']],
170 | 'phpdoc_trim_consecutive_blank_line_separation' => true,
171 | 'phpdoc_trim' => true,
172 | 'phpdoc_types_order' => true,
173 | 'phpdoc_types' => true,
174 | 'phpdoc_var_annotation_correct_order' => true,
175 | 'phpdoc_var_without_name' => true,
176 | 'pow_to_exponentiation' => true,
177 | 'protected_to_private' => true,
178 | 'psr_autoloading' => true,
179 | 'random_api_migration' => true,
180 | 'regular_callable_call' => true,
181 | 'return_assignment' => true,
182 | 'return_type_declaration' => ['space_before' => 'none'],
183 | 'return_type_declaration' => true,
184 | 'self_accessor' => true,
185 | 'self_static_accessor' => true,
186 | 'semicolon_after_instruction' => true,
187 | 'set_type_to_cast' => true,
188 | 'short_scalar_cast' => true,
189 | 'simple_to_complex_string_variable' => true,
190 | 'simplified_if_return' => true,
191 | 'single_blank_line_at_eof' => true,
192 | 'single_blank_line_before_namespace' => true,
193 | 'single_class_element_per_statement' => true,
194 | 'single_line_after_imports' => true,
195 | 'single_line_comment_spacing' => true,
196 | 'single_line_comment_style' => ['comment_types' => ['hash']],
197 | 'single_line_throw' => true,
198 | 'single_quote' => true,
199 | 'single_space_after_construct' => true,
200 | 'single_space_around_construct' => true,
201 | 'single_trait_insert_per_statement' => true,
202 | 'space_after_semicolon' => true,
203 | 'standardize_increment' => true,
204 | 'standardize_not_equals' => true,
205 | 'statement_indentation' => true,
206 | 'static_lambda' => true,
207 | 'strict_comparison' => true,
208 | 'strict_param' => true,
209 | 'string_length_to_empty' => true,
210 | 'string_line_ending' => true,
211 | 'switch_case_semicolon_to_colon' => true,
212 | 'switch_case_space' => true,
213 | 'switch_continue_to_break' => true,
214 | 'ternary_operator_spaces' => true,
215 | 'ternary_to_elvis_operator' => true,
216 | 'ternary_to_null_coalescing' => true,
217 | 'trailing_comma_in_multiline' => ['after_heredoc' => true, 'elements' => ['arguments', 'arrays', 'match', 'parameters']],
218 | 'trim_array_spaces' => true,
219 | 'types_spaces' => ['space' => 'single', 'space_multiple_catch' => 'single'],
220 | 'unary_operator_spaces' => true,
221 | 'use_arrow_functions' => true,
222 | 'visibility_required' => true,
223 | 'void_return' => true,
224 | 'whitespace_after_comma_in_array' => true,
225 | 'yoda_style' => true,
226 | ])
227 | ->setFinder(
228 | PhpCsFixer\Finder::create()
229 | ->exclude('vendor')
230 | ->in([__DIR__ . '/src/']),
231 | );
232 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | WordPress Plugin for [Auth0](https://auth0.com) Authentication
4 |
5 | [](https://doge.mit-license.org/)
6 |
7 | :rocket: [Getting Started](#getting-started) - :computer: [SDK Usage](#sdk-usage) - 📆 [Support Policy](#support-policy) - :speech_balloon: [Feedback](#feedback)
8 |
9 | ## Overview
10 |
11 | The Auth0 WordPress plugin replaces the standard WordPress login flow with a new authentication process using Auth0's Universal Login experience. This enables you to secure your WordPress site with Auth0's advanced features, such as MFA, SSO, Passwordless, PassKey, and so on.
12 |
13 | > [!IMPORTANT]
14 | > This plugin is **NOT** a SDK (Software Development Kit.) It's APIs are internal and not intended for developers to extend directly. We do not support altering the plugin's behavior or integrating it in any way beyond what is outlined in this README. If you're looking to build a more extensive integration, please create a solution using the [Auth0-PHP SDK](https://github.com/auth0/auth0-php) instead.
15 |
16 | > [!WARNING]
17 | > v4 of the plugin is no longer supported as of June 2023. We are no longer providing new features or bugfixes for that release. Please upgrade to v5 as soon as possible.
18 |
19 | ## Getting Started
20 |
21 | ### Requirements
22 |
23 | - PHP 8.1+
24 | - [Most recent version of WordPress](https://wordpress.org/news/category/releases/)
25 | - Database credentials with table creation permissions
26 |
27 | > Please review our [support policy](#support-policy) on specific PHP and WordPress versions and when they may exit support in the future.
28 |
29 | ### Installation
30 |
31 |
54 |
55 | #### Composer
56 |
57 | The plugin supports installation through [Composer](https://getcomposer.org/), and is [WPackagist](https://wpackagist.org/) compatible. This approach is preferred when using [Bedrock](https://roots.io/bedrock/), but will work with virtually any WordPress installation.
58 |
59 | For [Bedrock](https://roots.io/bedrock/) installations, you'll usually run this command from the root WordPress installation directory, but check the documentation the project's maintainers provide for the best guidance.
60 |
61 | For standard WordPress installations, this command can be run from the `wp-content/plugins` sub-directory.
62 |
63 | ```
64 | composer require symfony/http-client nyholm/psr7 auth0/wordpress:^5.0
65 | ```
66 |
67 |
68 | Note on Composer Dependencies
69 |
70 | When installed with Composer, the plugin depends on the presence of [PSR-18](https://packagist.org/providers/psr/http-client-implementation) and [PSR-17](https://packagist.org/providers/psr/http-factory-implementation) library implementations. The `require` command above includes two such libraries (`symfony/http-client` and `nyholm/psr7`) that satisfy these requirements, but you can use any other compatible libraries that you prefer. Visit Packagist for a list of [PSR-18](https://packagist.org/providers/psr/http-client-implementation) and [PSR-17](https://packagist.org/providers/psr/http-factory-implementation) providers.
71 |
72 | If you are using Bedrock or another Composer-based configuration, you can try installing `auth0/wordpress` without any other dependencies, as the implementations may be satisfied by other already installed packages.
73 |
74 | > **Note** PHP Standards Recommendations (PSRs) are standards for PHP libraries and applications that enable greater interoperability and choice. You can learn more about them and the PHP-FIG organization that maintains them [here](https://www.php-fig.org/).
75 |
76 |
77 |
78 |
90 |
91 | ### Activation
92 |
93 | After installation, you must activate the plugin within your WordPress site:
94 |
95 | 1. Open your WordPress Dashboard.
96 | 2. Select 'Plugins' from the sidebar, and then 'Installed Plugins.'
97 | 3. Choose 'Activate' underneath the plugin's name.
98 |
99 | ### Configure Auth0
100 |
101 | 1. Sign into Auth0. If you don't have an account, [it's free to create one](https://auth0.com/signup).
102 | 2. [Open 'Applications' from your Auth0 Dashboard](https://manage.auth0.com/#/applications/create), and select 'Create Application.'
103 | 3. Choose 'Regular Web Application' and then 'Create.'
104 | 4. From the newly created application's page, select the Settings tab.
105 |
106 | Please prepare the following information:
107 |
108 | - Note the **Domain**, **Client ID**, and **Client Secret**, available from the newly created Application's Settings page. You will need these to configure the plugin in the next step.
109 | - From your WordPress Dashboard's General Settings page, note your **WordPress Address** and **Site Address** URLs. We recommend you read our guidance on [common WordPress URL issues](#common-wordpress-url-issues).
110 |
111 | Continue configuring your Auth0 application from its Settings page:
112 |
113 | - **Allowed Callback URLs** should include the URL to your WordPress site's `wp-login.php`.
114 | - In most (but not all) cases, this will be your WordPress Address with `/wp-login.php` appended.
115 | - Please ensure your site is configured never to cache this URL, or you may see an "invalid state" error during login.
116 | - **Allowed Web Origins** should include both your WordPress Address and Site Address URLs.
117 | - **Allowed Logout URLs** should consist of your WordPress Address.
118 |
119 |
120 | Common WordPress URL Issues
121 |
122 | - These must be the URLs your visitors will use to access your WordPress site. If you are using a reverse proxy, you may need to manually configure your WordPress Address and Site Address URLs to match the URL you use to access your site.
123 | - Make sure these URLs match your site's configured protocol. When using a reverse proxy, you may need to update these to reflect serving over SSL/HTTPS.
124 |
125 |
126 |
127 | Troubleshooting
128 |
129 | If you're encountering issues, start by checking that your Auth0 Application is setup like so:
130 |
131 | - **Application Type** must be set to **Regular Web Application**.
132 | - **Token Endpoint Authentication Method** must be set to **Post**.
133 | - **Allowed Origins (CORS)** should be blank.
134 |
135 | Scroll down and expand the "Advanced Settings" panel, then:
136 |
137 | - Under **OAuth**:
138 | - Ensure that **JsonWebToken Signature Algorithm** is set to **RS256**.
139 | - Check that **OIDC Conformant** is enabled.
140 | - Under **Grant Types**:
141 | - Ensure that **Implicit**, **Authorization Code**, and **Client Credentials** are enabled.
142 | - You may also want to enable **Refresh Token**.
143 |
144 |
145 |
146 | ### Configure the Plugin
147 |
148 | Upon activating the Auth0 plugin, you will find a new "Auth0" section in the sidebar of your WordPress Dashboard. This section enables you to configure the plugin in a variety of ways.
149 |
150 | For the plugin to operate, at a minimum, you will need to configure the Domain, Client ID, and Client Secret fields. These are available from the Auth0 Application you created in the previous step. Once configured, select the "Enable Authentication" option to have the plugin begin handling authentication for you.
151 |
152 | We recommend testing on a staging/development site using a separate Auth0 Application before putting the plugin live on your production site.
153 |
154 | ### Configure WordPress
155 |
156 | #### Plugin Database Tables
157 |
158 | The plugin uses dedicated database tables to guarantee high performance. When the plugin is activated, it will use the database credentials you have configured for WordPress to create these tables.
159 |
160 | Please ensure your configured credentials have appropriate privileges to create new tables.
161 |
162 | #### Cron Configuration
163 |
164 | The plugin uses WordPress' [background task manager](https://developer.wordpress.org/plugins/cron/) to perform important periodic tasks. Proper synchronization between WordPress and Auth0 relies on this.
165 |
166 | By default, WordPress' task manager runs on every page load, which is inadvisable for production sites. For best performance and reliability, please ensure you have configured WordPress to use a [cron job](https://developer.wordpress.org/plugins/cron/hooking-wp-cron-into-the-system-task-scheduler/) to run these tasks periodically instead.
167 |
168 | ## SDK Usage
169 |
170 | The plugin is built on top of [Auth0-PHP](https://github.com/auth0/auth0-PHP) — Auth0's full-featured PHP SDK for Authentication and Management APIs.
171 |
172 | For custom WordPress development, please do not extend the plugin's classes themselves, as this is not supported. Nearly all of the plugin's APIs are considered `internal` and will change over time, most likely breaking any custom extension built upon them.
173 |
174 | Instead, please take advantage of the full PHP SDK that the plugin is built upon. You can use the plugin's `getSdk()` method to retrieve a configured instance of the SDK, ready for use. This method can be called from the plugin's global `wpAuth0()` helper, which returns the WordPress plugin itself.
175 |
176 | ```php
177 | getSdk(); // Returns an instanceof Auth0\SDK\Auth0
181 | ```
182 |
183 | Please direct questions about developing with the Auth0-PHP SDK to the [Auth0 Community](https://community.auth0.com), and issues or feature requests to [it's respective repository](https://github.com/auth0/auth0-PHP). Documentations and examples on working with the Auth0-PHP SDKs are also available from [its repository](https://github.com/auth0/auth0-PHP).
184 |
185 | ## Support Policy
186 |
187 | - Our PHP version support window mirrors the [PHP release support schedule](https://www.php.net/supported-versions.php). Our support for PHP versions ends when they stop receiving security fixes.
188 | - As Automattic's stated policy is "security patches are backported when possible, but this is not guaranteed," we only support [the latest release](https://wordpress.org/news/category/releases/) marked as ["actively supported"](https://endoflife.date/wordpress) by Automattic.
189 |
190 | | Plugin Version | WordPress Version | PHP Version | Support Ends |
191 | | -------------- | ----------------- | ----------- | ------------ |
192 | | 5 | 6 | 8.3 | Nov 2026 |
193 | | | | 8.2 | Dec 2025 |
194 | | | | 8.1 | Nov 2024 |
195 |
196 | Composer and WordPress do not offer upgrades to incompatible versions. Therefore, we regularly deprecate support within the plugin for PHP or WordPress versions that have reached end-of-life. These deprecations are not considered breaking changes and will not result in a major version bump.
197 |
198 | Sites running unsupported versions of PHP or WordPress will continue to function but will not receive updates until their environment is upgraded. For your security, please ensure your PHP runtime and WordPress remain up to date.
199 |
200 | ## Feedback
201 |
202 | ### Contributing
203 |
204 | We appreciate feedback and contribution to this repo! Before you get started, please see the following:
205 |
206 | - [Auth0's general contribution guidelines](https://github.com/auth0/open-source-template/blob/master/GENERAL-CONTRIBUTING.md)
207 | - [Auth0's code of conduct guidelines](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md)
208 |
209 | ### Raise an issue
210 |
211 | To provide feedback or report a bug, [please raise an issue on our issue tracker](https://github.com/auth0/wp-auth0/issues).
212 |
213 | ### Vulnerability Reporting
214 |
215 | Please do not report security vulnerabilities on the public GitHub issue tracker. The [Responsible Disclosure Program](https://auth0.com/whitehat) details the procedure for disclosing security issues.
216 |
217 | ---
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 | Auth0 is an easy-to-implement, adaptable authentication and authorization platform.
228 | To learn more checkout Why Auth0?
229 |
230 | This project is licensed under the MIT license. See the LICENSE file for more info.
231 |
--------------------------------------------------------------------------------
/src/Actions/Authentication.php:
--------------------------------------------------------------------------------
1 | |string>
24 | */
25 | protected array $registry = [
26 | 'init' => 'onInit',
27 | // 'rest_api_init' => 'onInit',
28 | // 'admin_init' => 'onInit',
29 | // 'shutdown' => 'onShutdown',
30 | 'send_headers' => 'onShutdown',
31 |
32 | 'auth_cookie_expiration' => ['onAuthCookieAssignExpiration', 3],
33 | 'auth_cookie_malformed' => ['onAuthCookieMalformed', 2],
34 | 'auth_cookie_expired' => 'onAuthCookieExpired',
35 | 'auth_cookie_bad_username' => 'onAuthCookieBadUsername',
36 | 'auth_cookie_bad_session_token' => 'onAuthCookieBadSessionToken',
37 | 'auth_cookie_bad_hash' => 'onAuthCookieBadHash',
38 |
39 | 'login_form_login' => 'onLogin',
40 | 'auth0_login_callback' => 'onLogin',
41 |
42 | 'login_form_logout' => 'onLogout',
43 | 'auth0_logout' => 'onLogout',
44 | 'auth0_token_exchange_failed' => 'onExchangeFailed',
45 |
46 | 'before_signup_header' => 'onRegistration',
47 |
48 | 'edit_user_created_user' => ['onCreatedUser', 2],
49 | 'deleted_user' => 'onDeletedUser',
50 | 'profile_update' => ['onUpdatedUser', 2],
51 | ];
52 |
53 | public function createAccountConnection(WP_User $wpUser, string $connection): void
54 | {
55 | $network = get_current_network_id();
56 | $blog = get_current_blog_id();
57 | $cacheKey = 'auth0_account_' . hash('sha256', $connection . '::' . $network . '!' . $blog);
58 |
59 | $found = false;
60 | wp_cache_get($cacheKey, '', false, $found);
61 |
62 | if (! $found && false === get_transient($cacheKey)) {
63 | $database = $this->getPlugin()->database();
64 | $table = $database->getTableName(Database::CONST_TABLE_ACCOUNTS);
65 | $found = null;
66 |
67 | $this->prepDatabase(Database::CONST_TABLE_ACCOUNTS);
68 |
69 | $found = $database->selectRow('*', $table, 'WHERE `user` = %d AND `site` = %d AND `blog` = %d AND `auth0` = "%s" LIMIT 1', [$wpUser->ID, $network, $blog, $connection]);
70 |
71 | if (null === $found) {
72 | set_transient($cacheKey, $wpUser->ID, 120);
73 | wp_cache_set($cacheKey, $found, 120);
74 |
75 | $database->insertRow($table, [
76 | 'user' => $wpUser->ID,
77 | 'site' => $network,
78 | 'blog' => $blog,
79 | 'auth0' => $connection,
80 | ], [
81 | '%d',
82 | '%d',
83 | '%d',
84 | '%s',
85 | ]);
86 | }
87 | }
88 | }
89 |
90 | public function deleteAccountConnections(int $userId): ?array
91 | {
92 | $database = $this->getPlugin()->database();
93 | $table = $database->getTableName(Database::CONST_TABLE_ACCOUNTS);
94 | $network = get_current_network_id();
95 | $blog = get_current_blog_id();
96 |
97 | $this->prepDatabase(Database::CONST_TABLE_ACCOUNTS);
98 |
99 | $connections = $database->selectResults('auth0', $table, 'WHERE `site` = %d AND `blog` = %d AND `user` = "%s" LIMIT 1', [$network, $blog, $userId]);
100 |
101 | if ($connections) {
102 | $database->deleteRow($table, ['user' => $userId, 'site' => $network, 'blog' => $blog], ['%d', '%s', '%s']);
103 | wp_cache_flush();
104 |
105 | return $connections;
106 | }
107 |
108 | return null;
109 | }
110 |
111 | public function getAccountByConnection(string $connection): ?WP_User
112 | {
113 | $network = get_current_network_id();
114 | $blog = get_current_blog_id();
115 | $cacheKey = 'auth0_account_' . hash('sha256', $connection . '::' . $network . '!' . $blog);
116 |
117 | $found = false;
118 | $user = wp_cache_get($cacheKey, '', false, $found);
119 |
120 | if ($found) {
121 | $found = $user;
122 | }
123 |
124 | if (! $found) {
125 | $found = get_transient($cacheKey);
126 |
127 | if (false === $found) {
128 | $database = $this->getPlugin()->database();
129 | $table = $database->getTableName(Database::CONST_TABLE_ACCOUNTS);
130 |
131 | $this->prepDatabase(Database::CONST_TABLE_ACCOUNTS);
132 | $found = $database->selectRow('user', $table, 'WHERE `site` = %d AND `blog` = %d AND `auth0` = "%s" LIMIT 1', [$network, $blog, $connection]);
133 |
134 | if (null === $found) {
135 | return null;
136 | }
137 |
138 | $found = $found->user;
139 | }
140 | }
141 |
142 | if ($found) {
143 | set_transient($cacheKey, $found, 120);
144 | wp_cache_set($cacheKey, $found, 120);
145 |
146 | $user = get_user_by('ID', $found);
147 | }
148 |
149 | if (false === $user) {
150 | return null;
151 | }
152 |
153 | return $user;
154 | }
155 |
156 | public function getAccountConnections(int $userId): ?array
157 | {
158 | $database = $this->getPlugin()->database();
159 | $table = $database->getTableName(Database::CONST_TABLE_ACCOUNTS);
160 | $network = get_current_network_id();
161 | $blog = get_current_blog_id();
162 |
163 | $this->prepDatabase(Database::CONST_TABLE_ACCOUNTS);
164 |
165 | $connections = $database->selectResults('auth0', $table, 'WHERE `site` = %d AND `blog` = %d AND `user` = "%s" LIMIT 1', [$network, $blog, $userId]);
166 |
167 | if ($connections) {
168 | return $connections;
169 | }
170 |
171 | return null;
172 | }
173 |
174 | /**
175 | * Fires when 'auth_cookie_expiration' is triggered by WordPress.
176 | *
177 | * @link https://developer.wordpress.org/reference/hooks/auth_cookie_expiration/
178 | *
179 | * @param int $length
180 | * @param int $user_id
181 | * @param bool $remember
182 | */
183 | public function onAuthCookieAssignExpiration(int $length, int $user_id, bool $remember): int
184 | {
185 | if ($remember) {
186 | $ttl = $this->getPlugin()->getOptionInteger('cookies', 'ttl') ?? 0;
187 |
188 | return $ttl > 0 ? $ttl : $length;
189 | }
190 |
191 | return $length;
192 | }
193 |
194 | /**
195 | * Fires when 'auth_cookie_bad_hash' is triggered by WordPress.
196 | *
197 | * @link https://developer.wordpress.org/reference/hooks/auth_cookie_bad_hash/
198 | *
199 | * @param array $cookieElements
200 | */
201 | public function onAuthCookieBadHash(array $cookieElements): void
202 | {
203 | $this->getSdk()
204 | ->clear();
205 | }
206 |
207 | /**
208 | * Fires when 'auth_cookie_bad_session_token' is triggered by WordPress.
209 | *
210 | * @link https://developer.wordpress.org/reference/hooks/auth_cookie_bad_session_token/
211 | *
212 | * @param array $cookieElements
213 | */
214 | public function onAuthCookieBadSessionToken(array $cookieElements): void
215 | {
216 | $this->getSdk()
217 | ->clear();
218 | }
219 |
220 | /**
221 | * Fires when 'auth_cookie_bad_username' is triggered by WordPress.
222 | *
223 | * @link https://developer.wordpress.org/reference/hooks/auth_cookie_bad_username/
224 | *
225 | * @param array $cookieElements
226 | */
227 | public function onAuthCookieBadUsername(array $cookieElements): void
228 | {
229 | $this->getSdk()
230 | ->clear();
231 | }
232 |
233 | /**
234 | * Fires when 'auth_cookie_expired' is triggered by WordPress.
235 | *
236 | * @link https://developer.wordpress.org/reference/hooks/auth_cookie_expired/
237 | *
238 | * @param array $cookieElements
239 | */
240 | public function onAuthCookieExpired(array $cookieElements): void
241 | {
242 | $this->getSdk()
243 | ->clear();
244 | }
245 |
246 | /**
247 | * Fires when 'auth_cookie_malformed' is triggered by WordPress.
248 | *
249 | * @link https://developer.wordpress.org/reference/hooks/auth_cookie_malformed/
250 | *
251 | * @param string $cookie
252 | * @param ?string $scheme
253 | */
254 | public function onAuthCookieMalformed(string $cookie, ?string $scheme = null): void
255 | {
256 | if ('' === $cookie) {
257 | return;
258 | }
259 |
260 | $this->getSdk()
261 | ->clear();
262 | }
263 |
264 | /**
265 | * Note that this ONLY fires for users created via WordPress' UI, like the "Add New" button from the Admin -> Users page.
266 | *
267 | * @param mixed $userId
268 | * @param null|mixed $notify
269 | */
270 | public function onCreatedUser($userId, $notify = null): void
271 | {
272 | if (! is_int($userId)) {
273 | return;
274 | }
275 |
276 | $network = get_current_network_id();
277 | $blog = get_current_blog_id();
278 | $database = $this->getPlugin()->database();
279 | $table = $database->getTableName(Database::CONST_TABLE_SYNC);
280 |
281 | $this->prepDatabase(Database::CONST_TABLE_SYNC);
282 |
283 | $payload = json_encode([
284 | 'event' => 'wp_user_created',
285 | 'user' => $userId,
286 | ], JSON_THROW_ON_ERROR);
287 | $checksum = hash('sha256', $payload);
288 |
289 | $dupe = $database->selectRow('id', $table, 'WHERE `hashsum` = "%s";', [$checksum]);
290 |
291 | if (! $dupe) {
292 | $database->insertRow($table, [
293 | 'site' => $network,
294 | 'blog' => $blog,
295 | 'created' => time(),
296 | 'payload' => $payload,
297 | 'hashsum' => hash('sha256', $payload),
298 | 'locked' => 0,
299 | ], [
300 | '%d',
301 | '%d',
302 | '%d',
303 | '%s',
304 | '%s',
305 | '%d',
306 | ]);
307 | }
308 | }
309 |
310 | public function onDeletedUser($userId): void
311 | {
312 | $connections = $this->deleteAccountConnections($userId);
313 |
314 | if (null !== $connections && [] !== $connections) {
315 | $network = get_current_network_id();
316 | $blog = get_current_blog_id();
317 | $database = $this->getPlugin()->database();
318 | $table = $database->getTableName(Database::CONST_TABLE_SYNC);
319 |
320 | $this->prepDatabase(Database::CONST_TABLE_SYNC);
321 |
322 | foreach ($connections as $connection) {
323 | $payload = json_encode([
324 | 'event' => 'wp_user_deleted',
325 | 'user' => $userId,
326 | 'connection' => $connection->auth0,
327 | ], JSON_THROW_ON_ERROR);
328 | $checksum = hash('sha256', $payload);
329 |
330 | $dupe = $database->selectRow('id', $table, 'WHERE `hashsum` = "%s";', [$checksum]);
331 |
332 | if (! $dupe) {
333 | $database->insertRow($table, [
334 | 'site' => $network,
335 | 'blog' => $blog,
336 | 'created' => time(),
337 | 'payload' => $payload,
338 | 'hashsum' => hash('sha256', $payload),
339 | 'locked' => 0,
340 | ], [
341 | '%d',
342 | '%d',
343 | '%d',
344 | '%s',
345 | '%s',
346 | '%d',
347 | ]);
348 | }
349 | }
350 | }
351 | }
352 |
353 | public function onInit(): void
354 | {
355 | if (! $this->getPlugin()->isEnabled()) {
356 | return;
357 | }
358 |
359 | if (! $this->getPlugin()->isReady()) {
360 | return;
361 | }
362 |
363 | if (! is_user_logged_in()) {
364 | return;
365 | }
366 |
367 | $session = $this->getSdk()->getCredentials();
368 | $expired = $session?->accessTokenExpired ?? true;
369 |
370 | if (! $expired && (wp_is_json_request() || wp_is_rest_endpoint())) {
371 | return;
372 | }
373 |
374 | $wordpress = wp_get_current_user();
375 |
376 | // Paired sessions enforced
377 | if (2 !== $this->getPlugin()->getOption('authentication', 'pair_sessions', 0)) {
378 | // ... for all but admins?
379 | if (0 === $this->getPlugin()->getOption('authentication', 'pair_sessions', 0) && is_admin()) {
380 | return;
381 | }
382 |
383 | // Is an Auth0 session available?
384 | if (! is_object($session) && 0 !== $wordpress->ID) {
385 | wp_logout();
386 |
387 | return;
388 | }
389 |
390 | // Is an WP session available?
391 | if (is_object($session) && 0 === $wordpress->ID) {
392 | $this->getSdk()->clear();
393 |
394 | return;
395 | }
396 |
397 | if (is_object($session)) {
398 | // Verify the WordPress user signed in is linked to the Auth0 Connection 'sub'.
399 | $sub = $session->user['sub'] ?? null;
400 |
401 | if (null !== $sub) {
402 | $match = $this->getAccountByConnection($sub);
403 |
404 | if (! $match instanceof WP_User || $match->ID !== $wordpress->ID) {
405 | $this->getSdk()->clear();
406 | wp_logout();
407 |
408 | return;
409 | }
410 | }
411 |
412 | // Verify that the Auth0 token cookie has not expired
413 | if ($expired && 'true' === $this->getPlugin()->getOption('sessions', 'refresh_tokens')) {
414 | try {
415 | // Token has expired, attempt to refresh it.
416 | $this->getSdk()->renew();
417 |
418 | return;
419 | } catch (StateException) {
420 | // Refresh failed.
421 | }
422 |
423 | // Invalidation authentication state.
424 | $this->getSdk()->clear();
425 | wp_logout();
426 |
427 | return;
428 | }
429 | }
430 | }
431 | }
432 |
433 | public function onLogin(): void
434 | {
435 | if (! $this->getPlugin()->isEnabled()) {
436 | return;
437 | }
438 |
439 | if (! $this->getPlugin()->isReady()) {
440 | return;
441 | }
442 |
443 | if (isset($_GET['auth0_fb'])) {
444 | $incomingFallbackRequest = Sanitize::string($_GET['auth0_fb']);
445 | $fallbackSecret = $this->getPlugin()->getOptionString('authentication', 'fallback_secret');
446 |
447 | if ($incomingFallbackRequest === $fallbackSecret) {
448 | return;
449 | }
450 |
451 | // Ignore invalid requests; continue as normal.
452 | }
453 |
454 | if (isset($_GET['auth0_bcl'], $_POST['logout_token'])) {
455 | $incomingBackchannelLogoutRequest = Sanitize::string($_GET['auth0_bcl']);
456 | $backchannelLogoutSecret = $this->getPlugin()->getOptionString('authentication', 'backchannel_logout_secret');
457 |
458 | if ($incomingBackchannelLogoutRequest === $backchannelLogoutSecret) {
459 | $logoutToken = Sanitize::string($_POST['logout_token']);
460 |
461 | try {
462 | $this->getSdk()->handleBackchannelLogout($logoutToken);
463 | exit();
464 | } catch (Throwable) {
465 | }
466 | }
467 |
468 | // Ignore invalid requests; continue as normal.
469 | }
470 |
471 | // Don't allow caching of this route
472 | nocache_headers();
473 |
474 | // Check if authentication flow parameters are present (?code and ?state)
475 | $code = $this->getSdk()->getRequestParameter('code');
476 | $state = $this->getSdk()->getRequestParameter('state');
477 | $exchangeParameters = null !== $code && null !== $state;
478 |
479 | // Check if authentication flow error parameter is present (?error)
480 | $error = $this->getSdk()
481 | ->getRequestParameter('error');
482 |
483 | // Are token exchange parameters present?
484 | if ($exchangeParameters) {
485 | try {
486 | // Attempt completion of the authentication flow using
487 | $this->getSdk()
488 | ->exchange(
489 | code: sanitize_text_field($code),
490 | state: sanitize_text_field($state),
491 | );
492 | } catch (Throwable $throwable) {
493 | // Exchange failed; throw an error
494 | try {
495 | error_log($throwable->getMessage());
496 | } catch (Throwable) {
497 | }
498 |
499 | do_action('auth0_token_exchange_failed', $throwable);
500 | return;
501 | }
502 |
503 | $session = $this->getSdk()
504 | ->getCredentials();
505 |
506 | // Do we indeed have a session now?
507 | if (null !== $session) {
508 | $sub = sanitize_text_field($session->user['sub'] ?? '');
509 | $email = sanitize_email($session->user['email'] ?? '');
510 | $verified = $session->user['email_verified'] ?? null;
511 |
512 | if ('' === $email) {
513 | $email = null;
514 | $verified = null;
515 | }
516 |
517 | $wpUser = $this->resolveIdentity(sub: $sub, email: $email, verified: $verified);
518 |
519 | if ($wpUser instanceof WP_User) {
520 | if ('' !== $sub) {
521 | $this->createAccountConnection($wpUser, $sub);
522 | }
523 |
524 | if (null !== $email && true === $verified && $email !== $wpUser->user_email) {
525 | $this->removeAction('profile_update');
526 | $this->setAccountEmail($wpUser, $email);
527 | $this->addAction('profile_update');
528 | }
529 |
530 | wp_set_current_user($wpUser->ID);
531 | wp_set_auth_cookie($wpUser->ID, true);
532 | do_action('wp_login', $wpUser->user_login, $wpUser);
533 | wp_redirect('/');
534 | exit;
535 | }
536 | }
537 | }
538 |
539 | if (null !== $error) {
540 | wp_redirect('/');
541 | exit;
542 | }
543 |
544 | if ($exchangeParameters && null === $error && (0 !== wp_get_current_user()->ID || null !== $this->getSdk()->getCredentials())) {
545 | wp_redirect('/');
546 | exit;
547 | }
548 |
549 | wp_redirect($this->getSdk()->login());
550 | exit;
551 | }
552 |
553 | public function onExchangeFailed(Throwable $_)
554 | {
555 | // Custom hook ('auth0_token_exchange_failed') to register when token exchange fails.
556 | wp_redirect('/');
557 | exit;
558 | }
559 |
560 | public function onLogout(): never
561 | {
562 | wp_logout();
563 | wp_redirect($this->getSdk()->logout(get_site_url()));
564 | exit;
565 | }
566 |
567 | public function onRegistration(): never
568 | {
569 | // Block registration attempts from the API?
570 | exit;
571 | }
572 |
573 | public function onShutdown(): void
574 | {
575 | if (! is_user_logged_in() || wp_is_json_request() || wp_is_rest_endpoint()) {
576 | return;
577 | }
578 |
579 | if ('false' !== $this->getPlugin()->getOption('sessions', 'rolling_sessions')) {
580 | $store = $this->getSdk()->configuration()->getSessionStorage();
581 |
582 | /**
583 | * @var CookieStore $store
584 | */
585 | $store->setState(true);
586 |
587 | wp_set_auth_cookie(get_current_user_id(), true);
588 | }
589 | }
590 |
591 | public function onUpdatedUser($userId, $previousUserData = null): void
592 | {
593 | if (! is_int($userId)) {
594 | return;
595 | }
596 |
597 | $network = get_current_network_id();
598 | $blog = get_current_blog_id();
599 | $database = $this->getPlugin()->database();
600 | $table = $database->getTableName(Database::CONST_TABLE_SYNC);
601 |
602 | $this->prepDatabase(Database::CONST_TABLE_SYNC);
603 |
604 | $payload = json_encode([
605 | 'event' => 'wp_user_updated',
606 | 'user' => $userId,
607 | ], JSON_THROW_ON_ERROR);
608 | $checksum = hash('sha256', $payload);
609 |
610 | $dupe = $database->selectRow('id', $table, 'WHERE `hashsum` = "%s";', [$checksum]);
611 |
612 | if (! $dupe) {
613 | $database->insertRow($table, [
614 | 'site' => $network,
615 | 'blog' => $blog,
616 | 'created' => time(),
617 | 'payload' => $payload,
618 | 'hashsum' => hash('sha256', $payload),
619 | 'locked' => 0,
620 | ], [
621 | '%d',
622 | '%d',
623 | '%d',
624 | '%s',
625 | '%s',
626 | '%d',
627 | ]);
628 | }
629 | }
630 |
631 | public function setAccountEmail(WP_User $wpUser, string $email): ?WP_User
632 | {
633 | if ($wpUser->user_email !== $email) {
634 | $wpUser->user_email = $email;
635 | $status = wp_update_user($wpUser);
636 |
637 | if ($status instanceof WP_Error) {
638 | return null;
639 | }
640 | }
641 |
642 | return $wpUser;
643 | }
644 |
645 | private function prepDatabase(string $databaseName)
646 | {
647 | $cacheKey = 'auth0_db_check_' . hash('sha256', $databaseName);
648 |
649 | $found = false;
650 | wp_cache_get($cacheKey, '', false, $found);
651 |
652 | if (! $found && false === get_transient($cacheKey)) {
653 | set_transient($cacheKey, true, 1800);
654 | wp_cache_set($cacheKey, true, 1800);
655 |
656 | return $this->getPlugin()->database()->createTable($databaseName);
657 | }
658 | }
659 |
660 | private function resolveIdentity(
661 | ?string $sub = null,
662 | ?string $email = null,
663 | ?bool $verified = null,
664 | ): ?WP_User {
665 | $email = sanitize_email(filter_var($email ?? '', FILTER_SANITIZE_EMAIL, FILTER_NULL_ON_FAILURE) ?? '');
666 |
667 | if (null !== $sub) {
668 | $sub = sanitize_text_field($sub);
669 | $found = $this->getAccountByConnection($sub);
670 |
671 | if ($found instanceof WP_User) {
672 | return $found;
673 | }
674 | }
675 |
676 | // If an email is not marked as verified by the connection, dismiss it.
677 | if (true !== $verified || '' === $email) {
678 | $email = null;
679 | }
680 |
681 | if (null !== $email) {
682 | $found = get_user_by('email', $email);
683 |
684 | if ($found instanceof WP_User) {
685 | // Are we allowed to match loosely by email?
686 | if ('strict' !== $this->getPlugin()->getOption('accounts', 'matching')) {
687 | return $found;
688 | }
689 |
690 | // Are administrators allowed to bypass the check as a failsafe for configuration issues?
691 | if (0 === $this->getPlugin()->getOption('authentication', 'pair_sessions', 0)) {
692 | $roles = $found->roles;
693 |
694 | if (in_array('administrator', $roles, true)) {
695 | return $found;
696 | }
697 | }
698 |
699 | return null;
700 | }
701 | }
702 |
703 | if ('create' === $this->getPlugin()->getOption('accounts', 'missing')) {
704 | $username = (null !== $email) ? explode('@', $email, 2)[0] : explode('|', $sub ?? '', 2)[1];
705 | $user = wp_create_user($username, wp_generate_password(random_int(12, 123), true, true), $email ?? '');
706 |
707 | if (! $user instanceof WP_Error) {
708 | $user = get_user_by('ID', $user);
709 |
710 | if ($user instanceof WP_User) {
711 | $role = $this->getPlugin()->getOptionString('accounts', 'default_role');
712 |
713 | if (is_string($role) && ! in_array($role, $user->roles, true)) {
714 | $user->set_role($role);
715 | wp_update_user($user);
716 | }
717 |
718 | return $user;
719 | }
720 | }
721 | }
722 |
723 | return null;
724 | }
725 | }
726 |
--------------------------------------------------------------------------------
/rector.php:
--------------------------------------------------------------------------------
1 | paths([
236 | __DIR__ . '/config',
237 | __DIR__ . '/src',
238 | ]);
239 |
240 | $rectorConfig->ruleWithConfiguration(
241 | RenameFunctionRector::class,
242 | [
243 | 'chop' => 'rtrim',
244 | 'doubleval' => 'floatval',
245 | 'fputs' => 'fwrite',
246 | 'gzputs' => 'gzwrites',
247 | 'ini_alter' => 'ini_set',
248 | 'is_double' => 'is_float',
249 | 'is_integer' => 'is_int',
250 | 'is_long' => 'is_int',
251 | 'is_real' => 'is_float',
252 | 'is_writeable' => 'is_writable',
253 | 'join' => 'implode',
254 | 'key_exists' => 'array_key_exists',
255 | 'mbstrcut' => 'mb_strcut',
256 | 'mbstrlen' => 'mb_strlen',
257 | 'mbstrpos' => 'mb_strpos',
258 | 'mbstrrpos' => 'mb_strrpos',
259 | 'mbsubstr' => 'mb_substr',
260 | 'pos' => 'current',
261 | 'sizeof' => 'count',
262 | 'split' => 'explode',
263 | 'strchr' => 'strstr',
264 | ],
265 | );
266 |
267 | $rectorConfig->ruleWithConfiguration(
268 | StaticCallToFuncCallRector::class,
269 | [
270 | new StaticCallToFuncCall('Nette\\Utils\\Strings', 'contains', 'str_contains'),
271 | new StaticCallToFuncCall('Nette\\Utils\\Strings', 'endsWith', 'str_ends_with'),
272 | new StaticCallToFuncCall('Nette\\Utils\\Strings', 'startsWith', 'str_starts_with'),
273 | ],
274 | );
275 |
276 | $rectorConfig->ruleWithConfiguration(
277 | ArgumentAdderRector::class,
278 | [new ArgumentAdder('Nette\\Utils\\Strings', 'replace', 2, 'replacement', '')],
279 | );
280 |
281 | $rectorConfig->ruleWithConfiguration(
282 | RenameFunctionRector::class,
283 | [
284 | 'pg_clientencoding' => 'pg_client_encoding',
285 | 'pg_cmdtuples' => 'pg_affected_rows',
286 | 'pg_errormessage' => 'pg_last_error',
287 | 'pg_fieldisnull' => 'pg_field_is_null',
288 | 'pg_fieldname' => 'pg_field_name',
289 | 'pg_fieldnum' => 'pg_field_num',
290 | 'pg_fieldprtlen' => 'pg_field_prtlen',
291 | 'pg_fieldsize' => 'pg_field_size',
292 | 'pg_fieldtype' => 'pg_field_type',
293 | 'pg_freeresult' => 'pg_free_result',
294 | 'pg_getlastoid' => 'pg_last_oid',
295 | 'pg_loclose' => 'pg_lo_close',
296 | 'pg_locreate' => 'pg_lo_create',
297 | 'pg_loexport' => 'pg_lo_export',
298 | 'pg_loimport' => 'pg_lo_import',
299 | 'pg_loopen' => 'pg_lo_open',
300 | 'pg_loread' => 'pg_lo_read',
301 | 'pg_loreadall' => 'pg_lo_read_all',
302 | 'pg_lounlink' => 'pg_lo_unlink',
303 | 'pg_lowrite' => 'pg_lo_write',
304 | 'pg_numfields' => 'pg_num_fields',
305 | 'pg_numrows' => 'pg_num_rows',
306 | 'pg_result' => 'pg_fetch_result',
307 | 'pg_setclientencoding' => 'pg_set_client_encoding'
308 | ],
309 | );
310 |
311 | $rectorConfig->ruleWithConfiguration(
312 | FunctionArgumentDefaultValueReplacerRector::class,
313 | [
314 | new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, 'gte', 'ge'),
315 | new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, 'lte', 'le'),
316 | new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, '', '!='),
317 | new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, '!', '!='),
318 | new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, 'g', 'gt'),
319 | new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, 'l', 'lt'),
320 | new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, 'gte', 'ge'),
321 | new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, 'lte', 'le'),
322 | new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, 'n', 'ne')
323 | ],
324 | );
325 |
326 | $rectorConfig->ruleWithConfiguration(
327 | FuncCallToConstFetchRector::class,
328 | [
329 | 'php_sapi_name' => 'PHP_SAPI',
330 | 'pi' => 'M_PI'
331 | ],
332 | );
333 |
334 | $rectorConfig->rules([
335 | AbsolutizeRequireAndIncludePathRector::class,
336 | // ActionInjectionToConstructorInjectionRector::class,
337 | AddArrayDefaultToArrayPropertyRector::class,
338 | AddArrowFunctionReturnTypeRector::class,
339 | AddClosureReturnTypeRector::class,
340 | // AddFalseDefaultToBoolPropertyRector::class,
341 | AddMethodCallBasedStrictParamTypeRector::class,
342 | AddParamBasedOnParentClassMethodRector::class,
343 | AddParamTypeBasedOnPHPUnitDataProviderRector::class,
344 | AddParamTypeSplFixedArrayRector::class,
345 | // AddPregQuoteDelimiterRector::class,
346 | AddReturnTypeDeclarationBasedOnParentClassMethodRector::class,
347 | AddReturnTypeDeclarationFromYieldsRector::class,
348 | AddVoidReturnTypeWhereNoReturnRector::class,
349 | AndAssignsToSeparateLinesRector::class,
350 | ArrayKeyExistsTernaryThenValueToCoalescingRector::class,
351 | // ArrayKeysAndInArrayToArrayKeyExistsRector::class,
352 | ArrayMergeOfNonArraysToSimpleArrayRector::class,
353 | // ArrayShapeFromConstantArrayReturnRector::class,
354 | BinarySwitchToIfElseRector::class,
355 | BooleanNotIdenticalToNotIdenticalRector::class,
356 | BoolvalToTypeCastRector::class,
357 | CallableThisArrayToAnonymousFunctionRector::class,
358 | CallUserFuncArrayToVariadicRector::class,
359 | CallUserFuncToMethodCallRector::class,
360 | CallUserFuncWithArrowFunctionToInlineRector::class,
361 | CatchExceptionNameMatchingTypeRector::class,
362 | ChangeArrayPushToArrayAssignRector::class,
363 | // ChangeGlobalVariablesToPropertiesRector::class,
364 | ChangeIfElseValueAssignToEarlyReturnRector::class,
365 | ChangeNestedForeachIfsToEarlyContinueRector::class,
366 | ChangeNestedIfsToEarlyReturnRector::class,
367 | ChangeOrIfContinueToMultiContinueRector::class,
368 | // ChangeReadOnlyPropertyWithDefaultValueToConstantRector::class,
369 | // ChangeReadOnlyVariableWithDefaultValueToConstantRector::class,
370 | ChangeSwitchToMatchRector::class,
371 | ClassOnObjectRector::class,
372 | ClassOnThisVariableObjectRector::class,
373 | ClassPropertyAssignToConstructorPromotionRector::class,
374 | CombinedAssignRector::class,
375 | CombineIfRector::class,
376 | CommonNotEqualRector::class,
377 | CompactToVariablesRector::class,
378 | CompleteDynamicPropertiesRector::class,
379 | ConsecutiveNullCompareReturnsToNullCoalesceQueueRector::class,
380 | ConsistentImplodeRector::class,
381 | // ConsistentPregDelimiterRector::class,
382 | CountArrayToEmptyArrayComparisonRector::class,
383 | EmptyOnNullableObjectToInstanceOfRector::class,
384 | EncapsedStringsToSprintfRector::class,
385 | ExplicitBoolCompareRector::class,
386 | // ExplicitMethodCallOverMagicGetSetRector::class,
387 | FinalizeClassesWithoutChildrenRector::class,
388 | FinalPrivateToPrivateVisibilityRector::class,
389 | FlipTypeControlToUseExclusiveTypeRector::class,
390 | FloatvalToTypeCastRector::class,
391 | ForeachItemsAssignToEmptyArrayToAssignRector::class,
392 | ForeachToInArrayRector::class,
393 | ForRepeatedCountToOwnVariableRector::class,
394 | // ForToForeachRector::class,
395 | FuncGetArgsToVariadicParamRector::class,
396 | GetClassToInstanceOfRector::class,
397 | GetDebugTypeRector::class,
398 | InlineArrayReturnAssignRector::class,
399 | InlineConstructorDefaultToPropertyRector::class,
400 | InlineIfToExplicitIfRector::class,
401 | InlineIsAInstanceOfRector::class,
402 | IntvalToTypeCastRector::class,
403 | IsAWithStringWithThirdArgumentRector::class,
404 | IssetOnPropertyObjectToPropertyExistsRector::class,
405 | JoinStringConcatRector::class,
406 | LogicalToBooleanRector::class,
407 | MakeInheritedMethodVisibilitySameAsParentRector::class,
408 | // MultipleClassFileToPsr4ClassesRector::class,
409 | // NarrowUnionTypeDocRector::class,
410 | NewlineBeforeNewAssignSetRector::class,
411 | NewStaticToNewSelfRector::class,
412 | // NormalizeNamespaceByPSR4ComposerAutoloadRector::class,
413 | NullableCompareToNullRector::class,
414 | OptionalParametersAfterRequiredRector::class,
415 | // ParamAnnotationIncorrectNullableRector::class,
416 | ParamTypeByMethodCallTypeRector::class,
417 | ParamTypeByParentCallTypeRector::class,
418 | ParamTypeFromStrictTypedPropertyRector::class,
419 | // Php8ResourceReturnToObjectRector::class,
420 | PostIncDecToPreIncDecRector::class,
421 | PrivatizeFinalClassMethodRector::class,
422 | PrivatizeFinalClassPropertyRector::class,
423 | PropertyTypeFromStrictSetterGetterRector::class,
424 | RemoveAlwaysElseRector::class,
425 | // RemoveAlwaysTrueConditionSetInConstructorRector::class,
426 | RemoveAndTrueRector::class,
427 | RemoveDeadConditionAboveReturnRector::class,
428 | RemoveDeadContinueRector::class,
429 | RemoveDeadIfForeachForRector::class,
430 | RemoveDeadLoopRector::class,
431 | RemoveDeadReturnRector::class,
432 | RemoveDeadStmtRector::class,
433 | RemoveDeadTryCatchRector::class,
434 | RemoveDeadZeroAndOneOperationRector::class,
435 | // RemoveDelegatingParentCallRector::class,
436 | RemoveDoubleAssignRector::class,
437 | // RemoveDoubleUnderscoreInMethodNameRector::class,
438 | RemoveDuplicatedArrayKeyRector::class,
439 | RemoveDuplicatedCaseInSwitchRector::class,
440 | // RemoveDuplicatedIfReturnRector::class,
441 | // RemoveDuplicatedInstanceOfRector::class,
442 | RemoveEmptyClassMethodRector::class,
443 | // RemoveEmptyMethodCallRector::class,
444 | // RemoveEmptyTestMethodRector::class,
445 | RemoveExtraParametersRector::class,
446 | RemoveFinalFromConstRector::class,
447 | RemoveJustPropertyFetchForAssignRector::class,
448 | // RemoveJustVariableAssignRector::class,
449 | // RemoveLastReturnRector::class,
450 | // RemoveNonExistingVarAnnotationRector::class,
451 | RemoveNullPropertyInitializationRector::class,
452 | RemoveParentCallWithoutParentRector::class,
453 | RemoveSoleValueSprintfRector::class,
454 | RemoveUnreachableStatementRector::class,
455 | RemoveUnusedConstructorParamRector::class,
456 | RemoveUnusedForeachKeyRector::class,
457 | RemoveUnusedNonEmptyArrayBeforeForeachRector::class,
458 | RemoveUnusedPrivateClassConstantRector::class,
459 | RemoveUnusedPrivateMethodParameterRector::class,
460 | RemoveUnusedPrivatePropertyRector::class,
461 | RemoveUnusedPromotedPropertyRector::class,
462 | RemoveUnusedVariableAssignRector::class,
463 | RemoveUnusedVariableInCatchRector::class,
464 | RemoveUselessReturnTagRector::class,
465 | RemoveUselessVarTagRector::class,
466 | RenameForeachValueVariableToMatchExprVariableRector::class,
467 | RenameForeachValueVariableToMatchMethodCallReturnTypeRector::class,
468 | ReplaceMultipleBooleanNotRector::class,
469 | ReturnAnnotationIncorrectNullableRector::class,
470 | // ReturnBinaryAndToEarlyReturnRector::class,
471 | ReturnBinaryOrToEarlyReturnRector::class,
472 | ReturnEarlyIfVariableRector::class,
473 | ReturnNeverTypeRector::class,
474 | ReturnTypeFromReturnDirectArrayRector::class,
475 | ReturnTypeFromReturnNewRector::class,
476 | ReturnTypeFromStrictBoolReturnExprRector::class,
477 | ReturnTypeFromStrictConstantReturnRector::class,
478 | ReturnTypeFromStrictNativeCallRector::class,
479 | ReturnTypeFromStrictNewArrayRector::class,
480 | ReturnTypeFromStrictScalarReturnExprRector::class,
481 | ReturnTypeFromStrictTernaryRector::class,
482 | ReturnTypeFromStrictTypedCallRector::class,
483 | ReturnTypeFromStrictTypedPropertyRector::class,
484 | SeparateMultiUseImportsRector::class,
485 | SetStateToStaticRector::class,
486 | SetTypeToCastRector::class,
487 | ShortenElseIfRector::class,
488 | SimplifyArraySearchRector::class,
489 | SimplifyBoolIdenticalTrueRector::class,
490 | SimplifyConditionsRector::class,
491 | SimplifyDeMorganBinaryRector::class,
492 | SimplifyEmptyArrayCheckRector::class,
493 | SimplifyEmptyCheckOnEmptyArrayRector::class,
494 | // SimplifyForeachToArrayFilterRector::class,
495 | SimplifyForeachToCoalescingRector::class,
496 | SimplifyFuncGetArgsCountRector::class,
497 | SimplifyIfElseToTernaryRector::class,
498 | SimplifyIfElseWithSameContentRector::class,
499 | // SimplifyIfExactValueReturnValueRector::class,
500 | SimplifyIfNotNullReturnRector::class,
501 | SimplifyIfNullableReturnRector::class,
502 | SimplifyIfReturnBoolRector::class,
503 | SimplifyInArrayValuesRector::class,
504 | SimplifyMirrorAssignRector::class,
505 | SimplifyRegexPatternRector::class,
506 | SimplifyStrposLowerRector::class,
507 | SimplifyTautologyTernaryRector::class,
508 | // SimplifyUselessLastVariableAssignRector::class,
509 | SimplifyUselessVariableRector::class,
510 | SingleInArrayToCompareRector::class,
511 | SingularSwitchToIfRector::class,
512 | SplitDoubleAssignRector::class,
513 | SplitGroupedClassConstantsRector::class,
514 | SplitGroupedPropertiesRector::class,
515 | // SplitListAssignToSeparateLineRector::class,
516 | StaticArrowFunctionRector::class,
517 | StaticClosureRector::class,
518 | StrContainsRector::class,
519 | StrEndsWithRector::class,
520 | StrictArraySearchRector::class,
521 | StringableForToStringRector::class,
522 | StrlenZeroToIdenticalEmptyStringRector::class,
523 | StrStartsWithRector::class,
524 | StrvalToTypeCastRector::class,
525 | SwitchNegatedTernaryRector::class,
526 | SymplifyQuoteEscapeRector::class,
527 | TernaryConditionVariableAssignmentRector::class,
528 | TernaryEmptyArrayArrayDimFetchToCoalesceRector::class,
529 | TernaryFalseExpressionToIfRector::class,
530 | TernaryToBooleanOrFalseToBooleanAndRector::class,
531 | ThrowWithPreviousExceptionRector::class,
532 | // TokenGetAllToObjectRector::class,
533 | TypedPropertyFromAssignsRector::class,
534 | TypedPropertyFromStrictConstructorRector::class,
535 | TypedPropertyFromStrictGetterMethodReturnTypeRector::class,
536 | TypedPropertyFromStrictSetUpRector::class,
537 | UnnecessaryTernaryExpressionRector::class,
538 | UnSpreadOperatorRector::class,
539 | UnusedForeachValueToArrayKeysRector::class,
540 | UnwrapFutureCompatibleIfPhpVersionRector::class,
541 | UnwrapSprintfOneArgumentRector::class,
542 | UseClassKeywordForClassNameResolutionRector::class,
543 | UseIdenticalOverEqualWithSameTypeRector::class,
544 | UseIncrementAssignRector::class,
545 | // VarAnnotationIncorrectNullableRector::class,
546 | // VarConstantCommentRector::class,
547 | VarToPublicPropertyRector::class,
548 | VersionCompareFuncCallToConstantRector::class,
549 | WrapEncapsedVariableInCurlyBracesRector::class,
550 | ]);
551 | };
552 |
--------------------------------------------------------------------------------