Skip to content

Instantly share code, notes, and snippets.

@JacobDB
Last active June 9, 2023 06:36
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save JacobDB/4a3cccd13404e015fdbcf761d6c136da to your computer and use it in GitHub Desktop.
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.
<?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>&nbsp;&nbsp;";
}
} 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
Copy link

html print ? Walker ?

@JacobDB
Copy link
Author

JacobDB commented Sep 8, 2022

@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.

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