| 1 |
<?php |
| 2 |
|
| 3 |
namespace YayMailScoped\YayCommerce\AdminShell\Menu; |
| 4 |
|
| 5 |
use YayMailScoped\YayCommerce\AdminShell\Support\AdminContext; |
| 6 |
/** |
| 7 |
* Applies the intended ordering to the YayCommerce submenu. |
| 8 |
* |
| 9 |
* WordPress treats add_submenu_page()'s $position as an array INSERTION INDEX, |
| 10 |
* not an ordering rank, and silently appends any value >= the current submenu |
| 11 |
* count. Sparse positions (e.g. 10, 20, 21, 40) therefore collapse to plugin |
| 12 |
* load order. Rather than fight that at registration time (which can't be |
| 13 |
* coordinated across already-shipped scoped copies), we let every plugin |
| 14 |
* register however it likes, then reorder the FINAL assembled array once. |
| 15 |
* |
| 16 |
* Ownership: run only by the version-elected winner (via do_shell_registration), |
| 17 |
* so a single authority orders ALL submenus — including those registered by |
| 18 |
* older co-installed copies. Positions are read from a shared cross-scope map |
| 19 |
* populated by AdminShell::register_plugin(). |
| 20 |
*/ |
| 21 |
class SubmenuPositioner |
| 22 |
{ |
| 23 |
const PARENT = 'yaycommerce'; |
| 24 |
const POSITION_KEY = 'yaycommerce_admin_shell_submenu_positions'; |
| 25 |
public function init(): void |
| 26 |
{ |
| 27 |
// Priority 9999: run after every plugin (<=10) and TopLevelMenu's parent |
| 28 |
// submenu cleanup (999) have finished, so the array is complete. |
| 29 |
// Bound to both hooks so reordering also applies in Network Admin. |
| 30 |
AdminContext::bind_menu([$this, 'reorder'], 9999); |
| 31 |
} |
| 32 |
/** |
| 33 |
* Reorder $submenu['yaycommerce'] by declared position. |
| 34 |
* Lower position first; ties and unknown/null positions keep their existing |
| 35 |
* order and sort after positioned items. |
| 36 |
*/ |
| 37 |
public function reorder(): void |
| 38 |
{ |
| 39 |
global $submenu; |
| 40 |
if (empty($submenu[self::PARENT]) || !is_array($submenu[self::PARENT])) { |
| 41 |
return; |
| 42 |
} |
| 43 |
$positions = $GLOBALS[self::POSITION_KEY] ?? []; |
| 44 |
// Decorate with original index for a stable sort (usort is not stable |
| 45 |
// before PHP 8.0). Item shape: [ menu_title, capability, menu_slug, ... ]. |
| 46 |
$decorated = []; |
| 47 |
foreach ($submenu[self::PARENT] as $index => $item) { |
| 48 |
$slug = $item[2] ?? ''; |
| 49 |
$decorated[] = ['item' => $item, 'index' => $index, 'position' => $positions[$slug] ?? null]; |
| 50 |
} |
| 51 |
usort($decorated, function ($a, $b) { |
| 52 |
$pa = $a['position']; |
| 53 |
$pb = $b['position']; |
| 54 |
if (null === $pa && null === $pb) { |
| 55 |
return $a['index'] <=> $b['index']; |
| 56 |
} |
| 57 |
if (null === $pa) { |
| 58 |
return 1; |
| 59 |
// unknown/null positions last |
| 60 |
} |
| 61 |
if (null === $pb) { |
| 62 |
return -1; |
| 63 |
} |
| 64 |
if ($pa === $pb) { |
| 65 |
return $a['index'] <=> $b['index']; |
| 66 |
// stable tiebreak |
| 67 |
} |
| 68 |
return $pa <=> $pb; |
| 69 |
}); |
| 70 |
// Re-index to sequential keys; WP renders the submenu in array order. |
| 71 |
$submenu[self::PARENT] = array_values(array_column($decorated, 'item')); |
| 72 |
} |
| 73 |
} |
| 74 |
|