Instantly share code, notes, and snippets.
Last active
June 9, 2023 06:36
-
Star
(2)
2
You must be signed in to star a gist -
Fork
(1)
1
You must be signed in to fork a gist
-
Save JacobDB/4a3cccd13404e015fdbcf761d6c136da to your computer and use it in GitHub Desktop.
Add any custom option to the WordPress nav menus editor. I primarily use this for mega menus, but it can be used to add basically any additional data to menus.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// add custom options to the menu editor | |
if (is_admin() && $pagenow === "nav-menus.php") { | |
// include this so we can access Walker_Nav_Menu_Edit | |
require_once ABSPATH . "wp-admin/includes/nav-menu.php"; | |
// Add the WordPress color picker styles & scripts | |
function new_site_nav_menu_color_picker() { | |
wp_enqueue_style("wp-color-picker"); | |
wp_enqueue_script("wp-color-picker"); | |
} | |
add_action("admin_enqueue_scripts", "new_site_nav_menu_color_picker"); | |
class new_site_create_custom_menu_options extends Walker_Nav_Menu_Edit { | |
static $displayed_fields = array(); | |
// create an array with all the new fields | |
static function get_custom_fields() { | |
return array( | |
array( | |
"locations" => array(), | |
"type" => "text", | |
"name" => "text_demo", | |
"label" => __("Demo text field", "new_site"), | |
"description" => "", | |
"scripts" => "", | |
"styles" => "", | |
), | |
array( | |
"locations" => array(), | |
"type" => "textarea", | |
"name" => "textarea_demo", | |
"label" => __("Demo textarea field", "new_site"), | |
"description" => __("Demo textarea field will be displayed in the menu if the current theme supports it.", "new_site"), | |
"scripts" => "", | |
"styles" => "", | |
), | |
array( | |
"locations" => array(), | |
"type" => "select", | |
"name" => "select_demo", | |
"label" => __("Demo select field", "new_site"), | |
"description" => "", | |
"scripts" => "", | |
"styles" => "", | |
"options" => array( | |
"" => "-", | |
"option_1" => __("Option 1", "new_site"), | |
"option_2" => __("Option 2", "new_site"), | |
), | |
), | |
array( | |
"locations" => array(), | |
"type" => "radio", | |
"name" => "radio_demo", | |
"label" => __("Demo radio fields", "new_site"), | |
"description" => "", | |
"scripts" => "", | |
"styles" => "", | |
"options" => array( | |
"option_1" => __("Option 1", "new_site"), | |
"option_2" => __("Option 2", "new_site"), | |
), | |
), | |
array( | |
"locations" => array(), | |
"type" => "checkbox", | |
"name" => "checkbox_demo", | |
"label" => __("Demo checkbox field", "new_site"), | |
"description" => "", | |
"scripts" => "", | |
"styles" => ".menu-item:not(.menu-item-depth-1) .field-checkbox_demo, .menu-item.menu-item-depth-0 + .menu-item.menu-item-depth-1 .field-checkbox_demo {display:none;}", | |
"value" => "true", | |
), | |
array( | |
"locations" => array(), | |
"type" => "color", | |
"name" => "color_demo", | |
"label" => __("Demo color field", "new_site"), | |
"description" => "", | |
"scripts" => " | |
(function($) { | |
$(function() { | |
$('.new_site-color-picker').wpColorPicker(); | |
}); | |
})(jQuery); | |
", | |
"styles" => "", | |
), | |
); | |
} | |
// append the new fields to the menu system | |
function start_el(&$output, $item, $depth = 0, $args = array(), $id = 0) { | |
$all_menus = get_nav_menu_locations(); | |
$assigned_menus = get_the_terms($item->ID, "nav_menu"); | |
$fields = self::get_custom_fields(); | |
$fields_markup = ""; | |
// get the menu item | |
parent::start_el($item_output, $item, $depth, $args); | |
// set up each new custom field | |
foreach ($fields as $field) { | |
// if fixed locations are set, see if the menu is assigned to that location, and if not, skip the field | |
if ($field["locations"]) { | |
$skip = true; | |
if ($all_menus) { | |
foreach ($field["locations"] as $location) { | |
if (isset($all_menus[$location])) { | |
foreach ($assigned_menus as $assigned_menu) { | |
if ($assigned_menu->term_id === $all_menus[$location]) { | |
$skip = false; continue; | |
} | |
} | |
} | |
if ($skip === false) continue; | |
} | |
} | |
if ($skip === true) continue; | |
} | |
// store the displayed fields for later use | |
if (!in_array($field["name"], self::$displayed_fields)) { | |
self::$displayed_fields[] = $field["name"]; | |
} | |
// retrieve the existing value from the database | |
$field["meta_value"] = get_post_meta($item->ID, "_menu_item_{$field["name"]}", true); | |
$fields_markup .= "<p class='field-{$field["name"]} description description-wide'>"; | |
$fields_markup .= "<label for='edit-menu-item-{$field["name"]}-{$item->ID}'>"; | |
if ($field["type"] === "text") { | |
$fields_markup .= "{$field["label"]}<br>"; | |
$fields_markup .= "<input id='edit-menu-item-{$field["name"]}-{$item->ID}' class='widefat edit-menu-item-{$field["name"]}' name='menu-item-{$field["name"]}[{$item->ID}]' value='{$field["meta_value"]}' type='text' />"; | |
} elseif ($field["type"] === "textarea") { | |
$fields_markup .= "{$field["label"]}<br>"; | |
$fields_markup .= "<textarea id='edit-menu-item-{$field["name"]}-{$item->ID}' class='widefat edit-menu-item-{$field["name"]}' rows='3' col='20' name='menu-item-{$field["name"]}[{$item->ID}]'>{$field["meta_value"]}</textarea>"; | |
} elseif ($field["type"] === "select") { | |
$fields_markup .= "{$field["label"]}<br>"; | |
$fields_markup .= "<select id='edit-menu-item-{$field["name"]}-{$item->ID}' class='widefat edit-menu-item-{$field["name"]}' rows='3' col='20' name='menu-item-{$field["name"]}[{$item->ID}]'>"; | |
foreach ($field["options"] as $value => $label) { | |
$fields_markup .= "<option value='{$value}'" . ($field["meta_value"] === $value ? " selected='selected'" : "") . ">{$label}</option>"; | |
} | |
$fields_markup .= "</select>"; | |
} elseif ($field["type"] === "radio") { | |
foreach ($field["options"] as $value => $label) { | |
$fields_markup .= "<label for='edit-menu-item-{$field["name"]}-{$item->ID}-" . sanitize_title($value) . "'>"; | |
$fields_markup .= "<input id='edit-menu-item-{$field["name"]}-{$item->ID}-" . sanitize_title($value) . "' name='menu-item-{$field["name"]}[{$item->ID}]' value='{$value}' type='radio'" . checked($field["meta_value"], $value, false) . " />"; | |
$fields_markup .= $label; | |
$fields_markup .= "</label> "; | |
} | |
} elseif ($field["type"] === "checkbox") { | |
$fields_markup .= "<input id='edit-menu-item-{$field["name"]}-{$item->ID}' name='menu-item-{$field["name"]}[{$item->ID}]' value='{$field["value"]}' type='checkbox'" . checked($field["meta_value"], $field["value"], false) . " />"; | |
$fields_markup .= $field["label"]; | |
} elseif ($field["type"] === "color") { | |
$fields_markup .= "{$field["label"]}<br>"; | |
$fields_markup .= "<span><input id='edit-menu-item-{$field["name"]}-{$item->ID}' class='widefat edit-menu-item-{$field["name"]} new_site-color-picker' name='menu-item-{$field["name"]}[{$item->ID}]' value='{$field["meta_value"]}' type='text' /></span>"; | |
} | |
if ($field["description"]) { | |
if (in_array($field["type"], array("radio", "checkbox"))) { | |
$fields_markup .= "<br>"; | |
} | |
$fields_markup .= "<span class='description'>{$field["description"]}</span>"; | |
} | |
$fields_markup .= "</label>"; | |
$fields_markup .= "</p>"; | |
} | |
// insert the new markup before the fieldset tag | |
$item_output = preg_replace("/(<fieldset)/", "{$fields_markup}$1", $item_output); | |
// update the output | |
$output .= $item_output; | |
} | |
// save the new fields | |
static function save_field_data($post_id) { | |
if (get_post_type($post_id) !== "nav_menu_item") return; | |
$post_object = get_post($post_id); | |
$custom_fields = self::get_custom_fields(); | |
foreach ($custom_fields as $field) { | |
$POST_key = "menu-item-{$field["name"]}"; | |
$meta_key = "_menu_item_{$field["name"]}"; | |
$field["value"] = isset($_POST[$POST_key][$post_id]) ? sanitize_text_field($_POST[$POST_key][$post_id]) : ""; | |
// validate the color picker | |
if ($field["type"] === "color" && $field["value"] !== "" && !preg_match("/^#[a-f0-9]{6}$/i", $field["value"])) { | |
$field["value"] = ""; | |
add_action("admin_notices", function () use ($post_object) { | |
echo "<div class='notice notice-error'><p>" . sprintf(__("Invalid HEX color code entered for '%s' [%s].", "new_site"), $post_object->post_title, $post_object->ID) . "</p></div>"; | |
}); | |
} | |
update_post_meta($post_id, $meta_key, $field["value"]); | |
} | |
} | |
// add the save function to the save_post action | |
static function setup_custom_fields() { | |
add_action("save_post", array(__CLASS__, "save_field_data")); | |
} | |
// insert field custom scripts in to the admin footer | |
static function insert_custom_scripts() { | |
$fields = self::get_custom_fields(); | |
foreach ($fields as $field) { | |
if ($field["scripts"] && in_array($field["name"], self::$displayed_fields)) { | |
echo "<script>{$field["scripts"]}</script>"; | |
} | |
} | |
} | |
// insert field custom styles in to the admin header | |
static function insert_custom_styles() { | |
$fields = self::get_custom_fields(); | |
foreach ($fields as $field) { | |
if ($field["styles"] && in_array($field["name"], self::$displayed_fields)) { | |
echo "<style>{$field["styles"]}</style>"; | |
} | |
} | |
} | |
// insert the screen options | |
static function insert_custom_screen_options($args) { | |
$fields = self::get_custom_fields(); | |
foreach ($fields as $field) { | |
if (in_array($field["name"], self::$displayed_fields)) { | |
$args[$field["name"]] = $field["label"]; | |
} | |
} | |
return $args; | |
} | |
} | |
add_action("init", array("new_site_create_custom_menu_options", "setup_custom_fields")); | |
add_filter("wp_edit_nav_menu_walker", function () { return "new_site_create_custom_menu_options"; }); | |
add_action("admin_footer", array("new_site_create_custom_menu_options", "insert_custom_scripts")); | |
add_action("admin_head", array("new_site_create_custom_menu_options", "insert_custom_styles")); | |
add_filter("manage_nav-menus_columns", array("new_site_create_custom_menu_options", "insert_custom_screen_options"), 20); | |
} |
@slopesweb huh, I forgot I had posted this gist, this method's a bit old. Here's links to my framework demonstrating how to use this, but honestly, Carbon Fields is better documented and may be a simpler route for you to take.
- Register custom fields for display in PHP: https://github.com/JacobDB/new-site/blob/7ae77d91dc9861a9621ec4bf4bee7d16eb8dbdcc/src/functions/menus.php#L572-L929
- Script to insert custom field data when inserting links: https://github.com/JacobDB/new-site/blob/7ae77d91dc9861a9621ec4bf4bee7d16eb8dbdcc/src/assets/scripts/wp-admin/menu-custom-fields.js
- Walker to modify markup based on custom fields: https://github.com/JacobDB/new-site/blob/7ae77d91dc9861a9621ec4bf4bee7d16eb8dbdcc/src/functions/menus.php#L222-L570
Unfortunately I don't have time to offer much support other than just linking you this information, but I hope this helps!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
html print ? Walker ?