MDL-69240 tool_moodlenet: Clean MoodleNet profile field
[moodle.git] / admin / tool / moodlenet / classes / profile_manager.php
CommitLineData
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
25namespace 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 */
34class 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}