Commit | Line | Data |
---|---|---|
b1e6d8b8 JD |
1 | <?php |
2 | // This file is part of Moodle - http://moodle.org/ | |
3 | // | |
4 | // Moodle is free software: you can redistribute it and/or modify | |
5 | // it under the terms of the GNU General Public License as published by | |
6 | // the Free Software Foundation, either version 3 of the License, or | |
7 | // (at your option) any later version. | |
8 | // | |
9 | // Moodle is distributed in the hope that it will be useful, | |
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | // GNU General Public License for more details. | |
13 | // | |
14 | // You should have received a copy of the GNU General Public License | |
15 | // along with Moodle. If not, see <http://www.gnu.org/licenses/>. | |
16 | ||
17 | /** | |
18 | * Profile manager class | |
19 | * | |
20 | * @package tool_moodlenet | |
21 | * @copyright 2020 Adrian Greeve <adrian@moodle.com> | |
22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
23 | */ | |
24 | ||
25 | namespace tool_moodlenet; | |
26 | ||
27 | /** | |
28 | * Class for handling interaction with the moodlenet profile. | |
29 | * | |
30 | * @package tool_moodlenet | |
31 | * @copyright 2020 Adrian Greeve <adrian@moodle.com> | |
32 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
33 | */ | |
34 | class profile_manager { | |
35 | ||
36 | /** | |
37 | * Get the mnet profile for a user. | |
38 | * | |
39 | * @param int $userid The ID for the user to get the profile form | |
40 | * @return moodlenet_user_profile or null. | |
41 | */ | |
42 | public static function get_moodlenet_user_profile(int $userid): ?moodlenet_user_profile { | |
43 | global $CFG; | |
44 | // Check for official profile. | |
45 | if (self::official_profile_exists()) { | |
46 | $user = \core_user::get_user($userid, 'moodlenetprofile'); | |
47 | try { | |
48 | $userprofile = $user->moodlenetprofile ? $user->moodlenetprofile : ''; | |
ffa17ad1 | 49 | return (isset($user)) ? new moodlenet_user_profile(s($userprofile), $userid) : null; |
b1e6d8b8 JD |
50 | } catch (\moodle_exception $e) { |
51 | // If an exception is thrown, means there isn't a valid profile set. No need to log exception. | |
52 | return null; | |
53 | } | |
54 | } | |
55 | // Otherwise get hacked in user profile field. | |
56 | require_once($CFG->dirroot . '/user/profile/lib.php'); | |
57 | $profilefields = profile_get_user_fields_with_data($userid); | |
58 | foreach ($profilefields as $key => $field) { | |
59 | if ($field->get_category_name() == self::get_category_name() | |
60 | && $field->inputname == 'profile_field_mnetprofile') { | |
61 | try { | |
ffa17ad1 | 62 | return new moodlenet_user_profile(s($field->display_data()), $userid); |
b1e6d8b8 JD |
63 | } catch (\moodle_exception $e) { |
64 | // If an exception is thrown, means there isn't a valid profile set. No need to log exception. | |
65 | return null; | |
66 | } | |
67 | } | |
68 | } | |
69 | return null; | |
70 | } | |
71 | ||
72 | /** | |
73 | * Save the moodlenet profile. | |
74 | * | |
75 | * @param moodlenet_user_profile $moodlenetprofile The moodlenet profile to save. | |
76 | */ | |
77 | public static function save_moodlenet_user_profile(moodlenet_user_profile $moodlenetprofile): void { | |
78 | global $CFG, $DB; | |
79 | // Do some cursory checks first to see if saving is possible. | |
80 | if (self::official_profile_exists()) { | |
81 | // All good. Let's save. | |
82 | $user = \core_user::get_user($moodlenetprofile->get_userid()); | |
83 | $user->moodlenetprofile = $moodlenetprofile->get_profile_name(); | |
84 | ||
85 | require_once($CFG->dirroot . '/user/lib.php'); | |
86 | ||
87 | \user_update_user($user, false, true); | |
88 | return; | |
89 | } | |
90 | $fielddata = self::get_user_profile_field(); | |
91 | $fielddata = self::validate_and_fix_missing_profile_items($fielddata); | |
92 | // Everything should be back to normal. Let's save. | |
93 | require_once($CFG->dirroot . '/user/profile/lib.php'); | |
94 | \profile_save_custom_fields($moodlenetprofile->get_userid(), | |
95 | [$fielddata->shortname => $moodlenetprofile->get_profile_name()]); | |
96 | } | |
97 | ||
98 | /** | |
99 | * Checks to see if the required user profile fields and categories are in place. If not it regenerates them. | |
100 | * | |
101 | * @param stdClass $fielddata The moodlenet profile field. | |
102 | * @return stdClass The same moodlenet profile field, with any necessary updates made. | |
103 | */ | |
104 | private static function validate_and_fix_missing_profile_items(\stdClass $fielddata): \stdClass { | |
105 | global $DB; | |
106 | ||
107 | if (empty((array) $fielddata)) { | |
108 | // We need to regenerate the category and field to store this data. | |
109 | if (!self::check_profile_category()) { | |
110 | $categoryid = self::create_user_profile_category(); | |
111 | self::create_user_profile_text_field($categoryid); | |
112 | } else { | |
113 | // We need the category id. | |
114 | $category = $DB->get_record('user_info_category', ['name' => self::get_category_name()]); | |
115 | self::create_user_profile_text_field($category->id); | |
116 | } | |
117 | $fielddata = self::get_user_profile_field(); | |
118 | } else { | |
119 | if (!self::check_profile_category($fielddata->categoryid)) { | |
120 | $categoryid = self::create_user_profile_category(); | |
121 | // Update the field to put it back into this category. | |
122 | $fielddata->categoryid = $categoryid; | |
123 | $DB->update_record('user_info_field', $fielddata); | |
124 | } | |
125 | } | |
126 | return $fielddata; | |
127 | } | |
128 | ||
129 | /** | |
130 | * Returns the user profile field table object. | |
131 | * | |
132 | * @return stdClass the moodlenet profile table object. False if no record found. | |
133 | */ | |
134 | private static function get_user_profile_field(): \stdClass { | |
135 | global $DB; | |
136 | $fieldname = self::get_profile_field_name(); | |
137 | $record = $DB->get_record('user_info_field', ['shortname' => $fieldname]); | |
138 | return ($record) ? $record : (object) []; | |
139 | } | |
140 | ||
141 | /** | |
142 | * This reports back if the category has been deleted or the config value is different. | |
143 | * | |
144 | * @param int $categoryid The category id to check against. | |
145 | * @return bool True is the category checks out, otherwise false. | |
146 | */ | |
147 | private static function check_profile_category(int $categoryid = null): bool { | |
148 | global $DB; | |
149 | $categoryname = self::get_category_name(); | |
150 | $categorydata = $DB->get_record('user_info_category', ['name' => $categoryname]); | |
151 | if (empty($categorydata)) { | |
152 | return false; | |
153 | } | |
154 | if (isset($categoryid) && $categorydata->id != $categoryid) { | |
155 | return false; | |
156 | } | |
157 | return true; | |
158 | } | |
159 | ||
160 | /** | |
161 | * Are we using the proper user profile field to hold the mnet profile? | |
162 | * | |
163 | * @return bool True if we are using a user table field for the mnet profile. False means we are using costom profile fields. | |
164 | */ | |
165 | public static function official_profile_exists(): bool { | |
166 | global $DB; | |
167 | ||
168 | $usertablecolumns = $DB->get_columns('user', false); | |
169 | if (isset($usertablecolumns['moodlenetprofile'])) { | |
170 | return true; | |
171 | } | |
172 | return false; | |
173 | } | |
174 | ||
175 | /** | |
176 | * Gets the category name that is set for this site. | |
177 | * | |
178 | * @return string The category used to hold the moodle net profile field. | |
179 | */ | |
180 | public static function get_category_name(): string { | |
181 | return get_config('tool_moodlenet', 'profile_category'); | |
182 | } | |
183 | ||
184 | /** | |
185 | * Sets the a unique category to hold the moodle net user profile. | |
186 | * | |
187 | * @param string $categoryname The base category name to use. | |
188 | * @return string The actual name of the category to use. | |
189 | */ | |
190 | private static function set_category_name(string $categoryname): string { | |
191 | global $DB; | |
192 | ||
193 | $attemptname = $categoryname; | |
194 | ||
195 | // Check if this category already exists. | |
196 | $foundcategoryname = false; | |
197 | $i = 0; | |
198 | do { | |
199 | $category = $DB->count_records('user_info_category', ['name' => $attemptname]); | |
200 | if ($category > 0) { | |
201 | $i++; | |
202 | $attemptname = $categoryname . $i; | |
203 | } else { | |
204 | set_config('profile_category', $attemptname, 'tool_moodlenet'); | |
205 | $foundcategoryname = true; | |
206 | } | |
207 | } while (!$foundcategoryname); | |
208 | return $attemptname; | |
209 | } | |
210 | ||
211 | /** | |
212 | * Create a custom user profile category to hold our custom field. | |
213 | * | |
214 | * @return int The id of the created category. | |
215 | */ | |
216 | public static function create_user_profile_category(): int { | |
217 | global $DB; | |
218 | // No nice API to do this, so direct DB calls it is. | |
219 | $data = new \stdClass(); | |
220 | $data->sortorder = $DB->count_records('user_info_category') + 1; | |
221 | $data->name = self::set_category_name(get_string('pluginname', 'tool_moodlenet')); | |
222 | $data->id = $DB->insert_record('user_info_category', $data, true); | |
223 | ||
224 | $createdcategory = $DB->get_record('user_info_category', array('id' => $data->id)); | |
225 | \core\event\user_info_category_created::create_from_category($createdcategory)->trigger(); | |
226 | return $createdcategory->id; | |
227 | } | |
228 | ||
229 | /** | |
230 | * Sets a unique name to be used for the moodle net profile. | |
231 | * | |
232 | * @param string $fieldname The base fieldname to use. | |
233 | * @return string The actual profile field name. | |
234 | */ | |
235 | private static function set_profile_field_name(string $fieldname): string { | |
236 | global $DB; | |
237 | ||
238 | $attemptname = $fieldname; | |
239 | ||
240 | // Check if this profilefield already exists. | |
241 | $foundfieldname = false; | |
242 | $i = 0; | |
243 | do { | |
244 | $profilefield = $DB->count_records('user_info_field', ['shortname' => $attemptname]); | |
245 | if ($profilefield > 0) { | |
246 | $i++; | |
247 | $attemptname = $fieldname . $i; | |
248 | } else { | |
249 | set_config('profile_field_name', $attemptname, 'tool_moodlenet'); | |
250 | $foundfieldname = true; | |
251 | } | |
252 | } while (!$foundfieldname); | |
253 | return $attemptname; | |
254 | } | |
255 | ||
256 | /** | |
257 | * Gets the unique profile field used to hold the moodle net profile. | |
258 | * | |
259 | * @return string The profile field name being used on this site. | |
260 | */ | |
261 | public static function get_profile_field_name(): string { | |
262 | return get_config('tool_moodlenet', 'profile_field_name'); | |
263 | } | |
264 | ||
265 | ||
266 | /** | |
267 | * Create a user profile field to hold the moodlenet profile information. | |
268 | * | |
269 | * @param int $categoryid The category to put this field into. | |
270 | */ | |
271 | public static function create_user_profile_text_field(int $categoryid): void { | |
272 | global $CFG; | |
273 | ||
274 | require_once($CFG->dirroot . '/user/profile/definelib.php'); | |
275 | require_once($CFG->dirroot . '/user/profile/field/text/define.class.php'); | |
276 | ||
277 | // Add our moodlenet profile field. | |
278 | $profileclass = new \profile_define_text(); | |
279 | $data = (object) [ | |
280 | 'shortname' => self::set_profile_field_name('mnetprofile'), | |
281 | 'name' => get_string('mnetprofile', 'tool_moodlenet'), | |
282 | 'datatype' => 'text', | |
283 | 'description' => get_string('mnetprofiledesc', 'tool_moodlenet'), | |
284 | 'descriptionformat' => 1, | |
285 | 'categoryid' => $categoryid, | |
286 | 'signup' => 1, | |
287 | 'forceunique' => 1, | |
288 | 'visible' => 2, | |
289 | 'param1' => 30, | |
290 | 'param2' => 2048 | |
291 | ]; | |
292 | $profileclass->define_save($data); | |
293 | } | |
294 | ||
295 | /** | |
296 | * Given our $moodlenetprofile let's cURL the domains' WebFinger endpoint | |
297 | * | |
298 | * @param moodlenet_user_profile $moodlenetprofile The moodlenet profile to get info from. | |
299 | * @return array [bool, text, raw] | |
300 | */ | |
301 | public static function get_moodlenet_profile_link(moodlenet_user_profile $moodlenetprofile): array { | |
302 | $domain = $moodlenetprofile->get_domain(); | |
303 | $username = $moodlenetprofile->get_username(); | |
304 | ||
305 | // Assumption: All MoodleNet instance's will contain a WebFinger validation script. | |
306 | $url = "https://".$domain."/.well-known/webfinger?resource=acct:".$username."@".$domain; | |
307 | ||
308 | $curl = new \curl(); | |
309 | $options = [ | |
310 | 'CURLOPT_HEADER' => 0, | |
311 | ]; | |
312 | $content = $curl->get($url, null, $options); | |
313 | $errno = $curl->get_errno(); | |
314 | $info = $curl->get_info(); | |
315 | ||
316 | // The base cURL seems fine, let's press on. | |
317 | if (!$errno) { | |
318 | // WebFinger gave us a 404 back so the user has no droids here. | |
319 | if ($info['http_code'] >= 400) { | |
320 | if ($info['http_code'] === 404) { | |
321 | // User not found. | |
322 | return [ | |
323 | 'result' => false, | |
324 | 'message' => get_string('profilevalidationfail', 'tool_moodlenet'), | |
325 | ]; | |
326 | } else { | |
327 | // There was some other error that was not a missing account. | |
328 | return [ | |
329 | 'result' => false, | |
330 | 'message' => get_string('profilevalidationerror', 'tool_moodlenet'), | |
331 | ]; | |
332 | } | |
333 | } | |
334 | ||
335 | // We must have a valid link so give it back to the user. | |
336 | $data = json_decode($content); | |
337 | return [ | |
338 | 'result' => true, | |
339 | 'message' => get_string('profilevalidationpass', 'tool_moodlenet'), | |
340 | 'domain' => $data->aliases[0] | |
341 | ]; | |
342 | } else { | |
343 | // There was some failure in curl so report it back. | |
344 | return [ | |
345 | 'result' => false, | |
346 | 'message' => get_string('profilevalidationerror', 'tool_moodlenet'), | |
347 | ]; | |
348 | } | |
349 | } | |
350 | } |