A common pattern for a headless CMS is that you need some data on every page. Think of the WordPress site title and slogan, menus, footer content or certain CMS settings. Here we have a custom example/v1/common
endpoint created. In this endpoint we retrieve in our example all WordPress navigation menus, all Advanced Custom Fields options and all useful WordPress settings:
namespace App\Controllers;
class RestAPI
{
/**
* Register custom Rest API endpoints
*/
public static function register_api_routes()
{
register_rest_route(
'example/v1',
'/common', [
'methods' => 'GET',
'callback' => ['App\Controllers\RestAPI', 'get_common_data'],
'permission_callback' => '__return_true'
]
);
}
/**
* Get common data
*/
public static function get_common_data($request)
{
$common = [
'menus' => \App\Controllers\Menus::get_nav_menus(), // Get WP Nav Menus
'options' => get_fields('options'), // Get ACF Options
'settings' => self::get_global_settings($request) // Get WP Settings
];
return $common;
}
/**
* Get global site settings
*/
public static function get_global_settings($request)
{
$settings = [
'name' => get_option('blogname'),
'description' => get_option('blogdescription'),
'home' => \App\make_relative_url(home_url('/')),
'date_format' => get_option('date_format'),
'time_format' => get_option('time_format'),
'gmt_offset' => get_option('gmt_offset'),
'timezone' => get_option('timezone_string'),
'start_of_week' => get_option('start_of_week'),
'posts_per_page' => get_option('posts_per_page'),
'wp_max_upload_size' => wp_max_upload_size()
];
return $settings;
}
}
Call the function from your functions.php:
add_action('rest_api_init', ['App\Controllers\RestAPI', 'register_api_routes']);
To retrieve and format all Wordpress menus for a headless setup, we created a separate PHP class Menus:
namespace App\Controllers;
class Menus
{
/**
* Construct Menu Item for WP Rest response
*/
public static function add_menu_item($item = null)
{
$menu_item = [];
if (!empty($item)) {
$menu_item['ID'] = $item->ID;
$menu_item['title'] = $item->title;
$menu_item['url'] = \App\make_relative_url($item->url);
if ($item->classes) {
$menu_item['classes'] = $item->classes;
}
if ($item->description) {
$menu_item['desc'] = $item->description;
}
if ($item->target) {
$menu_item['target'] = $item->target;
}
if ($item->object) {
$menu_item['object'] = $item->object;
}
if ($item->object_id && $item->object !== 'custom') {
$post = get_post($item->object_id);
$status = $post->post_status;
$parent = $post->post_parent ? get_post($post->post_parent) : null;
$parent_post = $post->post_parent;
if ($parent) {
$parent_post = [
'id' => (int) $parent->ID,
'slug' => $parent->post_name
];
}
if ($status === 'private') {
$menu_item['url'] = get_home_url() . ($parent ? '/' . $parent->post_name : '') .'/'. $post->post_name;
}
$menu_item['object'] = [
'id' => (int) $item->object_id,
'slug' => $post->post_name,
'status' => $status,
'parent' => $parent_post
];
}
}
return $menu_item;
}
/**
* Get WP Nav Menus
*/
public static function get_nav_menus()
{
$nav_menus = [];
$locations = get_nav_menu_locations();
foreach ($locations as $name => $menu_id) {
$menu_array = wp_get_nav_menu_items($menu_id);
$menu_items = [];
if ($menu_array) {
foreach ($menu_array as $item) {
// Parents
if (empty($item->menu_item_parent)) {
$menu_items[$item->ID] = self::add_menu_item($item);
// Children
} else {
$parent_id = $item->menu_item_parent;
$parent_exist = get_post_status($parent_id) !== FALSE;
if ($parent_exist) {
$submenu = [];
$submenu[$item->ID] = self::add_menu_item($item);
$menu_items[$parent_id]['children'][] = $submenu[$item->ID];
}
}
}
$nav_menu = array_values($menu_items);
$nav_menus[$name] = $nav_menu;
}
}
return $nav_menus;
}
}
This common data is now available via the custom endpoint:
https://cms.example.test/wp-json/example/v1/common/
and depending on the number of menus and ACF options you define will look something like this:
{
"menus": {
"header_menu": [
{
"ID": 1058,
"classes": [""],
"object": {
"id": 21,
"parent": 0,
"slug": "contact",
"status": "publish"
},
"title": "Contact",
"url": "/contact/"
}
],
"main_menu": [
{
"ID": 513,
"classes": [""],
"object": {
"id": 17,
"parent": 0,
"slug": "over-ons",
"status": "publish"
},
"title": "Over ons",
"url": "/over-ons/"
},
{
"ID": 514,
"classes": [""],
"object": {
"id": 12,
"parent": 0,
"slug": "werk",
"status": "publish"
},
"title": "Werk",
"url": "/werk/"
},
{
"ID": 1063,
"children": [
{
"ID": 1072,
"classes": [""],
"object": {
"id": 25,
"parent": 0,
"slug": "visual-consultancy",
"status": "publish"
},
"title": "Visual consultancy",
"url": "/diensten/visual-consultancy/"
},
{
"ID": 1064,
"classes": [""],
"object": {
"id": 26,
"parent": 0,
"slug": "animatie",
"status": "publish"
},
"title": "Animatie",
"url": "/diensten/animatie/"
},
{
"ID": 1066,
"classes": [""],
"object": {
"id": 24,
"parent": 0,
"slug": "datavisualisatie",
"status": "publish"
},
"title": "Datavisualisatie",
"url": "/diensten/datavisualisatie/"
}
],
"classes": [""],
"object": "custom",
"title": "Diensten",
"url": "#"
},
{
"ID": 512,
"classes": [""],
"object": {
"id": 19,
"parent": 0,
"slug": "werken-bij",
"status": "publish"
},
"title": "Werken bij",
"url": "/werken-bij/"
},
{
"ID": 1062,
"classes": [""],
"object": {
"id": 1059,
"parent": 0,
"slug": "artikelen",
"status": "publish"
},
"title": "Artikelen",
"url": "/artikelen/"
},
{
"ID": 511,
"classes": [""],
"object": {
"id": 21,
"parent": 0,
"slug": "contact",
"status": "publish"
},
"title": "Contact",
"url": "/contact/"
}
],
"service_menu": [
{
"ID": 516,
"classes": [""],
"object": {
"id": 15,
"parent": 0,
"slug": "algemene-voorwaarden",
"status": "publish"
},
"title": "Algemene voorwaarden",
"url": "/algemene-voorwaarden/"
},
{
"ID": 517,
"classes": [""],
"object": {
"id": 3,
"parent": 0,
"slug": "privacybeleid",
"status": "publish"
},
"title": "Privacybeleid",
"url": "/privacybeleid/"
}
]
},
"options": {
"contact_details": {
"address": "Leidsekade 90",
"city": "Amsterdam",
"company": "My Company",
"department": "Amsterdam",
"email": "[email protected]",
"phone": "+31 20 123 45 67"
},
"four_oh_four": {
"home_link": "Terug naar home",
"title": "Deze pagina kunnen we niet vinden."
},
"social_media": {
"channels": [
{
"icon": {
"class": "fa-brands fa-instagram",
"element": "<i class=\"fa-brands fa-instagram\" aria-hidden=\"true\"></i>",
"hex": "\\f16d",
"id": "instagram",
"prefix": "fa-brands",
"style": "brands",
"unicode": ""
},
"name": "Instagram",
"url": "https://www.instagram.com/mycompany/"
},
{
"icon": {
"class": "fa-brands fa-linkedin-in",
"element": "<i class=\"fa-brands fa-linkedin-in\" aria-hidden=\"true\"></i>",
"hex": "\\f0e1",
"id": "linkedin-in",
"prefix": "fa-brands",
"style": "brands",
"unicode": ""
},
"name": "Linkedin",
"url": "https://www.linkedin.com/company/mycompany/"
},
{
"icon": {
"class": "fa-brands fa-twitter",
"element": "<i class=\"fa-brands fa-twitter\" aria-hidden=\"true\"></i>",
"hex": "\\f099",
"id": "twitter",
"prefix": "fa-brands",
"style": "brands",
"unicode": ""
},
"name": "Twitter",
"url": "https://twitter.com/mycompany/"
}
]
}
},
"settings": {
"date_format": "F j, Y",
"description": "Just another wordpress site",
"gmt_offset": 2,
"home": "/",
"name": "My Site",
"posts_per_page": "10",
"start_of_week": "1",
"time_format": "g:i a",
"timezone": "Europe/Amsterdam",
"wp_max_upload_size": 104857600
}
}