Issues (30)

wp-rest-api-v2-menus.php (1 issue)

1
<?php
2
/*
3
Plugin Name: WP-REST-API V2 Menus
4
Version: 0.12.1
5
Description: Adding menus endpoints on WP REST API v2
6
Author: Claudio La Barbera
7
Author URI: https://thebatclaud.io
8
*/
9
10
/**
11
 * Get all registered menus
12
 * @return array List of menus with slug and description
13
 */
14
function wp_api_v2_menus_get_all_menus() {
15
	$menus = get_terms( 'nav_menu', array( 'hide_empty' => true ) );
16
17
	foreach ( $menus as $key => $menu ) {
18
		// check if there is acf installed
19
		if ( class_exists( 'acf' ) ) {
20
			$fields = get_fields( $menu );
21
			if ( ! empty( $fields ) ) {
22
				$menus[ $key ]->acf = new stdClass();
23
24
				foreach ( $fields as $field_key => $item ) {
25
					// add all acf custom fields
26
					$menus[ $key ]->acf->$field_key = $item;
27
				}
28
			}
29
		}
30
	}
31
32
	return apply_filters('wp_api_v2_menus__menus', $menus);
33
}
34
35
/**
36
 * Get all locations
37
 * @return array List of locations
38
 **/
39
40
function wp_api_v2_menu_get_all_locations() {
41
	$nav_menu_locations = get_nav_menu_locations();
42
	$locations          = new stdClass;
43
	foreach ( $nav_menu_locations as $location_slug => $menu_id ) {
44
		if ( get_term( $location_slug ) !== null ) {
45
			$locations->{$location_slug} = get_term( $location_slug );
46
		} else {
47
			$locations->{$location_slug} = new stdClass;
48
		}
49
		$locations->{$location_slug}->slug = $location_slug;
50
		$locations->{$location_slug}->menu = get_term( $menu_id );
51
	}
52
53
	return apply_filters('wp_api_v2_menus__locations', $locations);
54
}
55
56
/**
57
 * Get menu's data from his id
58
 *
59
 * @param array $data WP REST API data variable
60
 *
61
 * @return object Menu's data with his items
62
 */
63
function wp_api_v2_locations_get_menu_data( $data ) {
64
	// Create default empty object
65
	$menu = new stdClass;
66
67
	// this could be replaced with `if (has_nav_menu($data['id']))`
68
	if ( ( $locations = get_nav_menu_locations() ) && isset( $locations[ $data['id'] ] ) ) {
69
		// Replace default empty object with the location object
70
		$menu = get_term( $locations[ $data['id'] ] );
71
72
		if ( is_wp_error( $menu ) || null === $menu ) {
73
			return new WP_Error( 'not_found', 'No location has been found with this id or slug: `' . $data['id'] . '`. Please ensure you passed an existing location ID or location slug.', array( 'status' => 404 ) );
74
		}
75
76
		$menu->items = wp_api_v2_menus_get_menu_items( $locations[ $data['id'] ] );
77
	} else {
78
		return new WP_Error( 'not_found', 'No location has been found with this id or slug: `' . $data['id'] . '`. Please ensure you passed an existing location ID or location slug.', array( 'status' => 404 ) );
79
	}
80
81
	// check if there is acf installed
82
	if ( class_exists( 'acf' ) ) {
83
		$fields = get_fields( $menu );
84
		if ( ! empty( $fields ) ) {
85
			$menu->acf = new stdClass();
86
87
			foreach ( $fields as $field_key => $item ) {
88
				// add all acf custom fields
89
				$menu->acf->$field_key = $item;
90
			}
91
		}
92
	}
93
94
	return apply_filters('wp_api_v2_menus__menu', $menu);
95
}
96
97
/**
98
 * Check if a menu item is child of one of the menu's element passed as reference
99
 *
100
 * @param $parents Menu's items
101
 * @param $child Menu's item to check
102
 *
103
 * @return bool True if the parent is found, false otherwise
104
 */
105
function wp_api_v2_menus_dna_test( &$parents, $child ) {
106
	foreach ( $parents as $key => $item ) {
107
		if ( $child->menu_item_parent == $item->ID ) {
108
			if ( ! $item->child_items ) {
109
				$item->child_items = [];
110
			}
111
			array_push( $item->child_items, $child );
112
			return true;
113
		}
114
115
		if($item->child_items) {
116
			if(wp_api_v2_menus_dna_test($item->child_items, $child)) {
117
				return true;
118
			}
119
		}
120
	}
121
122
	return false;
123
}
124
125
/**
126
 * Search object in an array by ID
127
 */
128
function wp_api_v2_find_object_by_id( $array, $id ) {
129
	foreach ( $array as $element ) {
130
		if ( $id == $element->ID ) {
131
				return $element;
132
		}
133
	}
134
135
	return false;
136
}
137
138
/**
139
 * Retrieve items for a specific menu
140
 *
141
 * @param $id Menu id
142
 *
143
 * @return array List of menu items
144
 */
