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 19d13e8ab..01d4fee19 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, 2025040825.01, 'auth', 'oidc');
}
+ if ($oldversion < 2025040830.05) {
+ \auth_oidc\utils::migrate_removed_icon_choices();
+ upgrade_plugin_savepoint(true, 2025040830.05, 'auth', 'oidc');
+ }
+
return true;
}
diff --git a/auth/oidc/lang/en/auth_oidc.php b/auth/oidc/lang/en/auth_oidc.php
index c6031d7e0..5b6ff5b97 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: