diff --git a/packages/web/commons/schema.php b/packages/web/commons/schema.php index d68c673b07..cb41bbed3b 100644 --- a/packages/web/commons/schema.php +++ b/packages/web/commons/schema.php @@ -7586,4 +7586,37 @@ function () { // is a service account reachable only through an issued Bearer token. "ALTER TABLE `users` " . "ADD COLUMN `uAPIOnly` ENUM('0','1') NOT NULL DEFAULT '0'", + // The Font Awesome 7 migration renamed the icon *classes* in PHP and JS, + // but six task types and one task state carry their icon as DATA -- seeded + // by steps 2907-2987 above and rendered as `fa fa-` by + // fog.task.list.js, the host and group task menus, and Task Management. + // Every one of the seven is an FA4 outline variant whose `-o` suffix FA7 + // dropped outright, so after the migration they resolve to nothing and the + // icon renders blank. The prefix is fine: `fa` is still the solid alias. + // + // Appended rather than corrected in place. Editing steps 2907-2987 would + // fix a fresh install and leave every existing one broken, because an + // install that has already run them never replays them. + // + // Guarded on the old value so an administrator who has already picked + // their own icon for one of these keeps it -- this repairs FOG's seed, it + // does not impose a choice. + "UPDATE `taskTypes` SET `ttIcon`='square-plus' " + . "WHERE `ttID`=4 AND `ttIcon`='plus-square-o'", + "UPDATE `taskTypes` SET `ttIcon`='hard-drive' " + . "WHERE `ttID`=5 AND `ttIcon`='hdd-o'", + "UPDATE `taskTypes` SET `ttIcon`='circle-arrow-down' " + . "WHERE `ttID`=15 AND `ttIcon`='arrow-circle-o-down'", + "UPDATE `taskTypes` SET `ttIcon`='circle-arrow-up' " + . "WHERE `ttID`=16 AND `ttIcon`='arrow-circle-o-up'", + // Fast/Normal/Full Wipe are read as a set. Normal already holds + // `hourglass-2`, which FA7 still resolves as an alias of hourglass-half, + // and Full holds `hourglass`, so `hourglass-start` here restores the + // progression rather than just picking any surviving hourglass. + "UPDATE `taskTypes` SET `ttIcon`='hourglass-start' " + . "WHERE `ttID`=18 AND `ttIcon`='hourglass-o'", + "UPDATE `taskTypes` SET `ttIcon`='flag' " + . "WHERE `ttID`=22 AND `ttIcon`='flag-o'", + "UPDATE `taskStates` SET `tsIcon`='bookmark' " + . "WHERE `tsID`=1 AND `tsIcon`='bookmark-o'", ]; diff --git a/packages/web/lib/fog/system.class.php b/packages/web/lib/fog/system.class.php index 6c34d5b77a..cd1b3a8c36 100644 --- a/packages/web/lib/fog/system.class.php +++ b/packages/web/lib/fog/system.class.php @@ -94,8 +94,8 @@ public function __construct() // 1.5.x carried count does, see SchemaReconciler's docstring -- is // permanently "up to date" from the updater's point of view and will // never run another indexed step, whatever this constant says. - define('FOG_SCHEMA', 360); - define('FOG_BCACHE_VER', 313); + define('FOG_SCHEMA', 367); + define('FOG_BCACHE_VER', 314); define('FOG_CLIENT_VERSION', '0.13.0'); // GH-959: iPXE lives in FOGProject/fog-ipxe and its binaries arrive as // a release asset. Pinned here rather than tracked as "latest" so a @@ -108,7 +108,7 @@ public function __construct() // installer reads this to pick which release to download, so a given // FOG release ships a known set of plugins rather than whatever the // default branch held on the day someone installed. - define('FOG_PLUGINS_VERSION', 'v1.6.15'); + define('FOG_PLUGINS_VERSION', 'v1.6.16'); // GH-850: FOG_BASE_DIR is now installer-driven. Initiator loads // commons/fogpaths.php (written from the installer's $fogprogramdir) // before the autoloader runs, so in a normal boot these are already diff --git a/packages/web/lib/pages/groupmanagement.page.php b/packages/web/lib/pages/groupmanagement.page.php index 8abe0c8413..3ee554e07c 100644 --- a/packages/web/lib/pages/groupmanagement.page.php +++ b/packages/web/lib/pages/groupmanagement.page.php @@ -2649,7 +2649,7 @@ public function groupTasks() . $id . '&type=' . $TaskType->id - . '" class="taskitem">
' . $TaskType->name diff --git a/packages/web/lib/pages/hostmanagement.page.php b/packages/web/lib/pages/hostmanagement.page.php index 2561b1ff19..f706682f16 100644 --- a/packages/web/lib/pages/hostmanagement.page.php +++ b/packages/web/lib/pages/hostmanagement.page.php @@ -3991,7 +3991,7 @@ public function hostTasks() . $id . '&type=' . $TaskType->id - . '" class="taskitem">
' . $TaskType->name diff --git a/packages/web/management/js/fog/ipxe/fog.ipxe.list.js b/packages/web/management/js/fog/ipxe/fog.ipxe.list.js index 202e38be99..42e5de22c7 100644 --- a/packages/web/management/js/fog/ipxe/fog.ipxe.list.js +++ b/packages/web/management/js/fog/ipxe/fog.ipxe.list.js @@ -42,7 +42,7 @@ } return ''; }, @@ -59,7 +59,7 @@ } return ''; }, diff --git a/packages/web/management/js/fog/task/fog.task.list.js b/packages/web/management/js/fog/task/fog.task.list.js index c6fc9eff82..bf70007e02 100644 --- a/packages/web/management/js/fog/task/fog.task.list.js +++ b/packages/web/management/js/fog/task/fog.task.list.js @@ -149,14 +149,14 @@ { render: function(data, type, row) { return row.tasktypename - + ' ' + + ' ' }, targets: 6 }, { render: function(data, type, row) { return row.taskstatename - + ' ' + + ' ' }, targets: 7 }, @@ -223,7 +223,7 @@ }, { render: function(data, type, row) { - return ''; + return ''; }, targets: 3 } @@ -273,7 +273,7 @@ { render: function(data, type, row) { return data - + ' '; }, @@ -368,7 +368,7 @@ { render: function(data, type, row) { return $.escapeHtml(data || '') - + ' '; + + ' '; }, targets: 2 }, @@ -384,7 +384,7 @@ { render: function(data, type, row) { return $.escapeHtml(data || '') - + ' '; + + ' '; }, targets: 4 }, @@ -460,14 +460,14 @@ { render: function(data, type, row) { return $.escapeHtml(data || '') - + ' '; + + ' '; }, targets: 2 }, { render: function(data, type, row) { return $.escapeHtml(data || '') - + ' '; + + ' '; }, targets: 3 }, @@ -539,7 +539,7 @@ $.escapeHtml(row.hostname || '')], ['Task', $.escapeHtml(String(row.taskid || '')) + ' — ' + $.escapeHtml(row.tasktypename || '')], ['State at the time', $.escapeHtml(row.taskstatename || '') - + ' '], + + ' '], ['Type', '' + $.escapeHtml(row.logtype || '') + ''], ['Recorded by', $.escapeHtml(row.createdBy || '')] diff --git a/tests/fontawesome7-icon-names.test.php b/tests/fontawesome7-icon-names.test.php index ea5a15eb66..d86e664fca 100644 --- a/tests/fontawesome7-icon-names.test.php +++ b/tests/fontawesome7-icon-names.test.php @@ -228,4 +228,81 @@ && (bool)preg_match("/closer: 'fas fa-xmark'/", $common) ); +// --------------------------------------------------------------------------- +// 5. Icon names that live in the DATABASE, not in a class attribute. +// +// The check above scans source for a literal `fas fa-name`, so it is blind to +// the icons FOG stores as data. taskTypes.ttIcon and taskStates.tsIcon hold a +// bare icon name -- no prefix -- seeded by commons/schema.php and rendered by +// fog.task.list.js and the host/group task menus as `fas fa-`. +// +// That blind spot shipped: the FA7 migration renamed every class in core and +// left seven seeded values on FA4 outline names FA7 dropped (plus-square-o, +// hdd-o, arrow-circle-o-down, arrow-circle-o-up, hourglass-o, flag-o and +// bookmark-o), so six task types and the Queued state rendered blank on every +// upgraded and every fresh install. Nothing in the suite could see it, because +// nothing in the suite reads the seed. +// +// Pinned against the shipped stylesheet, the same authority section 3 uses. +// The final value for an id is whichever step sets it last, so the seed is +// replayed in file order rather than collected -- taking every literal would +// fail on the historical steps that are SUPPOSED to hold the old names, which +// are deliberately never edited (an install that has run them never replays +// them, so a correction has to be appended instead). +// --------------------------------------------------------------------------- +$schemaSrc = file_get_contents($root . '/packages/web/commons/schema.php'); +// Statements in this file are written as PHP string concatenations wrapped +// across lines, so the glue is folded away first -- otherwise the pattern +// below silently sees only the steps that happen to fit on one line, which is +// every historical step and none of the appended corrections. That reads as +// the bug still being present. +$schemaSrc = preg_replace('/[\x27"]\s*\.\s*[\x27"]/', '', $schemaSrc); +$seeded = []; +$pattern = '/`(taskTypes|taskStates)`\s+SET\s+`(ttIcon|tsIcon)`\s*=\s*' + . "'([^']+)'\s+WHERE\s+`(ttID|tsID)`\s*=\s*(\d+)/i"; +if (preg_match_all($pattern, $schemaSrc, $mm, PREG_SET_ORDER)) { + foreach ($mm as $row) { + // Last write wins, exactly as a replay from step 0 would leave it. + $seeded[$row[1] . '#' . $row[5]] = $row[3]; + } +} +$t->check( + sprintf('the seeded icon names were found (%d)', count($seeded)), + count($seeded) >= 20 +); + +$deadSeed = []; +foreach ($seeded as $where => $value) { + // A stored value may carry modifiers -- taskStates 3 is + // "spinner fa-pulse fa-fw" -- and only the first token is the icon. + $name = strtok(trim($value), ' '); + if (!preg_match('/\.fa-' . preg_quote($name, '/') . '[,:{ ]/', $cssSrc)) { + $deadSeed[] = $where . ' => ' . $value; + } +} +$t->check( + sprintf( + 'every seeded icon name resolves in the shipped CSS%s', + [] === $deadSeed ? '' : ' -- DEAD: ' . implode(', ', $deadSeed) + ), + [] === $deadSeed +); + +// The renderers that compose those values must not be left on the old prefix +// either. They evade the bare-form check in section 2 because the name is +// concatenated on, so the literal ends at the quote and matches nothing. +$concat = []; +foreach ($files as $path) { + if (preg_match_all('/\bfa fa-[\x27"]/', file_get_contents($path))) { + $concat[] = str_replace($root . '/', '', $path); + } +} +$t->check( + sprintf( + 'no renderer concatenates onto the bare "fa fa-" prefix%s', + [] === $concat ? '' : ' -- FOUND: ' . implode(', ', $concat) + ), + [] === $concat +); + $t->finish();