diff --git a/auth/oidc/classes/adminsetting/iconselect.css b/auth/oidc/classes/adminsetting/iconselect.css index 6fe4121d4..d55e20f75 100644 --- a/auth/oidc/classes/adminsetting/iconselect.css +++ b/auth/oidc/classes/adminsetting/iconselect.css @@ -7,6 +7,7 @@ label.iconselect img { width: 25px; height: 25px; padding: 10px; + margin-right: 0; } input.iconselect { display: none; diff --git a/auth/oidc/classes/loginflow/authcode.php b/auth/oidc/classes/loginflow/authcode.php index b12867dca..5c8fac38f 100644 --- a/auth/oidc/classes/loginflow/authcode.php +++ b/auth/oidc/classes/loginflow/authcode.php @@ -59,25 +59,29 @@ public function loginpage_idp_list($wantsurl) { return []; } $showicon = isset($this->config->set_pix) ? $this->config->set_pix : true; + $name = strip_tags(format_text($this->config->opname)); $idpentry = [ 'url' => new url('/auth/oidc/', ['source' => 'loginpage']), - 'name' => strip_tags(format_text($this->config->opname)), + 'name' => $name, ]; if ($showicon) { if (!empty($this->config->customicon)) { $iconvalue = new pix_icon('0/customicon', get_string('pluginname', 'auth_oidc'), 'auth_oidc'); } else { - $icon = (!empty($this->config->icon)) ? $this->config->icon : 'auth_oidc:o365'; + $icon = (!empty($this->config->icon)) ? $this->config->icon : 'auth_oidc:microsoft_365'; $icon = explode(':', $icon); if (isset($icon[1])) { [$iconcomponent, $iconname] = $icon; } else { $iconcomponent = 'auth_oidc'; - $iconname = 'o365'; + $iconname = 'microsoft_365'; } $iconvalue = new pix_icon($iconname, get_string('pluginname', 'auth_oidc'), $iconcomponent); } $idpentry['icon'] = $iconvalue; + // Two non-breaking spaces so the button text keeps a visible gap after the icon. + // Regular spaces would be collapsed to one by normal HTML whitespace handling. + $idpentry['name'] = "\u{00A0}\u{00A0}" . $name; } return [$idpentry]; } diff --git a/auth/oidc/classes/utils.php b/auth/oidc/classes/utils.php index 5dbaf86dd..2cf09c415 100644 --- a/auth/oidc/classes/utils.php +++ b/auth/oidc/classes/utils.php @@ -28,6 +28,7 @@ use Exception; use moodle_exception; use auth_oidc\event\action_failed; +use core\context\system; use core\url; /** @@ -252,4 +253,99 @@ public static function get_openssl_internal_path() { return $CFG->dataroot . '/microsoft_certs'; } + + /** + * Migrate a site's selected stock icon to the custom icon setting if it used one of the + * icon choices that have been removed from the icon selector. + * + * The 'auth_oidc/icon' setting stores a "component:pix" identifier. The set of stock + * choices has been reduced to a handful of icons relevant to this plugin; any site that had + * selected one of the removed choices (all generic core Moodle icons) needs that icon copied + * into the custom icon file area so the login page keeps showing the same image. + * + * Safe to call more than once: once a site has been migrated (or its 'icon' setting was + * never one of the removed choices), every subsequent call is a no-op, since the checks + * above always return early once either 'auth_oidc/icon' is empty/unset or + * 'auth_oidc/customicon' is populated. The file and config writes are wrapped in a + * delegated transaction so a failure partway through can't leave those two settings out of + * sync with each other, which is what the early-return checks rely on. + */ + public static function migrate_removed_icon_choices(): void { + global $CFG, $DB; + + $currenticon = get_config('auth_oidc', 'icon'); + if (empty($currenticon)) { + return; + } + + if (!empty(get_config('auth_oidc', 'customicon'))) { + // A custom icon is already in use and takes priority, so the stock icon setting is + // not currently affecting what is displayed. Nothing to migrate. + return; + } + + // The old default icon has simply been replaced with a new image under a new pix name. + if ($currenticon === 'auth_oidc:o365') { + set_config('icon', 'auth_oidc:office_365', 'auth_oidc'); + return; + } + + $keepicons = [ + 'auth_oidc:microsoft_365', + 'auth_oidc:microsoft', + 'auth_oidc:microsoft_365_copilot', + 'auth_oidc:office_365', + 'auth_oidc:openid', + 'auth_oidc:keycloak', + ]; + if (in_array($currenticon, $keepicons, true)) { + return; + } + + $parts = explode(':', $currenticon, 2); + if (count($parts) !== 2) { + return; + } + [, $pix] = $parts; + + $sourcefile = null; + $extension = null; + foreach (['svg', 'png', 'gif', 'jpg', 'jpeg'] as $candidateextension) { + $candidatefile = "{$CFG->dirroot}/pix/{$pix}.{$candidateextension}"; + if (file_exists($candidatefile)) { + $sourcefile = $candidatefile; + $extension = $candidateextension; + break; + } + } + if ($sourcefile === null) { + // Can't locate the source image for the removed choice, so there is nothing to copy. + return; + } + + $systemcontext = system::instance(); + $fs = get_file_storage(); + $filename = 'migrated_' . clean_param(str_replace('/', '_', $pix), PARAM_FILE) . '.' . $extension; + $filerecord = [ + 'contextid' => $systemcontext->id, + 'component' => 'auth_oidc', + 'filearea' => 'customicon', + 'itemid' => 0, + 'filepath' => '/', + 'filename' => $filename, + ]; + + // Wrapped in a transaction so a failure partway through (e.g. the file write succeeding + // but a config write failing) can't leave 'icon' and 'customicon' out of sync, which + // would break the early-return guards above on any later call. + $transaction = $DB->start_delegated_transaction(); + $fs->delete_area_files($systemcontext->id, 'auth_oidc', 'customicon', 0); + $fs->create_file_from_pathname($filerecord, $sourcefile); + set_config('customicon', '/' . $filename, 'auth_oidc'); + unset_config('icon', 'auth_oidc'); + $transaction->allow_commit(); + + require_once($CFG->dirroot . '/auth/oidc/lib.php'); + auth_oidc_initialize_customicon('/' . $filename); + } } diff --git a/auth/oidc/db/upgrade.php b/auth/oidc/db/upgrade.php index ff51d77d7..de13217e2 100644 --- a/auth/oidc/db/upgrade.php +++ b/auth/oidc/db/upgrade.php @@ -586,6 +586,11 @@ function xmldb_auth_oidc_upgrade($oldversion) { upgrade_plugin_savepoint(true, 2024100730.01, 'auth', 'oidc'); } + if ($oldversion < 2024100735.05) { + \auth_oidc\utils::migrate_removed_icon_choices(); + upgrade_plugin_savepoint(true, 2024100735.05, 'auth', 'oidc'); + } + return true; } diff --git a/auth/oidc/lang/en/auth_oidc.php b/auth/oidc/lang/en/auth_oidc.php index 62da1de43..126c7f64f 100644 --- a/auth/oidc/lang/en/auth_oidc.php +++ b/auth/oidc/lang/en/auth_oidc.php @@ -119,23 +119,14 @@ $string['cfg_set_pix_desc'] = 'If enabled, displays an icon next to the provider name on the login page.'; $string['cfg_icon_key'] = 'Icon'; $string['cfg_icon_desc'] = 'An icon to display next to the provider name on the login page.'; -$string['cfg_iconalt_o365'] = 'Microsoft 365 icon'; -$string['cfg_iconalt_locked'] = 'Locked icon'; -$string['cfg_iconalt_lock'] = 'Lock icon'; -$string['cfg_iconalt_go'] = 'Green circle'; -$string['cfg_iconalt_stop'] = 'Red circle'; -$string['cfg_iconalt_user'] = 'User icon'; -$string['cfg_iconalt_user2'] = 'User icon alternate'; -$string['cfg_iconalt_key'] = 'Key icon'; -$string['cfg_iconalt_group'] = 'Group icon'; -$string['cfg_iconalt_group2'] = 'Group icon alternate'; -$string['cfg_iconalt_mnet'] = 'MNET icon'; -$string['cfg_iconalt_userlock'] = 'User with lock icon'; -$string['cfg_iconalt_plus'] = 'Plus icon'; -$string['cfg_iconalt_check'] = 'Checkmark icon'; -$string['cfg_iconalt_rightarrow'] = 'Right-facing arrow icon'; +$string['cfg_iconalt_o365'] = 'Office 365 icon'; +$string['cfg_iconalt_microsoft365'] = 'Microsoft 365 icon'; +$string['cfg_iconalt_microsoft'] = 'Microsoft icon'; +$string['cfg_iconalt_microsoft365copilot'] = 'Microsoft 365 Copilot icon'; +$string['cfg_iconalt_openid'] = 'OpenID icon'; +$string['cfg_iconalt_keycloak'] = 'Keycloak icon'; $string['cfg_customicon_key'] = 'Custom Icon'; -$string['cfg_customicon_desc'] = 'If you\'d like to use your own icon, upload it here. This overrides any icon chosen above.

Notes on using custom icons:'; +$string['cfg_customicon_desc'] = 'If you\'d like to use your own icon, upload it here. This overrides any icon chosen above.

Notes on using custom icons:'; $string['cfg_debugmode_key'] = 'Record debug messages'; $string['cfg_debugmode_desc'] = 'If enabled, information will be logged to the Moodle log that can help in identifying problems.'; $string['cfg_loginflow_key'] = 'Login Flow'; diff --git a/auth/oidc/lib.php b/auth/oidc/lib.php index 5d13a5ae0..e7eab9f73 100644 --- a/auth/oidc/lib.php +++ b/auth/oidc/lib.php @@ -84,6 +84,17 @@ */ const AUTH_OIDC_AUTH_CERT_SOURCE_FILE = 2; +/** + * File extensions accepted for the 'auth_oidc/customicon' upload setting. + * + * SVG is deliberately excluded: unlike the plugin's own bundled stock icons, this file is + * admin-uploaded and served as-is from dataroot, so allowing SVG here would let an admin + * upload active content (script/event handlers). Shared by the setting's file picker + * (settings.php) and the extension allow-list checked before copying the file into + * pix_plugins (auth_oidc_initialize_customicon()) so the two can't drift apart. + */ +const AUTH_OIDC_CUSTOMICON_ALLOWED_EXTENSIONS = ['png', 'jpg', 'gif']; + /** * Callback invoked when application credentials or endpoint settings are updated. * @@ -227,7 +238,25 @@ function auth_oidc_initialize_customicon($filefullname) { } if (file_exists($CFG->dataroot . '/pix_plugins/auth/oidc/0')) { - $file->copy_content_to($CFG->dataroot . '/pix_plugins/auth/oidc/0/customicon.jpg'); + // Remove any previously stored custom icon so a stale file with a different + // extension can't take priority when the theme resolves the icon image. + $oldiconfiles = glob($CFG->dataroot . '/pix_plugins/auth/oidc/0/customicon.*'); + foreach ($oldiconfiles ?: [] as $oldiconfile) { + // A failed unlink (e.g. permissions, or the file already being gone) isn't fatal + // here: copy_content_to() below will still overwrite/create the current extension's + // file, so at worst a stale file of a different extension is left behind. + @unlink($oldiconfile); + } + + $extension = strtolower(pathinfo($file->get_filename(), PATHINFO_EXTENSION)); + if (!in_array($extension, AUTH_OIDC_CUSTOMICON_ALLOWED_EXTENSIONS, true)) { + // Unexpected/empty extension: don't create a weird or unvalidated file under + // pix_plugins. The stale files for previously-valid extensions were already + // removed above, so this leaves no custom icon in place. + return false; + } + + $file->copy_content_to($CFG->dataroot . "/pix_plugins/auth/oidc/0/customicon.{$extension}"); theme_reset_all_caches(); } } diff --git a/auth/oidc/pix/keycloak.png b/auth/oidc/pix/keycloak.png new file mode 100644 index 000000000..48e18430c Binary files /dev/null and b/auth/oidc/pix/keycloak.png differ diff --git a/auth/oidc/pix/microsoft.svg b/auth/oidc/pix/microsoft.svg new file mode 100644 index 000000000..5334aa7ca --- /dev/null +++ b/auth/oidc/pix/microsoft.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/auth/oidc/pix/microsoft_365.svg b/auth/oidc/pix/microsoft_365.svg new file mode 100644 index 000000000..53fc9a444 --- /dev/null +++ b/auth/oidc/pix/microsoft_365.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/auth/oidc/pix/microsoft_365_copilot.svg b/auth/oidc/pix/microsoft_365_copilot.svg new file mode 100644 index 000000000..cf32dbde5 --- /dev/null +++ b/auth/oidc/pix/microsoft_365_copilot.svg @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/auth/oidc/pix/o365.png b/auth/oidc/pix/o365.png deleted file mode 100644 index 0fa022c25..000000000 Binary files a/auth/oidc/pix/o365.png and /dev/null differ diff --git a/auth/oidc/pix/office_365.svg b/auth/oidc/pix/office_365.svg new file mode 100644 index 000000000..66e8725af --- /dev/null +++ b/auth/oidc/pix/office_365.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/auth/oidc/pix/openid.svg b/auth/oidc/pix/openid.svg new file mode 100644 index 000000000..63a90e05c --- /dev/null +++ b/auth/oidc/pix/openid.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/auth/oidc/settings.php b/auth/oidc/settings.php index 2c1d9774a..c2ce94ca8 100644 --- a/auth/oidc/settings.php +++ b/auth/oidc/settings.php @@ -679,79 +679,34 @@ // Icon. $icons = [ [ - 'pix' => 'o365', - 'alt' => new lang_string('cfg_iconalt_o365', 'auth_oidc'), + 'pix' => 'microsoft_365', + 'alt' => new lang_string('cfg_iconalt_microsoft365', 'auth_oidc'), 'component' => 'auth_oidc', ], [ - 'pix' => 't/locked', - 'alt' => new lang_string('cfg_iconalt_locked', 'auth_oidc'), - 'component' => 'moodle', - ], - [ - 'pix' => 't/lock', - 'alt' => new lang_string('cfg_iconalt_lock', 'auth_oidc'), - 'component' => 'moodle', - ], - [ - 'pix' => 't/go', - 'alt' => new lang_string('cfg_iconalt_go', 'auth_oidc'), - 'component' => 'moodle', - ], - [ - 'pix' => 't/stop', - 'alt' => new lang_string('cfg_iconalt_stop', 'auth_oidc'), - 'component' => 'moodle', - ], - [ - 'pix' => 't/user', - 'alt' => new lang_string('cfg_iconalt_user', 'auth_oidc'), - 'component' => 'moodle', - ], - [ - 'pix' => 'u/user35', - 'alt' => new lang_string('cfg_iconalt_user2', 'auth_oidc'), - 'component' => 'moodle', - ], - [ - 'pix' => 'i/permissions', - 'alt' => new lang_string('cfg_iconalt_key', 'auth_oidc'), - 'component' => 'moodle', - ], - [ - 'pix' => 'i/cohort', - 'alt' => new lang_string('cfg_iconalt_group', 'auth_oidc'), - 'component' => 'moodle', - ], - [ - 'pix' => 'i/groups', - 'alt' => new lang_string('cfg_iconalt_group2', 'auth_oidc'), - 'component' => 'moodle', - ], - [ - 'pix' => 'i/mnethost', - 'alt' => new lang_string('cfg_iconalt_mnet', 'auth_oidc'), - 'component' => 'moodle', + 'pix' => 'microsoft', + 'alt' => new lang_string('cfg_iconalt_microsoft', 'auth_oidc'), + 'component' => 'auth_oidc', ], [ - 'pix' => 'i/permissionlock', - 'alt' => new lang_string('cfg_iconalt_userlock', 'auth_oidc'), - 'component' => 'moodle', + 'pix' => 'microsoft_365_copilot', + 'alt' => new lang_string('cfg_iconalt_microsoft365copilot', 'auth_oidc'), + 'component' => 'auth_oidc', ], [ - 'pix' => 't/more', - 'alt' => new lang_string('cfg_iconalt_plus', 'auth_oidc'), - 'component' => 'moodle', + 'pix' => 'office_365', + 'alt' => new lang_string('cfg_iconalt_o365', 'auth_oidc'), + 'component' => 'auth_oidc', ], [ - 'pix' => 't/approve', - 'alt' => new lang_string('cfg_iconalt_check', 'auth_oidc'), - 'component' => 'moodle', + 'pix' => 'openid', + 'alt' => new lang_string('cfg_iconalt_openid', 'auth_oidc'), + 'component' => 'auth_oidc', ], [ - 'pix' => 't/right', - 'alt' => new lang_string('cfg_iconalt_rightarrow', 'auth_oidc'), - 'component' => 'moodle', + 'pix' => 'keycloak', + 'alt' => new lang_string('cfg_iconalt_keycloak', 'auth_oidc'), + 'component' => 'auth_oidc', ], ]; $settings->add( @@ -759,7 +714,7 @@ 'auth_oidc/icon', get_string('cfg_icon_key', 'auth_oidc'), get_string('cfg_icon_desc', 'auth_oidc'), - 'auth_oidc:o365', + 'auth_oidc:microsoft_365', $icons ) ); @@ -773,7 +728,13 @@ get_string('cfg_customicon_desc', 'auth_oidc'), 'customicon', 0, - ['accepted_types' => ['.png', '.jpg', '.ico'], 'maxbytes' => get_max_upload_file_size()] + [ + 'accepted_types' => array_map( + fn ($extension) => ".{$extension}", + AUTH_OIDC_CUSTOMICON_ALLOWED_EXTENSIONS + ), + 'maxbytes' => get_max_upload_file_size(), + ] ); $customiconsetting->set_updatedcallback('auth_oidc_initialize_customicon'); $settings->add($customiconsetting); diff --git a/auth/oidc/version.php b/auth/oidc/version.php index 1df9f493a..735e66f6c 100644 --- a/auth/oidc/version.php +++ b/auth/oidc/version.php @@ -25,7 +25,7 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2024100735; +$plugin->version = 2024100735.05; $plugin->requires = 2024100700; $plugin->release = '4.5.7'; $plugin->component = 'auth_oidc';