145
function wp_api_v2_menus_get_menu_items( $id ) {
146
	$menu_items = wp_get_nav_menu_items( $id );
147
148
    // fallback: if menu_items is null then return empty array
149
    if($menu_items === false) {
150
        return [];
151
    }
152
153
	$all_menu_items = $menu_items;
154
155
	// check if there is acf installed
156
	if ( class_exists( 'acf' ) ) {
157
		foreach ( $menu_items as $menu_key => $menu_item ) {
158
			$fields = get_fields( $menu_item->ID );
159
			if ( ! empty( $fields ) ) {
160
				$menu_items[$menu_key]->acf = new stdClass();
161
162
				foreach ( $fields as $field_key => $item ) {
163
					// add all acf custom fields
164
					$menu_items[ $menu_key ]->acf->$field_key = $item;
165
				}
166
			}
167
		}
168
	}
169
170
	// wordpress does not group child menu items with parent menu items
171
	$child_items = [];
172
	// pull all child menu items into separate object
173
	foreach ( $menu_items as $key => $item ) {
174
175
		if($item->type == 'post_type') {
176
			// add slug to menu items
177
			$slug = get_post_field( 'post_name', $item->object_id );
178
			$item->slug = $slug;
179
		} else if($item->type == 'taxonomy') {
180
			$cat = get_term($item->object_id);
0 ignored issues
show
The function get_term was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

180
			$cat = /** @scrutinizer ignore-call */ get_term($item->object_id);
Loading history...
181
			$item->slug = $cat->slug;
182
		} else if($item->type == 'post_type_archive') {
183
			$post_type_data = get_post_type_object($item->object);
184
185
			if ($post_type_data->has_archive) {
186
				$item->slug = $post_type_data->rewrite['slug'];
187
			}
188
		}
189
190
		if (isset($item->thumbnail_id) && $item->thumbnail_id) {
191
			$item->thumbnail_src = wp_get_attachment_image_url(intval($item->thumbnail_id), 'post-thumbnail');
192
		}
193
		if (isset($item->thumbnail_hover_id) && $item->thumbnail_hover_id) {
194
			$item->thumbnail_hover_src = wp_get_attachment_image_url(intval($item->thumbnail_hover_id), 'post-thumbnail');
195
		}
196
197
		if ( $item->menu_item_parent ) {
198
			array_push( $child_items, $item );
199
			unset( $menu_items[ $key ] );
200
		}
201
202
	}
203
204
	// push child items into their parent item in the original object
205
	do {
206
		foreach($child_items as $key => $child_item) {
207
			$parent = wp_api_v2_find_object_by_id( $all_menu_items, $child_item->menu_item_parent );
208
209
			if ( empty( $parent ) ) {
210
				unset($child_items[$key]);
211
			}
212
213
			else if (wp_api_v2_menus_dna_test($menu_items, $child_item)) {
214
				unset($child_items[$key]);
215
			}
216
		}
217
	} while(count($child_items));
218
219
	return apply_filters('wp_api_v2_menus__menu_items', array_values($menu_items));
220
}
221
222
/**
223
 * Get menu's data from his id.
224
 *    It ensures compatibility for previous versions when this endpoint
225
 *    was allowing locations id in place of menus id)
226
 *
227
 * @param array $data WP REST API data variable
228
 *
229
 * @return object Menu's data with his items
230
 */
231
function wp_api_v2_menus_get_menu_data( $data ) {
232
	// This ensure retro compatibility with versions `<= 0.5` when this endpoint
233
	//   was allowing locations id in place of menus id
234
	if ( has_nav_menu( $data['id'] ) ) {
235
		$menu = wp_api_v2_locations_get_menu_data( $data );
236
	} else if ( is_nav_menu( $data['id'] ) ) {
237
		if ( is_int( $data['id'] ) ) {
238
			$id = $data['id'];
239
		} else {
240
			$id = wp_get_nav_menu_object( $data['id'] );
241
		}
242
		$menu        = get_term( $id );
243
		$menu->items = wp_api_v2_menus_get_menu_items( $id );
244
	} else {
245
		return new WP_Error( 'not_found', 'No menu has been found with this id or slug: `' . $data['id'] . '`. Please ensure you passed an existing menu ID, menu slug, location ID or location slug.', array( 'status' => 404 ) );
246
	}
247
248
	// check if there is acf installed
249
	if ( class_exists( 'acf' ) ) {
250
		$fields = get_fields( $menu );
251
		if ( ! empty( $fields ) ) {
252
			$menu->acf = new stdClass();
253
254
			foreach ( $fields as $field_key => $item ) {
255
				// add all acf custom fields
256
				$menu->acf->$field_key = $item;
257
			}
258
		}
259
	}
260
261
	return apply_filters('wp_api_v2_menus__menu', $menu);
262
}
263
264
add_action( 'rest_api_init', function () {
265
	register_rest_route( 'menus/v1', '/menus', array(
266
		'methods'  => 'GET',
267
		'callback' => 'wp_api_v2_menus_get_all_menus',
268
		'permission_callback' => '__return_true'
269
	) );
270
271
	register_rest_route( 'menus/v1', '/menus/(?P<id>[a-zA-Z0-9_-]+)', array(
272
		'methods'  => 'GET',
273
		'callback' => 'wp_api_v2_menus_get_menu_data',
274
		'permission_callback' => '__return_true'
275
	) );
276
277
	register_rest_route( 'menus/v1', '/locations/(?P<id>[a-zA-Z0-9_-]+)', array(
278
		'methods'  => 'GET',
279
		'callback' => 'wp_api_v2_locations_get_menu_data',
280
		'permission_callback' => '__return_true'
281
	) );
282
283
	register_rest_route( 'menus/v1', '/locations', array(
284
		'methods'  => 'GET',
285
		'callback' => 'wp_api_v2_menu_get_all_locations',
286
		'permission_callback' => '__return_true'
287
	) );
288
} );
289