25f14345 by Jeff Balicki

WPO365 | CUSTOM USER FIELDS

Signed-off-by: Jeff <jeff@gotenzing.com>
1 parent d7b2bbe7
1 === WPO365 | CUSTOM USER FIELDS ===
2 Contributors: wpo365
3 Tags: office 365, O365, Microsoft 365, avatar, user profile, Microsoft Graph
4 Requires at least: 5.0
5 Tested up to: 6.4
6 Stable tag: 25.1
7 Requires PHP: 5.6.40
8
9 == Description ==
10
11 Synchronize Azure AD user attributes e.g. department, job title etc. to WordPress user profiles (includes the PROFILE+ extension).
12
13 = Plugin Features =
14
15 - Synchronize Microsoft 365 / Azure AD profile fields e.g. job title, department or employee number to WordPress / BuddyPress.
16
17 = Prerequisites =
18
19 - The [WPO365 | LOGIN plugin](https://wordpress.org/plugins/wpo365-login/) must be installed and activated. See [online documentation](https://docs.wpo365.com/article/98-synchronize-microsoft-365-azure-ad-profile-fields) for configuration example.
20
21 = Support =
22
23 We will go to great length trying to support you if the plugin doesn't work as expected. Go to our [Support Page](https://www.wpo365.com/how-to-get-support/) to get in touch with us. We haven't been able to test our plugin in all endless possible Wordpress configurations and versions so we are keen to hear from you and happy to learn!
24
25 = Feedback =
26
27 We are keen to hear from you so share your feedback with us on [Twitter](https://twitter.com/WPO365) and help us get better!
28
29 == Installation ==
30
31 1. Purchase the plugin from the [website](https://www.wpo365.com/).
32 2. Navigate to [Your Account](https://www.wpo365.com/your-account/) to download the premium extension.
33 3. Go to WP Admin > Plugins > Add new and click Upload plugin.
34 4. Upload the plugin.
35 5. Wait until the installation finishes and then click Activate.
36 6. See [online documentation](https://docs.wpo365.com/article/98-synchronize-microsoft-365-azure-ad-profile-fields) for configuration example.
37
38 == Frequently Asked Questions ==
39
40 == Screenshots ==
41
42 == Upgrade Notice ==
43
44 == Changelog ==
45
46 Please check the [online change log](https://www.wpo365.com/change-log/) for changes.
...\ No newline at end of file ...\ No newline at end of file
1 <?php
2
3 namespace Wpo\Services;
4
5 use \Wpo\Core\WordPress_Helpers;
6 use \Wpo\Services\Log_Service;
7 use \Wpo\Services\Options_Service;
8 use \Wpo\Services\User_Details_Service;
9
10 // Prevent public access to this script
11 defined('ABSPATH') or die();
12
13 if (!class_exists('\Wpo\Services\BuddyPress_Service')) {
14
15 class BuddyPress_Service
16 {
17
18 /**
19 * Adds an additional section to the bottom of the user profile page
20 *
21 * @since 5.3
22 *
23 * @param WP_User $user whose profile is being shown
24 * @return void
25 */
26 public static function bp_show_extra_user_fields($user)
27 {
28
29 if (false === Options_Service::get_global_boolean_var('graph_user_details')) {
30 Log_Service::write_log('DEBUG', __METHOD__ . ' -> Extra user fields disabled as per configuration');
31 return;
32 } elseif (true === Options_Service::get_global_boolean_var('use_bp_extended')) {
33 Log_Service::write_log('DEBUG', __METHOD__ . ' -> Extra user fields will be display on BuddyPress Extended Profile instead');
34 return;
35 } elseif (!class_exists('\Wpo\Services\User_Details_Service')) {
36 Log_Service::write_log('WARN', __METHOD__ . ' -> Cannot show extra BuddyPress fields because of missing dependency');
37 return;
38 } else {
39
40 echo ('<div class="bp-widget base">');
41 echo ('<h3 class="screen-heading profile-group-title">' . __('Directory Info', 'wpo365-login') . '</h3>');
42 echo ('<table class="profile-fields bp-tables-user"><tbody>');
43
44 \Wpo\Services\User_Custom_Fields_Service::process_extra_user_fields(function ($name, $title) use (&$user) {
45 $parsed_user_field_key = User_Details_Service::parse_user_field_key($name);
46 $name = $parsed_user_field_key[0];
47 $wp_user_meta_key = $parsed_user_field_key[1];
48
49 $value = get_user_meta(\bp_displayed_user_id(), $wp_user_meta_key, true);
50 echo ('<tr class="field_1 field_name required-field visibility-public field_type_textbox"><td class="label">' . esc_html($title) . '</td>');
51
52 if (is_array($value)) {
53 echo ('<td class="data">');
54
55 foreach ($value as $idx => $val)
56 echo ('<p>' . esc_html($val) . '</p>');
57
58 echo ('</td>');
59 } else
60 echo ('<td class="data"><p>' . esc_html($value) . '</p></td>');
61
62 echo ("</tr>");
63 });
64
65 echo ('</tbody></table></div>');
66 }
67 }
68
69 /**
70 * Helper method that returns the O365 avatar for Buddy Press.
71 *
72 * @since 9.0
73 *
74 * @param $avatar Image tag for the user's avatar.
75 * @return string Image tag for the user's avatar possibly with img URL replaced with O365 profile image URL.
76 */
77 public static function fetch_buddy_press_avatar($bp_avatar, $params)
78 {
79
80 if (false === Options_Service::get_global_boolean_var('use_bp_avatar')) {
81 return $bp_avatar;
82 }
83
84 if (!is_array($params) || !isset($params['item_id'])) {
85 return $bp_avatar;
86 }
87
88 if (!class_exists('\Wpo\Services\Avatar_Service')) {
89 Log_Service::write_log('WARN', __METHOD__ . ' -> Cannot BuddyPress avatar because of missing dependency');
90 return $bp_avatar;
91 }
92
93 /**
94 * @since 10.5
95 *
96 * Don't return avatar if objec is not a user (e.g. but a group)
97 */
98 if (is_array($params) && isset($params['object']) && false === WordPress_Helpers::stripos($params['object'], 'user')) {
99 return $bp_avatar;
100 }
101
102 $o365_avatar_url = \Wpo\Services\Avatar_Service::get_o365_avatar_url(intval($params['item_id']));
103
104 return empty($o365_avatar_url)
105 ? $bp_avatar
106 : \preg_replace('/src=".+?"/', 'src="' . $o365_avatar_url . '"', $bp_avatar);
107 }
108 }
109 }
1 <?php
2
3 namespace Wpo\Services;
4
5 // Prevent public access to this script
6 defined('ABSPATH') or die();
7
8 use \Wpo\Services\Log_Service;
9 use \Wpo\Services\Options_Service;
10
11 if (!class_exists('\Wpo\Services\User_Create_Update_Service')) {
12
13 class User_Create_Update_Service
14 {
15
16 /**
17 * Updates a WordPress user.
18 *
19 * @since 11.0
20 *
21 * @param mixed $wp_user_id WordPress ID of the user that will be updated
22 * @param bool $is_deamon If true then actions that may sign out the user are ignored
23 * @param bool $exit_on_error If true the user my be signed out if an action fails
24 *
25 * @return int The WordPress ID of the user.
26 */
27 public static function create_user(&$wpo_usr, $is_deamon = false, $exit_on_error = true)
28 {
29 Log_Service::write_log('DEBUG', '##### -> ' . __METHOD__);
30
31 $user_login = !empty($wpo_usr->preferred_username)
32 ? $wpo_usr->preferred_username
33 : $wpo_usr->upn;
34
35 if (Options_Service::get_global_boolean_var('use_short_login_name') || Options_Service::get_global_string_var('user_name_preference') == 'short') {
36 $user_login = \stristr($user_login, '@', true);
37 }
38
39 if (!empty($wpo_usr->custom_username)) {
40 $user_login = $wpo_usr->custom_username;
41 }
42
43
44 /**
45 * @since 12.5
46 *
47 * Don't create a user when that user should not be added to a subsite in case of wpmu shared mode.
48 */
49 if (!$is_deamon && is_multisite() && !Options_Service::mu_use_subsite_options() && !is_main_site() && Options_Service::get_global_boolean_var('skip_add_user_to_subsite')) {
50 $blog_id = get_current_blog_id();
51
52 // Not using subsite options and administrator has disabled automatic adding of users to subsites
53 Log_Service::write_log('WARN', __METHOD__ . " -> Skipped creating a user with login $user_login for blog with ID $blog_id because administrator has disabled adding a user to a subsite");
54 Authentication_Service::goodbye(Error_Service::USER_NOT_FOUND);
55 exit();
56 }
57
58 if (!$is_deamon && !Options_Service::get_global_boolean_var('create_and_add_users')) {
59 Log_Service::write_log('ERROR', __METHOD__ . ' -> User not found and settings prevented creating a new user on-demand for user ' . $user_login);
60 Authentication_Service::goodbye(Error_Service::USER_NOT_FOUND);
61 exit();
62 }
63
64 /**
65 * @since 23.0 Added possibility to hook up (custom) actions to pre-defined events for various WPO365 workloads.
66 */
67
68 do_action(
69 'wpo365/user/creating',
70 $wpo_usr->preferred_username,
71 $wpo_usr->email,
72 $wpo_usr->groups
73 );
74
75 $usr_default_role = is_main_site()
76 ? Options_Service::get_global_string_var('new_usr_default_role')
77 : Options_Service::get_global_string_var('mu_new_usr_default_role');
78
79 $password_length = Options_Service::get_global_numeric_var('password_length');
80
81 if (empty($password_length) || $password_length < 16) {
82 $password_length = 16;
83 }
84
85 $userdata = array(
86 'user_login' => $user_login,
87 'user_pass' => wp_generate_password($password_length, true, false),
88 'display_name' => $wpo_usr->full_name,
89 'user_email' => $wpo_usr->email,
90 'first_name' => $wpo_usr->first_name,
91 'last_name' => $wpo_usr->last_name,
92 'role' => $usr_default_role,
93 );
94
95 /**
96 * @since 9.4
97 *
98 * Optionally removing any user_register hooks as these more often than
99 * not interfer and cause unexpected behavior.
100 */
101
102 $user_regiser_hooks = null;
103
104 if (Options_Service::get_global_boolean_var('skip_user_register_action') && isset($GLOBALS['wp_filter']) && isset($GLOBALS['wp_filter']['user_register'])) {
105 Log_Service::write_log('DEBUG', __METHOD__ . ' -> Temporarily removing all filters for the user_register action to avoid interference');
106 $user_regiser_hooks = $GLOBALS['wp_filter']['user_register'];
107 unset($GLOBALS['wp_filter']['user_register']);
108 }
109
110 $existing_registering = remove_filter('wp_pre_insert_user_data', '\Wpo\Services\Wp_To_Aad_Create_Update_Service::handle_user_registering', PHP_INT_MAX);
111 $existing_registered = remove_action('user_registered', '\Wpo\Services\Wp_To_Aad_Create_Update_Service::handle_user_registered', PHP_INT_MAX);
112 $wp_usr_id = wp_insert_user($userdata);
113
114 if ($existing_registering) {
115 add_filter('wp_pre_insert_user_data', '\Wpo\Services\Wp_To_Aad_Create_Update_Service::handle_user_registering', PHP_INT_MAX, 4);
116 }
117
118 if ($existing_registered) {
119 add_action('user_register', '\Wpo\Services\Wp_To_Aad_Create_Update_Service::handle_user_registered', PHP_INT_MAX, 1);
120 }
121
122 if (!empty($GLOBALS['wp_filter']) && !empty($user_regiser_hooks)) {
123 $GLOBALS['wp_filter']['user_register'] = $user_regiser_hooks;
124 }
125
126 if (is_wp_error($wp_usr_id)) {
127 Log_Service::write_log('ERROR', __METHOD__ . ' -> Could not create wp user. See next line for error information.');
128 Log_Service::write_log('ERROR', $wp_usr_id);
129
130 if ($exit_on_error) {
131 Authentication_Service::goodbye(Error_Service::CHECK_LOG);
132 exit();
133 }
134
135 return 0;
136 }
137
138 /**
139 * @since 15.0
140 */
141
142 do_action('wpo365/user/created', $wp_usr_id);
143
144 $wpo_usr->created = true;
145 Log_Service::write_log('DEBUG', __METHOD__ . ' -> Created new user with ID ' . $wp_usr_id);
146
147 // WPMU -> Add user to current blog
148 if (\class_exists('\Wpo\Services\User_Create_Service') && \method_exists('\Wpo\Services\User_Create_Service', 'wpmu_add_user_to_blog')) {
149 \Wpo\Services\User_Create_Service::wpmu_add_user_to_blog($wp_usr_id, $wpo_usr->preferred_username);
150 }
151
152 if (\class_exists('\Wpo\Services\User_Role_Service') && \method_exists('\Wpo\Services\User_Role_Service', 'update_user_roles')) {
153 \Wpo\Services\User_Role_Service::update_user_roles($wp_usr_id, $wpo_usr);
154 }
155
156 add_filter('allow_password_reset', '\Wpo\Services\User_Create_Service::temporarily_allow_password_reset', PHP_INT_MAX, 1);
157 wp_new_user_notification($wp_usr_id, null, 'both');
158 remove_filter('allow_password_reset', '\Wpo\Services\User_Create_Service::temporarily_allow_password_reset', PHP_INT_MAX);
159
160 return $wp_usr_id;
161 }
162
163 /**
164 * Updates a WordPress user.
165 *
166 * @param mixed $wp_user_id WordPress ID of the user that will be updated
167 * @param mixed $wpo_usr Internal user representation (Graph and ID token data)
168 * @param bool $is_deamon If true then actions that may sign out the user are ignored
169 *
170 * @return void
171 */
172 public static function update_user($wp_usr_id, $wpo_usr, $is_deamon = false)
173 {
174 Log_Service::write_log('DEBUG', '##### -> ' . __METHOD__);
175
176 // Save user's UPN
177 if (!empty($wpo_usr->upn)) {
178 update_user_meta($wp_usr_id, 'userPrincipalName', $wpo_usr->upn);
179 }
180
181 if (!$wpo_usr->created) {
182
183 if (!$is_deamon && \class_exists('\Wpo\Services\User_Create_Service') && \method_exists('\Wpo\Services\User_Create_Service', 'wpmu_add_user_to_blog')) {
184 \Wpo\Services\User_Create_Service::wpmu_add_user_to_blog($wp_usr_id, $wpo_usr->preferred_username);
185 }
186
187 if (\class_exists('\Wpo\Services\User_Role_Service') && \method_exists('\Wpo\Services\User_Role_Service', 'update_user_roles')) {
188 \Wpo\Services\User_Role_Service::update_user_roles($wp_usr_id, $wpo_usr);
189 }
190 }
191
192 // Update Avatar
193 if (Options_Service::get_global_boolean_var('use_avatar') && class_exists('\Wpo\Services\Avatar_Service')) {
194 $default_avatar = get_avatar($wp_usr_id);
195 }
196
197 // Update custom fields
198 if (class_exists('\Wpo\Services\User_Custom_Fields_Service')) {
199
200 if (Options_Service::get_global_boolean_var('use_saml') && Options_Service::get_global_string_var('extra_user_fields_source') == 'samlResponse') {
201 \Wpo\Services\User_Custom_Fields_Service::update_custom_fields_from_saml_attributes($wp_usr_id, $wpo_usr);
202 } else {
203 \Wpo\Services\User_Custom_Fields_Service::update_custom_fields($wp_usr_id, $wpo_usr);
204 }
205 }
206
207 // Update default WordPress user fields
208 self::update_wp_user($wp_usr_id, $wpo_usr);
209 }
210
211 /**
212 * @since 11.0
213 */
214 private static function update_wp_user($wp_usr_id, $wpo_usr)
215 {
216 // Update "core" WP_User fields
217 $wp_user_data = array('ID' => $wp_usr_id);
218
219 if (!empty($wpo_usr->email)) {
220 $wp_user_data['user_email'] = $wpo_usr->email;
221 }
222
223 if (!empty($wpo_usr->first_name)) {
224 $wp_user_data['first_name'] = $wpo_usr->first_name;
225 }
226
227 if (!empty($wpo_usr->last_name)) {
228 $wp_user_data['last_name'] = $wpo_usr->last_name;
229 }
230
231 if (!empty($wpo_usr->full_name)) {
232 $wp_user_data['display_name'] = $wpo_usr->full_name;
233 }
234
235 $existing_registering = remove_filter('wp_pre_insert_user_data', '\Wpo\Services\Wp_To_Aad_Create_Update_Service::handle_user_registering', PHP_INT_MAX);
236 $existing_registered = remove_action('user_registered', '\Wpo\Services\Wp_To_Aad_Create_Update_Service::handle_user_registered', PHP_INT_MAX);
237 wp_update_user($wp_user_data);
238
239 if ($existing_registering) {
240 add_filter('wp_pre_insert_user_data', '\Wpo\Services\Wp_To_Aad_Create_Update_Service::handle_user_registering', PHP_INT_MAX, 4);
241 }
242
243 if ($existing_registered) {
244 add_action('user_register', '\Wpo\Services\Wp_To_Aad_Create_Update_Service::handle_user_registered', PHP_INT_MAX, 1);
245 }
246 }
247 }
248 }
1 <?php
2
3 namespace Wpo\Services;
4
5 // Prevent public access to this script
6 defined('ABSPATH') or die();
7
8 use \Wpo\Core\WordPress_Helpers;
9 use \Wpo\Services\Log_Service;
10 use \Wpo\Services\Options_Service;
11 use \Wpo\Services\Graph_Service;
12 use \Wpo\Services\Saml2_Service;
13 use \Wpo\Services\User_Service;
14 use \Wpo\Services\User_Details_Service;
15
16 if (!class_exists('\Wpo\Services\User_Custom_Fields_Service')) {
17
18 class User_Custom_Fields_Service
19 {
20 /**
21 * @since 11.0
22 */
23 public static function update_custom_fields($wp_usr_id, $wpo_usr)
24 {
25 Log_Service::write_log('DEBUG', '##### -> ' . __METHOD__);
26
27 if (empty($wpo_usr->graph_resource)) {
28 Log_Service::write_log('DEBUG', __METHOD__ . ' -> Cannot update custom user fields because the graph resource has not been retrieved');
29 return;
30 }
31
32 // Check to see if expanded properties need to be loaded (currently only manager is supported)
33 $extra_user_fields = Options_Service::get_global_list_var('extra_user_fields');
34 $expanded_fields = array();
35
36 // Iterate over the configured graph fields and identify any supported expandable properties
37 array_map(function ($kv_pair) use (&$expanded_fields) {
38 if (false !== WordPress_Helpers::stripos($kv_pair['key'], 'manager')) {
39 $expanded_fields[] = 'manager';
40 }
41 }, $extra_user_fields);
42
43 // Query to expand property
44 if (in_array('manager', $expanded_fields)) {
45 $upn = User_Service::try_get_user_principal_name($wp_usr_id);
46
47 if (!empty($upn)) {
48 $user_manager = Graph_Service::fetch('/users/' . \rawurlencode($upn) . '/manager', 'GET', false, array('Accept: application/json;odata.metadata=minimal'));
49
50 // Expand user details
51 if (Graph_Service::is_fetch_result_ok($user_manager, 'Could not retrieve user manager details for user ' . $upn, 'WARN')) {
52 $wpo_usr->graph_resource['manager'] = $user_manager['payload'];
53 }
54 }
55 }
56
57 self::process_extra_user_fields(function ($name, $title) use (&$wpo_usr, &$wp_usr_id) {
58
59 $parsed_user_field_key = User_Details_Service::parse_user_field_key($name);
60 $name = $parsed_user_field_key[0];
61 $wp_user_meta_key = $parsed_user_field_key[1];
62
63 $name_arr = explode('.', $name);
64 $current = $wpo_usr->graph_resource;
65 $value = null;
66
67 if (sizeof($name_arr) > 1) {
68
69 $found = false;
70
71 for ($i = 0; $i < sizeof($name_arr); $i++) {
72 /**
73 * Found must be true for the last iteration therefore it resets every cycle
74 */
75
76 $found = false;
77
78 /**
79 * Administrator has specified to get the nth element of an array / object
80 */
81
82 if (is_array($current) && \is_numeric($name_arr[$i]) && $i < sizeof($current) && array_key_exists($name_arr[$i], $current)) {
83 $current = $current[$name_arr[$i]];
84 $found = true;
85 }
86
87 /**
88 * Administrator has specified to get the named element of an array / object
89 */
90
91 else if (is_array($current) && array_key_exists($name_arr[$i], $current)) {
92 $current = $current[$name_arr[$i]];
93 $found = true;
94 }
95 }
96
97 if ($found) {
98 $value = $current;
99 }
100 }
101
102 /**
103 * Administrator has specified a simple non-nested property
104 */
105
106 else if (array_key_exists($name, $current) && !empty($current[$name])) {
107 $value = $name == 'manager'
108 ? self::parse_manager_details($current['manager'])
109 : $current[$name];
110 }
111
112 if (empty($value)) {
113 $value = '';
114 }
115
116 update_user_meta(
117 $wp_usr_id,
118 $wp_user_meta_key,
119 $value
120 );
121
122 if (function_exists('xprofile_set_field_data') && true === Options_Service::get_global_boolean_var('use_bp_extended')) {
123 xprofile_set_field_data($title, $wp_usr_id, $value);
124 }
125 });
126 }
127
128 /**
129 * Processes the extra user fields and tries to read them from the SAML attributes
130 * and if found saves their value as WordPress user meta.
131 *
132 * @since 20.0
133 *
134 * @param mixed $wp_usr_id
135 * @param mixed $wpo_usr
136 * @return void
137 */
138 public static function update_custom_fields_from_saml_attributes($wp_usr_id, $wpo_usr)
139 {
140 Log_Service::write_log('DEBUG', '##### -> ' . __METHOD__);
141
142 if (empty($wpo_usr->saml_attributes)) {
143 Log_Service::write_log('DEBUG', __METHOD__ . ' -> Cannot update custom user fields because the SAML attributes are not found');
144 return;
145 }
146
147 self::process_extra_user_fields(function ($name, $title) use (&$wpo_usr, &$wp_usr_id) {
148
149 $parsed_user_field_key = User_Details_Service::parse_user_field_key($name);
150 $claim = $parsed_user_field_key[0];
151 $wp_user_meta_key = $parsed_user_field_key[1];
152
153 if (strcmp($claim, $wp_user_meta_key) === 0 && WordPress_Helpers::stripos($wp_user_meta_key, '/') > 0) {
154 $key_exploded = explode('/', $wp_user_meta_key);
155 $wp_user_meta_key = sprintf('saml_%s', array_pop($key_exploded));
156 }
157
158 $value = Saml2_Service::get_attribute($claim, $wpo_usr->saml_attributes);
159
160 update_user_meta(
161 $wp_usr_id,
162 $wp_user_meta_key,
163 $value
164 );
165
166 if (function_exists('xprofile_set_field_data') && true === Options_Service::get_global_boolean_var('use_bp_extended')) {
167 xprofile_set_field_data($title, $wp_usr_id, $value);
168 }
169 });
170 }
171
172 /**
173 *
174 * @param function callback with signature ( $name, $title ) => void
175 *
176 * @return void
177 */
178 public static function process_extra_user_fields($callback)
179 {
180 $extra_user_fields = Options_Service::get_global_list_var('extra_user_fields');
181
182 if (sizeof($extra_user_fields) == 0)
183 return;
184
185 foreach ($extra_user_fields as $kv_pair)
186 $callback($kv_pair['key'], $kv_pair['value']);
187 }
188
189 /**
190 * Adds an additional section to the bottom of the user profile page
191 *
192 * @since 2.0
193 *
194 * @param WP_User $user whose profile is being shown
195 * @return void
196 */
197 public static function show_extra_user_fields($user)
198 {
199 if (false === Options_Service::get_global_boolean_var('graph_user_details')) {
200 Log_Service::write_log('DEBUG', __METHOD__ . ' -> Extra user fields disabled as per configuration');
201 return;
202 } elseif (true === Options_Service::get_global_boolean_var('use_bp_extended')) {
203 Log_Service::write_log('DEBUG', __METHOD__ . ' -> Extra user fields will be display on BuddyPress Extended Profile instead');
204 return;
205 } else {
206
207 echo ("<h3>" . __('Office 365 Profile Information', 'wpo365-login') . "</h3>");
208 echo ("<table class=\"form-table\">");
209
210 self::process_extra_user_fields(function ($name, $title) use (&$user) {
211
212 $parsed_user_field_key = User_Details_Service::parse_user_field_key($name);
213 $name = $parsed_user_field_key[0];
214 $wp_user_meta_key = $parsed_user_field_key[1];
215
216 // The following may be true for SAML based custom attributes
217 if (strcmp($name, $wp_user_meta_key) === 0 && WordPress_Helpers::stripos($wp_user_meta_key, '/') > 0) {
218 $key_exploded = explode('/', $wp_user_meta_key);
219 $wp_user_meta_key = sprintf('saml_%s', array_pop($key_exploded));
220 }
221
222 $value = get_user_meta($user->ID, $wp_user_meta_key, true);
223
224 echo ('<tr><th><label for="' . esc_attr($wp_user_meta_key) . '">' . esc_html($title) . '</label></th>');
225
226 if (is_array($value)) {
227
228 echo ("<td>");
229
230 foreach ($value as $idx => $val) {
231
232 if (empty($val)) {
233 continue;
234 }
235
236 echo '<input type="text" name="' . esc_attr($wp_user_meta_key) . '__##__' . esc_attr($idx) . '" id="' . esc_attr($wp_user_meta_key) . esc_attr($idx) . '" value="' . esc_attr($val) . '" class="regular-text" /><br />';
237 }
238
239 echo ("</td>");
240 } else {
241
242 echo ('<td><input type="text" name="' . esc_attr($wp_user_meta_key) . '" id="' . esc_attr($wp_user_meta_key) . '" value="' . esc_attr($value) . '" class="regular-text" /><br/></td>');
243 }
244
245 echo ("</tr>");
246 });
247
248 echo ('</table>');
249 }
250 }
251
252 /**
253 * Allow users to save their updated extra user fields
254 *
255 * @since 4.0
256 *
257 * @return mixed(boolean|void)
258 */
259 public static function save_user_details($user_id)
260 {
261 if (!current_user_can('edit_user', $user_id)) {
262 return false;
263 }
264
265 self::process_extra_user_fields(function ($name, $title) use (&$user_id) {
266
267 $parsed_user_field_key = User_Details_Service::parse_user_field_key($name);
268 $name = $parsed_user_field_key[0];
269 $wp_user_meta_key = $parsed_user_field_key[1];
270
271 // The following may be true for SAML based custom attributes
272 if (strcmp($name, $wp_user_meta_key) === 0 && WordPress_Helpers::stripos($wp_user_meta_key, '/') > 0) {
273 $key_exploded = explode('/', $wp_user_meta_key);
274 $wp_user_meta_key = sprintf('saml_%s', array_pop($key_exploded));
275 }
276
277 $lookup = str_replace('.', '_', $wp_user_meta_key); // '.' is changed to '_' when sent in a request
278
279 if (isset($_POST[$lookup])) {
280
281 update_user_meta(
282 $user_id,
283 $wp_user_meta_key,
284 sanitize_text_field($_POST[$lookup])
285 );
286 return;
287 }
288
289 $array_of_user_meta = array();
290
291 foreach ($_POST as $key => $value) {
292
293 if (false !== WordPress_Helpers::strpos($key, $lookup . "__##__")) {
294 $array_of_user_meta[$key] = $value;
295 }
296 }
297
298 if (false === empty($array_of_user_meta)) {
299
300 $array_of_user_meta_values = array_values($array_of_user_meta);
301
302 update_user_meta(
303 $user_id,
304 $wp_user_meta_key,
305 $array_of_user_meta_values
306 );
307 return;
308 }
309 });
310 }
311
312 /**
313 * Gets details of a user as an array with displayName, mail, officeLocation, department,
314 * businessPhones, mobilePhone.
315 *
316 * @since 15.0
317 *
318 * @param int $wp_usr_id
319 * @return array
320 */
321 public static function get_manager_details_from_wp_user($wp_usr_id)
322 {
323
324 if (empty($wp_usr = get_user_by('ID', $wp_usr_id))) {
325 return array();
326 }
327
328 $displayName = $wp_usr->display_name;
329 $mail = $wp_usr->user_email;
330 $officeLocation = get_user_meta($wp_usr_id, 'officeLocation', true);
331 $department = get_user_meta($wp_usr_id, 'department', true);
332 $businessPhones = get_user_meta($wp_usr_id, 'businessPhones', true);
333 $mobilePhone = get_user_meta($wp_usr_id, 'mobilePhone', true);
334
335 return array(
336 'displayName' => $displayName,
337 'mail' => $mail,
338 'officeLocation' => !empty($officeLocation) ? $officeLocation : '',
339 'department' => !empty($department) ? $department : '',
340 'businessPhones' => !empty($businessPhones) ? $businessPhones : '',
341 'mobilePhone' => !empty($mobilePhone) ? $mobilePhone : '',
342 );
343 }
344
345 /**
346 * Parses the manager details fetched from Microsoft Graph.
347 *
348 * @since 7.17
349 *
350 * @return array Assoc. array with the most important manager details.
351 */
352 private static function parse_manager_details($manager)
353 {
354 if (empty($manager)) {
355 return array();
356 }
357 $displayName = !empty($manager['displayName'])
358 ? $manager['displayName']
359 : '';
360 $mail = !empty($manager['mail'])
361 ? $manager['mail']
362 : '';
363 $officeLocation = !empty($manager['officeLocation'])
364 ? $manager['officeLocation']
365 : '';
366 $department = !empty($manager['department'])
367 ? $manager['department']
368 : '';
369 $businessPhones = !empty($manager['businessPhones'])
370 ? $manager['businessPhones'][0]
371 : '';
372 $mobilePhone = !empty($manager['mobilePhone'])
373 ? $manager['mobilePhone'][0]
374 : '';
375 return array(
376 'displayName' => $displayName,
377 'mail' => $mail,
378 'officeLocation' => $officeLocation,
379 'department' => $department,
380 'businessPhones' => $businessPhones,
381 'mobilePhone' => $mobilePhone,
382 );
383 }
384 }
385 }
1 <?php
2
3 namespace Wpo\Services;
4
5 // Prevent public access to this script
6 defined('ABSPATH') or die();
7
8 use \Wpo\Core\User;
9 use \Wpo\Core\WordPress_Helpers;
10 use \Wpo\Services\Log_Service;
11 use \Wpo\Services\Options_Service;
12 use \Wpo\Services\Graph_Service;
13 use \Wpo\Services\User_Service;
14
15 if (!class_exists('\Wpo\Services\User_Details_Service')) {
16
17 class User_Details_Service
18 {
19
20 /**
21 * @since 11.0
22 */
23 public static function try_improve_core_fields(&$wpo_usr)
24 {
25 Log_Service::write_log('DEBUG', '##### -> ' . __METHOD__);
26
27 if (isset($wpo_usr->graph_resource['userPrincipalName'])) {
28 $wpo_usr->upn = $wpo_usr->graph_resource['userPrincipalName'];
29 }
30
31 if (isset($wpo_usr->graph_resource['mail'])) {
32 $wpo_usr->email = $wpo_usr->graph_resource['mail'];
33 }
34
35 if (isset($wpo_usr->graph_resource['givenName'])) {
36 $wpo_usr->first_name = $wpo_usr->graph_resource['givenName'];
37 }
38
39 if (isset($wpo_usr->graph_resource['surname'])) {
40 $wpo_usr->last_name = $wpo_usr->graph_resource['surname'];
41 }
42
43 if (isset($wpo_usr->graph_resource['displayName'])) {
44 $wpo_usr->full_name = $wpo_usr->graph_resource['displayName'];
45 }
46
47 $graph_display_name_format = Options_Service::get_global_string_var('graph_display_name_format');
48
49 if ($graph_display_name_format == 'skip') {
50 $wpo_usr->full_name = '';
51 } else {
52 if (!empty($wpo_usr->first_name) && !empty($wpo_usr->last_name)) {
53
54 if ($graph_display_name_format == 'givenNameSurname') {
55 $wpo_usr->full_name = sprintf('%s %s', $wpo_usr->first_name, $wpo_usr->last_name);
56 } elseif ($graph_display_name_format == 'surnameGivenName') {
57 $wpo_usr->full_name = sprintf('%s, %s', $wpo_usr->last_name, $wpo_usr->first_name);
58 }
59 }
60 }
61 }
62
63 /**
64 * Retrieves the user's AAD group memberships and adds them to the internally used User.
65 *
66 * @since 11.0
67 *
68 * @param string $resource_identifier Object ID or user principal name
69 * @param bool $use_me Deprecated, the method will decide for itself
70 * @param bool $use_delegated If the true the plugin will use a token with delegated permissions
71 * @param bool $return_error Whether the method should return the error if one occurs
72 *
73 * @return object The graph resource if request or else null
74 */
75 public static function get_graph_user($resource_identifier = null, $use_me = false, $use_delegated = false, $return_error = false)
76 {
77 Log_Service::write_log('DEBUG', '##### -> ' . __METHOD__);
78
79 $query = empty($resource_identifier)
80 ? '/me'
81 : '/users/' . \rawurlencode($resource_identifier);
82
83 $select_properties = Options_Service::get_global_list_var('graph_select_properties');
84
85 if (!empty($select_properties)) {
86 $default_properties = array('businessPhones', 'displayName', 'givenName', 'jobTitle', 'mail', 'mobilePhone', 'officeLocation', 'preferredLanguage', 'surname', 'userPrincipalName', 'id', 'city', 'companyName', 'country', 'department', 'employeeId', 'streetAddress', 'state', 'postalCode');
87 $select_properties = array_merge($select_properties, $default_properties);
88 $select_properties = array_map(function ($item) {
89 return \trim($item);
90 }, $select_properties);
91 $query = sprintf(
92 '%s?$select=%s',
93 $query,
94 \implode(',', $select_properties)
95 );
96 }
97
98 $headers = array(
99 'Accept: application/json;odata.metadata=minimal',
100 'Content-Type: application/json',
101 );
102
103 $graph_resource = Graph_Service::fetch($query, 'GET', false, $headers, $use_delegated);
104
105 if (Graph_Service::is_fetch_result_ok($graph_resource, 'Could not retrieve user details')) {
106 return $graph_resource['payload'];
107 }
108
109 return $return_error ? $graph_resource : null;
110 }
111
112 /**
113 * This helper will create a new member graph_resource on the wpo_usr parameter,
114 * populates it with fields from the ID token instead and eventually returns the
115 * wpo_usr.
116 *
117 * @since 18.0
118 *
119 * @param object &$wpo_usr By reference
120 * @param object $id_token
121 * @return void
122 */
123 public static function update_wpo_usr_from_id_token(&$wpo_usr, $id_token)
124 {
125 $extra_user_fields = Options_Service::get_global_list_var('extra_user_fields');
126 $wpo_usr->graph_resource = array();
127
128 // Just copy these "core" fields for User_Details_Service::try_improve_core_fields
129 if (!empty($wpo_usr->email)) {
130 $wpo_usr->graph_resource['mail'] = $wpo_usr->email;
131 }
132
133 if (!empty($wpo_usr->first_name)) {
134 $wpo_usr->graph_resource['givenName'] = $wpo_usr->first_name;
135 }
136
137 if (!empty($wpo_usr->last_name)) {
138 $wpo_usr->graph_resource['surname'] = $wpo_usr->last_name;
139 }
140
141 // Now try to add custom user fields to the mocked graph_resource
142 if (!empty($extra_user_fields)) {
143 $id_token_props = get_object_vars($id_token);
144
145 foreach ($extra_user_fields as $index => $keyValuePair) {
146
147 if (empty($keyValuePair) || !is_array($keyValuePair) || empty($keyValuePair['key'])) {
148 continue;
149 }
150
151 $parsed_user_field_key = self::parse_user_field_key($keyValuePair['key']);
152 $name = $parsed_user_field_key[0];
153
154 $value = array_key_exists($name, $id_token_props)
155 ? $id_token_props[$name]
156 : false;
157
158 if (empty($value)) {
159 continue;
160 }
161
162 $wpo_usr->graph_resource[$name] = $value;
163 }
164 }
165 }
166
167 /**
168 * This helper will create a new member graph_resource on the wpo_usr parameter,
169 * populates it with fields from the SAML response instead and eventually returns the
170 * wpo_usr.
171 *
172 * @since 20.0
173 *
174 * @param mixed $wpo_usr
175 * @param mixed $saml_attributes
176 * @return void
177 */
178 public static function update_wpo_usr_from_saml_attributes(&$wpo_usr, $saml_attributes)
179 {
180 if (is_array($saml_attributes)) {
181 $wpo_usr->saml_attributes = $saml_attributes;
182 }
183 }
184
185 /**
186 * Parses the extra_user_fields key that since v19.5 may be a compound field that contains
187 * the name for the usermeta field in WordPress for the user attribute retrieved from Microsoft
188 * Graph.
189 *
190 * @since 20.0
191 *
192 * @param mixed $name The name of the Microsoft Graph property possibly combined with a proposed name for the key for the usermeta e.g. mobilePhone;#msGraphMobilePhone
193 * @return array The name of the Microsoft Graph property and the proposed name for the key for the usermeta.
194 */
195 public static function parse_user_field_key($name)
196 {
197 if (false !== WordPress_Helpers::stripos($name, ';#')) {
198 $name_arr = explode(';#', $name);
199 $name = $name_arr[0];
200
201 if (sizeof($name_arr) > 1) {
202 $wp_user_meta_key = $name_arr[1];
203 }
204 }
205
206 $wp_user_meta_key = empty($wp_user_meta_key) ? $name : $wp_user_meta_key;
207
208 return array(
209 $name,
210 $wp_user_meta_key
211 );
212 }
213
214 /**
215 * Administrators can prevent WordPress from sending emails when their email changed.
216 * This is especially useful since WordPress doesn't compare new and old email addresses
217 * case insensitive.
218 *
219 * @since 11.9
220 *
221 * @return boolean Whether or not to send the email changed notification.
222 */
223 public static function prevent_send_email_change_email()
224 {
225 Log_Service::write_log('DEBUG', '##### -> ' . __METHOD__);
226
227 if (Options_Service::get_global_boolean_var('prevent_send_email_change_email')) {
228 return false;
229 }
230
231 return true;
232 }
233 }
234 }
1 <?php
2
3 namespace Wpo\Services;
4
5 use WP_Role;
6 use \Wpo\Core\Permissions_Helpers;
7 use \Wpo\Services\Log_Service;
8 use \Wpo\Services\Options_Service;
9
10 // Prevent public access to this script
11 defined('ABSPATH') or die();
12
13 if (!class_exists('\Wpo\Services\User_Role_Service')) {
14
15 class User_Role_Service
16 {
17
18 public static function update_user_roles($wp_usr_id, $wpo_usr)
19 {
20 Log_Service::write_log('DEBUG', '##### -> ' . __METHOD__);
21
22 if (Options_Service::get_global_boolean_var('enable_audiences') && class_exists('\Wpo\Services\Audiences_Service')) {
23 // Optionally update audience assignments
24 \Wpo\Services\Audiences_Service::aad_group_x_audience($wp_usr_id, $wpo_usr);
25 }
26
27 if (class_exists('\Wpo\Services\Mapped_Itthinx_Groups_Service') && method_exists('\Wpo\Services\Mapped_Itthinx_Groups_Service', 'aad_group_x_itthinx_group')) {
28 // Optionally update itthinx group assignments
29 \Wpo\Services\Mapped_Itthinx_Groups_Service::aad_group_x_itthinx_group($wp_usr_id, $wpo_usr);
30 }
31
32 if (class_exists('\Wpo\Services\Mapped_Itthinx_Groups_Service') && method_exists('\Wpo\Services\Mapped_Itthinx_Groups_Service', 'custom_field_x_itthinx_group')) {
33 // Optionally update itthinx group assignments
34 \Wpo\Services\Mapped_Itthinx_Groups_Service::custom_field_x_itthinx_group($wp_usr_id, $wpo_usr);
35 }
36
37 /**
38 * @since 24.0 LearnDash Integration
39 */
40
41 if (class_exists('\Wpo\Services\LearnDash_Integration_Service') && method_exists('\Wpo\Services\LearnDash_Integration_Service', 'update_ld_assignments')) {
42 \Wpo\Services\LearnDash_Integration_Service::update_ld_assignments($wp_usr_id, $wpo_usr->groups);
43 }
44
45 $update_strategy = strtolower(Options_Service::get_global_string_var('replace_or_update_user_roles'));
46
47 if ($update_strategy == 'skip') {
48 Log_Service::write_log('DEBUG', __METHOD__ . ' -> Target role for user could not be determined because the administrator configured the plugin to not update user roles');
49 return;
50 }
51
52 // Get all possible roles for user
53 $user_roles = self::get_user_roles($wp_usr_id, $wpo_usr);
54
55 $wp_usr = \get_user_by('ID', $wp_usr_id);
56
57 if (!Options_Service::get_global_boolean_var('update_admins') && (\in_array('administrator', $wp_usr->roles) || is_super_admin($wp_usr_id))) {
58 Log_Service::write_log('DEBUG', __METHOD__ . ' -> Not updating the role for a user that is already an administrator.');
59 return;
60 }
61
62 $usr_default_role = is_main_site()
63 ? Options_Service::get_global_string_var('new_usr_default_role')
64 : Options_Service::get_global_string_var('mu_new_usr_default_role');
65
66 // Remove default role -> It will be added later if requested as fallback
67 if (in_array($usr_default_role, $wp_usr->roles)) {
68 $wp_usr->remove_role($usr_default_role);
69 // refresh the user meta for
70 $wp_usr = \get_user_by('ID', $wp_usr_id);
71 }
72
73 // Empty any existing roles when configured to do so
74 if ($update_strategy == 'replace') {
75 foreach ($wp_usr->roles as $current_user_role) {
76
77 /**
78 * @since 24.0 Filters whether the role should be removed.
79 */
80 $skip_remove_role = apply_filters('wpo365/roles/remove', false, $current_user_role);
81
82 if (!$skip_remove_role) {
83 $wp_usr->remove_role($current_user_role);
84 }
85 }
86
87 // refresh the user meta for
88 $wp_usr = \get_user_by('ID', $wp_usr_id);
89 }
90
91 // Add from new roles if not already added
92 foreach ($user_roles as $user_role) {
93 if (false === in_array($user_role, $wp_usr->roles)) {
94 $wp_usr->add_role($user_role);
95 }
96 }
97
98 // refresh the user meta for
99 $wp_usr = \get_user_by('ID', $wp_usr_id);
100
101 // Add default role if needed / configured
102 if (empty($wp_usr->roles) || (!empty($wp_usr->roles) && false === Options_Service::get_global_boolean_var('default_role_as_fallback'))) {
103
104 if (!empty($usr_default_role)) {
105 $wp_role = self::get_wp_role_case($usr_default_role);
106
107 if (empty($wp_role)) {
108 Log_Service::write_log('ERROR', __METHOD__ . ' -> Trying to add the default role but it appears undefined');
109 } else {
110 $wp_usr->add_role($usr_default_role);
111 }
112 }
113 }
114 }
115
116 /**
117 * Find a WP role in an case insensitive matter.
118 *
119 * @since 24.0
120 *
121 * @param mixed $role_name
122 * @return WP_Role|null
123 */
124 public static function get_wp_role_case($role_name)
125 {
126 $wp_roles_objects = wp_roles()->role_objects;
127
128 foreach ($wp_roles_objects as $wp_role) {
129
130 if (strcasecmp($wp_role->name, $role_name) === 0) {
131 return $wp_role;
132 }
133 }
134
135 return null;
136 }
137
138 /**
139 * Gets the user's default role or if a mapping exists overrides that default role
140 * and returns the role according to the mapping.
141 *
142 * @since 3.2
143 *
144 *
145 * @return mixed(array|WP_Error) user's role as string or an WP_Error if not defined
146 */
147 private static function get_user_roles($wp_usr_id, $wpo_usr)
148 {
149 $user_roles = array();
150
151 // Graph user resource property x WP role
152 if (class_exists('\Wpo\Services\Mapped_Custom_Fields_Service') && method_exists('\Wpo\Services\Mapped_Custom_Fields_Service', 'custom_field_x_role')) {
153 \Wpo\Services\Mapped_Custom_Fields_Service::custom_field_x_role($user_roles, $wpo_usr);
154 }
155
156 // AAD group x WP role
157 if (class_exists('\Wpo\Services\Mapped_Aad_Groups_Service') && method_exists('\Wpo\Services\Mapped_Aad_Groups_Service', 'aad_group_x_role')) {
158 \Wpo\Services\Mapped_Aad_Groups_Service::aad_group_x_role($user_roles, $wpo_usr);
159 }
160
161 // AAD group x Super Admin
162 if (class_exists('\Wpo\Services\Mapped_Aad_Groups_Service') && method_exists('\Wpo\Services\Mapped_Aad_Groups_Service', 'aad_group_x_super_admin')) {
163 \Wpo\Services\Mapped_Aad_Groups_Service::aad_group_x_super_admin($wp_usr_id, $wpo_usr, true);
164 }
165
166 // Logon Domain x WP role
167 if (class_exists('\Wpo\Services\Mapped_Domains_Service') && method_exists('\Wpo\Services\Mapped_Domains_Service', 'domain_x_role')) {
168 \Wpo\Services\Mapped_Domains_Service::domain_x_role($user_roles, $wpo_usr);
169 }
170
171 return $user_roles;
172 }
173 }
174 }
1 {
2 "name": "wpo365/wpo365-custom-fields",
3 "type": "plugin",
4 "description": "Synchronize Azure AD user attributes e.g. department, job title etc. to WordPress user profiles (includes the PROFILE+ extension).",
5 "keywords": ["wordpress", "Office 365", "O365", "Azure AD"],
6 "homepage": "https://www.wpo365.com/",
7 "license": "",
8 "authors": [
9 {
10 "name": "Marco van Wieren",
11 "email": "support@wpo365.com",
12 "homepage": "https://www.wpo365.com/",
13 "role": "Developer"
14 }
15 ],
16 "require": {
17 "php": ">=5.6.40"
18 },
19 "autoload": {
20 "psr-4": {
21 "Wpo\\": "",
22 "Wpo\\Services\\": "Services"
23 }
24 }
25 }
1 Downloads by van Wieren - WPO365 | EXTENSION
2
3 Copyright 2012 - 2014
4
5 End User License Agreement
6
7 Commercial use
8
9 After you have purchased the plus version of our plugin
10 and have downloaded the plugin as ZIP file, you are licensed
11 to install the plugin only into the number of website(s)
12 corresponding to the license you purchased plus into a
13 corresponding staging environments.
14
15 You may not duplicate the plugin in whole or in part,
16 except that you may make one copy of it for backup or
17 archival purposes.
18
19 You may terminate this license at any time by destroying the
20 original and all copies of the plugin in whatever form.
21
22 You may permanently transfer all of your rights under this
23 EULA provided you transfer all copies of the plugin (including
24 copies of all prior versions if the plugin is an upgrade) and
25 retain none, and the recipient agrees to the terms of this EULA.
26
27 You may not redistribute, modify or resold the plugin in any
28 way without the written permission of Downloads by van Wieren. You may not rent,
29 lease, or lend the plugin. You may not use the plugin in any
30 software or application that compete with products and services
31 of Downloads by van Wieren.
32
33 School / Non-profit
34
35 If you purchase the School / Non-profit license, Downloads by van Wieren may
36 request you to submit proof of your organization’s status in
37 writing.
38
39 Warranty
40
41 Plugins sold and distributed by Downloads by van Wieren are done so in the hope
42 that they will be useful, but WITHOUT ANY WARRANTY. Inasmuch as
43 WordPress functions correctly on a clean install of itself,
44 Downloads by van Wieren plugins are guaranteed to function on a clean install
45 of the minimum, stable and required version of WordPress for
46 the plugins. Because the number and variety of third-party
47 plugins and themes is vast and wide, we do not guarantee that
48 the plugin will function with all third-party plugins,
49 themes or browsers of any kind.
50
51 We do not assume responsibility and will not be held responsible
52 for any conflicts or compatibility issues that may occur due to
53 third-party software. We assume no responsibility for any data
54 loss as a result of installing or using Downloads by van Wieren plugins.
55 Should conflicts occur with third-party software, we may provide support
56 at our discretion.
57
58 Refund Policy
59
60 We firmly believe in and stand behind our products 100%, but
61 we understand that they cannot work perfectly for everyone all
62 of the time. If you would like to request a refund, please contact
63 us at info@wpo365.com.
64
65 When requesting a refund, we respectfully ask that you meet the
66 following refund policy conditions:
67
68 Eligibility conditions for a refund request:
69
70 - You are within the first 30 days of the original purchase of the plugin.
71 - We cannot grant refunds after the first 30 days of the original purchase.
72 - You have purchased the plugin and after installing and testing the plugin,
73 have found that it will not work for your business or required setup.
74
75 Or
76
77 - You have an issue that we are unable to resolve which makes the system unusable.
78 We may ask you questions regarding the nature of your refund request so we can
79 improve the plugin in the future.
80 - If your issue(s) comes from not being able to install the plugin properly
81 or get the plugin to perform its basic functions, we will happily consider
82 your refund request.
83 - You have contacted our support team and allowed us to attempt to resolve
84 your issue(s), or have explained why the plugin will not work for you.
85 Please note, technical issues caused by 3rd party plugins, themes or other
86 software will not provide grounds for a refund.
87 - You agree to deactivate and uninstall the plugin from your site if a refund
88 is granted.
89 - Refunds will be offered at our sole discretion. By purchasing plugin(s) from
90 our site, you agree to this refund policy and relinquish any rights to subject
91 it to any questions, judgment or legal actions. We are not liable to cover any
92 differences in exchange rates between the time you purchased and the time you are
93 refunded.
94
95 Restrictions
96
97 Prohibition on Reverse Engineering, Decompilation, and Disassembly. You may not
98 reverse engineer, decompile, or disassemble the plugin in any way without the
99 written permission of Downloads by van Wieren.
100
101 Copyright
102
103 The plugin is owned by Downloads by van Wieren, and is protected by copyright laws
104 and international copyright treaties, as well as other intellectual property laws
105 and treaties. The plugin is licensed, not sold, to You for use solely subject to
106 the terms and conditions of this Agreement.
107
108 Termination
109
110 Without prejudice to any other rights, Downloads by van Wieren may terminate this
111 EULA if you fail to comply with the terms and conditions of this EULA. In such event,
112 you must destroy all copies of the plugin.
...\ No newline at end of file ...\ No newline at end of file
1 <?php
2
3 // autoload.php @generated by Composer
4
5 require_once __DIR__ . '/composer/autoload_real.php';
6
7 return ComposerAutoloaderInitf106db3bb07a0cc7f373b495de3213f9::getLoader();
1 <?php
2
3 /*
4 * This file is part of Composer.
5 *
6 * (c) Nils Adermann <naderman@naderman.de>
7 * Jordi Boggiano <j.boggiano@seld.be>
8 *
9 * For the full copyright and license information, please view the LICENSE
10 * file that was distributed with this source code.
11 */
12
13 namespace Composer\Autoload;
14
15 /**
16 * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
17 *
18 * $loader = new \Composer\Autoload\ClassLoader();
19 *
20 * // register classes with namespaces
21 * $loader->add('Symfony\Component', __DIR__.'/component');
22 * $loader->add('Symfony', __DIR__.'/framework');
23 *
24 * // activate the autoloader
25 * $loader->register();
26 *
27 * // to enable searching the include path (eg. for PEAR packages)
28 * $loader->setUseIncludePath(true);
29 *
30 * In this example, if you try to use a class in the Symfony\Component
31 * namespace or one of its children (Symfony\Component\Console for instance),
32 * the autoloader will first look for the class under the component/
33 * directory, and it will then fallback to the framework/ directory if not
34 * found before giving up.
35 *
36 * This class is loosely based on the Symfony UniversalClassLoader.
37 *
38 * @author Fabien Potencier <fabien@symfony.com>
39 * @author Jordi Boggiano <j.boggiano@seld.be>
40 * @see http://www.php-fig.org/psr/psr-0/
41 * @see http://www.php-fig.org/psr/psr-4/
42 */
43 class ClassLoader
44 {
45 // PSR-4
46 private $prefixLengthsPsr4 = array();
47 private $prefixDirsPsr4 = array();
48 private $fallbackDirsPsr4 = array();
49
50 // PSR-0
51 private $prefixesPsr0 = array();
52 private $fallbackDirsPsr0 = array();
53
54 private $useIncludePath = false;
55 private $classMap = array();
56 private $classMapAuthoritative = false;
57 private $missingClasses = array();
58 private $apcuPrefix;
59
60 public function getPrefixes()
61 {
62 if (!empty($this->prefixesPsr0)) {
63 return call_user_func_array('array_merge', $this->prefixesPsr0);
64 }
65
66 return array();
67 }
68
69 public function getPrefixesPsr4()
70 {
71 return $this->prefixDirsPsr4;
72 }
73
74 public function getFallbackDirs()
75 {
76 return $this->fallbackDirsPsr0;
77 }
78
79 public function getFallbackDirsPsr4()
80 {
81 return $this->fallbackDirsPsr4;
82 }
83
84 public function getClassMap()
85 {
86 return $this->classMap;
87 }
88
89 /**
90 * @param array $classMap Class to filename map
91 */
92 public function addClassMap(array $classMap)
93 {
94 if ($this->classMap) {
95 $this->classMap = array_merge($this->classMap, $classMap);
96 } else {
97 $this->classMap = $classMap;
98 }
99 }
100
101 /**
102 * Registers a set of PSR-0 directories for a given prefix, either
103 * appending or prepending to the ones previously set for this prefix.
104 *
105 * @param string $prefix The prefix
106 * @param array|string $paths The PSR-0 root directories
107 * @param bool $prepend Whether to prepend the directories
108 */
109 public function add($prefix, $paths, $prepend = false)
110 {
111 if (!$prefix) {
112 if ($prepend) {
113 $this->fallbackDirsPsr0 = array_merge(
114 (array) $paths,
115 $this->fallbackDirsPsr0
116 );
117 } else {
118 $this->fallbackDirsPsr0 = array_merge(
119 $this->fallbackDirsPsr0,
120 (array) $paths
121 );
122 }
123
124 return;
125 }
126
127 $first = $prefix[0];
128 if (!isset($this->prefixesPsr0[$first][$prefix])) {
129 $this->prefixesPsr0[$first][$prefix] = (array) $paths;
130
131 return;
132 }
133 if ($prepend) {
134 $this->prefixesPsr0[$first][$prefix] = array_merge(
135 (array) $paths,
136 $this->prefixesPsr0[$first][$prefix]
137 );
138 } else {
139 $this->prefixesPsr0[$first][$prefix] = array_merge(
140 $this->prefixesPsr0[$first][$prefix],
141 (array) $paths
142 );
143 }
144 }
145
146 /**
147 * Registers a set of PSR-4 directories for a given namespace, either
148 * appending or prepending to the ones previously set for this namespace.
149 *
150 * @param string $prefix The prefix/namespace, with trailing '\\'
151 * @param array|string $paths The PSR-4 base directories
152 * @param bool $prepend Whether to prepend the directories
153 *
154 * @throws \InvalidArgumentException
155 */
156 public function addPsr4($prefix, $paths, $prepend = false)
157 {
158 if (!$prefix) {
159 // Register directories for the root namespace.
160 if ($prepend) {
161 $this->fallbackDirsPsr4 = array_merge(
162 (array) $paths,
163 $this->fallbackDirsPsr4
164 );
165 } else {
166 $this->fallbackDirsPsr4 = array_merge(
167 $this->fallbackDirsPsr4,
168 (array) $paths
169 );
170 }
171 } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
172 // Register directories for a new namespace.
173 $length = strlen($prefix);
174 if ('\\' !== $prefix[$length - 1]) {
175 throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
176 }
177 $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
178 $this->prefixDirsPsr4[$prefix] = (array) $paths;
179 } elseif ($prepend) {
180 // Prepend directories for an already registered namespace.
181 $this->prefixDirsPsr4[$prefix] = array_merge(
182 (array) $paths,
183 $this->prefixDirsPsr4[$prefix]
184 );
185 } else {
186 // Append directories for an already registered namespace.
187 $this->prefixDirsPsr4[$prefix] = array_merge(
188 $this->prefixDirsPsr4[$prefix],
189 (array) $paths
190 );
191 }
192 }
193
194 /**
195 * Registers a set of PSR-0 directories for a given prefix,
196 * replacing any others previously set for this prefix.
197 *
198 * @param string $prefix The prefix
199 * @param array|string $paths The PSR-0 base directories
200 */
201 public function set($prefix, $paths)
202 {
203 if (!$prefix) {
204 $this->fallbackDirsPsr0 = (array) $paths;
205 } else {
206 $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
207 }
208 }
209
210 /**
211 * Registers a set of PSR-4 directories for a given namespace,
212 * replacing any others previously set for this namespace.
213 *
214 * @param string $prefix The prefix/namespace, with trailing '\\'
215 * @param array|string $paths The PSR-4 base directories
216 *
217 * @throws \InvalidArgumentException
218 */
219 public function setPsr4($prefix, $paths)
220 {
221 if (!$prefix) {
222 $this->fallbackDirsPsr4 = (array) $paths;
223 } else {
224 $length = strlen($prefix);
225 if ('\\' !== $prefix[$length - 1]) {
226 throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
227 }
228 $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
229 $this->prefixDirsPsr4[$prefix] = (array) $paths;
230 }
231 }
232
233 /**
234 * Turns on searching the include path for class files.
235 *
236 * @param bool $useIncludePath
237 */
238 public function setUseIncludePath($useIncludePath)
239 {
240 $this->useIncludePath = $useIncludePath;
241 }
242
243 /**
244 * Can be used to check if the autoloader uses the include path to check
245 * for classes.
246 *
247 * @return bool
248 */
249 public function getUseIncludePath()
250 {
251 return $this->useIncludePath;
252 }
253
254 /**
255 * Turns off searching the prefix and fallback directories for classes
256 * that have not been registered with the class map.
257 *
258 * @param bool $classMapAuthoritative
259 */
260 public function setClassMapAuthoritative($classMapAuthoritative)
261 {
262 $this->classMapAuthoritative = $classMapAuthoritative;
263 }
264
265 /**
266 * Should class lookup fail if not found in the current class map?
267 *
268 * @return bool
269 */
270 public function isClassMapAuthoritative()
271 {
272 return $this->classMapAuthoritative;
273 }
274
275 /**
276 * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
277 *
278 * @param string|null $apcuPrefix
279 */
280 public function setApcuPrefix($apcuPrefix)
281 {
282 $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
283 }
284
285 /**
286 * The APCu prefix in use, or null if APCu caching is not enabled.
287 *
288 * @return string|null
289 */
290 public function getApcuPrefix()
291 {
292 return $this->apcuPrefix;
293 }
294
295 /**
296 * Registers this instance as an autoloader.
297 *
298 * @param bool $prepend Whether to prepend the autoloader or not
299 */
300 public function register($prepend = false)
301 {
302 spl_autoload_register(array($this, 'loadClass'), true, $prepend);
303 }
304
305 /**
306 * Unregisters this instance as an autoloader.
307 */
308 public function unregister()
309 {
310 spl_autoload_unregister(array($this, 'loadClass'));
311 }
312
313 /**
314 * Loads the given class or interface.
315 *
316 * @param string $class The name of the class
317 * @return bool|null True if loaded, null otherwise
318 */
319 public function loadClass($class)
320 {
321 if ($file = $this->findFile($class)) {
322 includeFile($file);
323
324 return true;
325 }
326 }
327
328 /**
329 * Finds the path to the file where the class is defined.
330 *
331 * @param string $class The name of the class
332 *
333 * @return string|false The path if found, false otherwise
334 */
335 public function findFile($class)
336 {
337 // class map lookup
338 if (isset($this->classMap[$class])) {
339 return $this->classMap[$class];
340 }
341 if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
342 return false;
343 }
344 if (null !== $this->apcuPrefix) {
345 $file = apcu_fetch($this->apcuPrefix.$class, $hit);
346 if ($hit) {
347 return $file;
348 }
349 }
350
351 $file = $this->findFileWithExtension($class, '.php');
352
353 // Search for Hack files if we are running on HHVM
354 if (false === $file && defined('HHVM_VERSION')) {
355 $file = $this->findFileWithExtension($class, '.hh');
356 }
357
358 if (null !== $this->apcuPrefix) {
359 apcu_add($this->apcuPrefix.$class, $file);
360 }
361
362 if (false === $file) {
363 // Remember that this class does not exist.
364 $this->missingClasses[$class] = true;
365 }
366
367 return $file;
368 }
369
370 private function findFileWithExtension($class, $ext)
371 {
372 // PSR-4 lookup
373 $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
374
375 $first = $class[0];
376 if (isset($this->prefixLengthsPsr4[$first])) {
377 $subPath = $class;
378 while (false !== $lastPos = strrpos($subPath, '\\')) {
379 $subPath = substr($subPath, 0, $lastPos);
380 $search = $subPath . '\\';
381 if (isset($this->prefixDirsPsr4[$search])) {
382 $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
383 foreach ($this->prefixDirsPsr4[$search] as $dir) {
384 if (file_exists($file = $dir . $pathEnd)) {
385 return $file;
386 }
387 }
388 }
389 }
390 }
391
392 // PSR-4 fallback dirs
393 foreach ($this->fallbackDirsPsr4 as $dir) {
394 if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
395 return $file;
396 }
397 }
398
399 // PSR-0 lookup
400 if (false !== $pos = strrpos($class, '\\')) {
401 // namespaced class name
402 $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
403 . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
404 } else {
405 // PEAR-like class name
406 $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
407 }
408
409 if (isset($this->prefixesPsr0[$first])) {
410 foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
411 if (0 === strpos($class, $prefix)) {
412 foreach ($dirs as $dir) {
413 if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
414 return $file;
415 }
416 }
417 }
418 }
419 }
420
421 // PSR-0 fallback dirs
422 foreach ($this->fallbackDirsPsr0 as $dir) {
423 if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
424 return $file;
425 }
426 }
427
428 // PSR-0 include paths.
429 if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
430 return $file;
431 }
432
433 return false;
434 }
435 }
436
437 /**
438 * Scope isolated include.
439 *
440 * Prevents access to $this/self from included files.
441 */
442 function includeFile($file)
443 {
444 include $file;
445 }
1
2 Copyright (c) Nils Adermann, Jordi Boggiano
3
4 Permission is hereby granted, free of charge, to any person obtaining a copy
5 of this software and associated documentation files (the "Software"), to deal
6 in the Software without restriction, including without limitation the rights
7 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 copies of the Software, and to permit persons to whom the Software is furnished
9 to do so, subject to the following conditions:
10
11 The above copyright notice and this permission notice shall be included in all
12 copies or substantial portions of the Software.
13
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 THE SOFTWARE.
21
1 <?php
2
3 // autoload_classmap.php @generated by Composer
4
5 $vendorDir = dirname(dirname(__FILE__));
6 $baseDir = dirname($vendorDir);
7
8 return array(
9 );
1 <?php
2
3 // autoload_namespaces.php @generated by Composer
4
5 $vendorDir = dirname(dirname(__FILE__));
6 $baseDir = dirname($vendorDir);
7
8 return array(
9 );
1 <?php
2
3 // autoload_psr4.php @generated by Composer
4
5 $vendorDir = dirname(dirname(__FILE__));
6 $baseDir = dirname($vendorDir);
7
8 return array(
9 'Wpo\\Services\\' => array($baseDir . '/Services'),
10 'Wpo\\' => array($baseDir . '/'),
11 );
1 <?php
2
3 // autoload_real.php @generated by Composer
4
5 class ComposerAutoloaderInitf106db3bb07a0cc7f373b495de3213f9
6 {
7 private static $loader;
8
9 public static function loadClassLoader($class)
10 {
11 if ('Composer\Autoload\ClassLoader' === $class) {
12 require __DIR__ . '/ClassLoader.php';
13 }
14 }
15
16 /**
17 * @return \Composer\Autoload\ClassLoader
18 */
19 public static function getLoader()
20 {
21 if (null !== self::$loader) {
22 return self::$loader;
23 }
24
25 spl_autoload_register(array('ComposerAutoloaderInitf106db3bb07a0cc7f373b495de3213f9', 'loadClassLoader'), true, true);
26 self::$loader = $loader = new \Composer\Autoload\ClassLoader();
27 spl_autoload_unregister(array('ComposerAutoloaderInitf106db3bb07a0cc7f373b495de3213f9', 'loadClassLoader'));
28
29 $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
30 if ($useStaticLoader) {
31 require_once __DIR__ . '/autoload_static.php';
32
33 call_user_func(\Composer\Autoload\ComposerStaticInitf106db3bb07a0cc7f373b495de3213f9::getInitializer($loader));
34 } else {
35 $map = require __DIR__ . '/autoload_namespaces.php';
36 foreach ($map as $namespace => $path) {
37 $loader->set($namespace, $path);
38 }
39
40 $map = require __DIR__ . '/autoload_psr4.php';
41 foreach ($map as $namespace => $path) {
42 $loader->setPsr4($namespace, $path);
43 }
44
45 $classMap = require __DIR__ . '/autoload_classmap.php';
46 if ($classMap) {
47 $loader->addClassMap($classMap);
48 }
49 }
50
51 $loader->register(true);
52
53 return $loader;
54 }
55 }
1 <?php
2
3 // autoload_static.php @generated by Composer
4
5 namespace Composer\Autoload;
6
7 class ComposerStaticInitf106db3bb07a0cc7f373b495de3213f9
8 {
9 public static $prefixLengthsPsr4 = array (
10 'W' =>
11 array (
12 'Wpo\\Services\\' => 13,
13 'Wpo\\' => 4,
14 ),
15 );
16
17 public static $prefixDirsPsr4 = array (
18 'Wpo\\Services\\' =>
19 array (
20 0 => __DIR__ . '/../..' . '/Services',
21 ),
22 'Wpo\\' =>
23 array (
24 0 => __DIR__ . '/../..' . '/',
25 ),
26 );
27
28 public static function getInitializer(ClassLoader $loader)
29 {
30 return \Closure::bind(function () use ($loader) {
31 $loader->prefixLengthsPsr4 = ComposerStaticInitf106db3bb07a0cc7f373b495de3213f9::$prefixLengthsPsr4;
32 $loader->prefixDirsPsr4 = ComposerStaticInitf106db3bb07a0cc7f373b495de3213f9::$prefixDirsPsr4;
33
34 }, null, ClassLoader::class);
35 }
36 }
1 <?php
2
3 /**
4 * Plugin Name: WPO365 | CUSTOM USER FIELDS
5 * Plugin URI: https://www.wpo365.com/downloads/wpo365-custom-user-fields/
6 * Description: Synchronize Azure AD user attributes e.g. department, job title etc. to WordPress user profiles (includes the PROFILE+ extension).
7 * Version: 25.1
8 * Author: support@wpo365.com
9 * Author URI: https://www.wpo365.com
10 * License: See license.txt
11 */
12
13 namespace Wpo;
14
15 require __DIR__ . '/vendor/autoload.php';
16
17 // Prevent public access to this script
18 defined('ABSPATH') or die();
19
20 if (!class_exists('\Wpo\Custom_Fields')) {
21
22 class Custom_Fields
23 {
24
25 public function __construct()
26 {
27 // Show admin notification when BASIC edition is not installed
28 add_action('admin_notices', array($this, 'ensure_wpo365_login'), 10, 0);
29 add_action('network_admin_notices', array($this, 'ensure_wpo365_login'), 10, 0);
30 add_action('plugins_loaded', array($this, 'ensure_wpo365_login'), 10, 0);
31 }
32
33 public function ensure_wpo365_login()
34 {
35 $version_exists = \class_exists('\Wpo\Core\Version') && isset(\Wpo\Core\Version::$current) && \Wpo\Core\Version::$current >= 20;
36
37 if (current_action() == 'plugins_loaded') {
38
39 if (!$version_exists) {
40
41 if (false === function_exists('deactivate_plugins')) {
42 require_once ABSPATH . 'wp-admin/includes/plugin.php';
43 }
44
45 deactivate_plugins(plugin_basename(__FILE__));
46 }
47
48 return;
49 }
50
51 $plugin_exists = \file_exists(dirname(__DIR__) . '/wpo365-login');
52
53 // Required version installed and activated
54 if ($version_exists) {
55 return;
56 }
57
58 // Required plugin not installed
59 if (!$plugin_exists) {
60 $install_url = wp_nonce_url(
61 add_query_arg(
62 array(
63 'action' => 'install-plugin',
64 'plugin' => 'wpo365-login',
65 'from' => 'plugins',
66 ),
67 self_admin_url('update.php')
68 ),
69 'install-plugin_wpo365-login'
70 );
71 echo '<div class="notice notice-error" style="margin-left: 2px;"><p>'
72 . sprintf(__('The %s plugin requires the latest version of %s to be installed and activated.', 'wpo365-login'), '<strong>WPO365 | CUSTOM USER FIELDS</strong>', '<strong>WPO365 | LOGIN (free)</strong>')
73 . '</p><p>'
74 . '<a class="button button-primary" href="' . esc_url($install_url) . '">' . __('Install plugin', 'wpo365-login') . '</a>.'
75 . '</p></div>';
76 return;
77 }
78
79 // Required plubin installed but must either be activated or upgraded
80 $activate_url = add_query_arg(
81 array(
82 '_wpnonce' => wp_create_nonce('activate-plugin_wpo365-login/wpo365-login.php'),
83 'action' => 'activate',
84 'plugin' => 'wpo365-login/wpo365-login.php',
85 ),
86 network_admin_url('plugins.php')
87 );
88
89 if (is_network_admin()) {
90 $activate_url = add_query_arg(array('networkwide' => 1), $activate_url);
91 }
92
93 $update_url = wp_nonce_url(
94 self_admin_url('update.php?action=upgrade-plugin&plugin=') . 'wpo365-login/wpo365-login.php',
95 'upgrade-plugin_wpo365-login/wpo365-login.php'
96 );
97
98 echo '<div class="notice notice-error" style="margin-left: 2px;"><p>'
99 . sprintf(__('The %s plugin requires the latest version of %s to be installed and activated.', 'wpo365-login'), '<strong>WPO365 | CUSTOM USER FIELDS</strong>', '<strong>WPO365 | LOGIN (free)</strong>')
100 . '</p><p>'
101 . '<a class="button button-primary" href="' . esc_url($activate_url) . '">' . __('Activate plugin', 'wpo365-login') . '</a>&nbsp;'
102 . '<a class="button button-primary" href="' . esc_url($update_url) . '">' . __('Update plugin', 'wpo365-login') . '</a>'
103 . '</p></div>';
104 }
105 }
106 }
107
108 $wpo365_custom_fields = new Custom_Fields();