widget-manager.class.php 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245
  1. <?php
  2. if ( ! defined( 'ABSPATH' ) ) {
  3. exit; // disable direct access
  4. }
  5. if ( ! class_exists('Mega_Menu_Widget_Manager') ) :
  6. /**
  7. * Processes AJAX requests from the Mega Menu panel editor.
  8. * Also registers our widget sidebar.
  9. *
  10. * There is very little in WordPress core to help with listing, editing, saving,
  11. * deleting widgets etc so this class implements that functionality.
  12. */
  13. class Mega_Menu_Widget_Manager {
  14. /**
  15. * Constructor
  16. *
  17. * @since 1.0
  18. */
  19. public function __construct() {
  20. add_action( 'init', array( $this, 'register_sidebar' ) );
  21. add_action( 'wp_ajax_mm_edit_widget', array( $this, 'ajax_show_widget_form' ) );
  22. add_action( 'wp_ajax_mm_save_widget', array( $this, 'ajax_save_widget' ) );
  23. add_action( 'wp_ajax_mm_update_widget_columns', array( $this, 'ajax_update_widget_columns' ) );
  24. add_action( 'wp_ajax_mm_update_menu_item_columns', array( $this, 'ajax_update_menu_item_columns' ) );
  25. add_action( 'wp_ajax_mm_delete_widget', array( $this, 'ajax_delete_widget' ) );
  26. add_action( 'wp_ajax_mm_add_widget', array( $this, 'ajax_add_widget' ) );
  27. add_action( 'wp_ajax_mm_reorder_items', array( $this, 'ajax_reorder_items' ) );
  28. add_action( 'wp_ajax_mm_save_grid_data', array( $this, 'ajax_save_grid_data' ) );
  29. add_filter( 'widget_update_callback', array( $this, 'persist_mega_menu_widget_settings'), 10, 4 );
  30. add_action( 'megamenu_after_widget_add', array( $this, 'clear_caches' ) );
  31. add_action( 'megamenu_after_widget_save', array( $this, 'clear_caches' ) );
  32. add_action( 'megamenu_after_widget_delete', array( $this, 'clear_caches' ) );
  33. }
  34. /**
  35. * Depending on how a widget has been written, it may not necessarily base the new widget settings on
  36. * a copy the old settings. If this is the case, the mega menu data will be lost. This function
  37. * checks to make sure widgets persist the mega menu data when they're saved.
  38. *
  39. * @since 1.0
  40. */
  41. public function persist_mega_menu_widget_settings( $instance, $new_instance, $old_instance, $that ) {
  42. if ( isset( $old_instance["mega_menu_columns"] ) && ! isset( $new_instance["mega_menu_columns"] ) ) {
  43. $instance["mega_menu_columns"] = $old_instance["mega_menu_columns"];
  44. }
  45. if ( isset( $old_instance["mega_menu_order"] ) && ! isset( $new_instance["mega_menu_order"] ) ) {
  46. $instance["mega_menu_order"] = $old_instance["mega_menu_order"];
  47. }
  48. if ( isset( $old_instance["mega_menu_parent_menu_id"] ) && ! isset( $new_instance["mega_menu_parent_menu_id"] ) ) {
  49. $instance["mega_menu_parent_menu_id"] = $old_instance["mega_menu_parent_menu_id"];
  50. }
  51. return $instance;
  52. }
  53. /**
  54. * Create our own widget area to store all mega menu widgets.
  55. * All widgets from all menus are stored here, they are filtered later
  56. * to ensure the correct widgets show under the correct menu item.
  57. *
  58. * @since 1.0
  59. */
  60. public function register_sidebar() {
  61. register_sidebar(
  62. array(
  63. 'id' => 'mega-menu',
  64. 'name' => __("Max Mega Menu Widgets", "megamenu"),
  65. 'description' => __("This is where Max Mega Menu stores widgets that you have added to sub menus using the mega menu builder. You can edit existing widgets here, but new widgets must be added through the Mega Menu interface (under Appearance > Menus).", "megamenu")
  66. )
  67. );
  68. }
  69. /**
  70. * Display a widget settings form
  71. *
  72. * @since 1.0
  73. */
  74. public function ajax_show_widget_form() {
  75. check_ajax_referer( 'megamenu_edit' );
  76. $widget_id = sanitize_text_field( $_POST['widget_id'] );
  77. if ( ob_get_contents() ) ob_clean(); // remove any warnings or output from other plugins which may corrupt the response
  78. wp_die( trim( $this->show_widget_form( $widget_id ) ) );
  79. }
  80. /**
  81. * Save a widget
  82. *
  83. * @since 1.0
  84. */
  85. public function ajax_save_widget() {
  86. $widget_id = sanitize_text_field( $_POST['widget_id'] );
  87. $id_base = sanitize_text_field( $_POST['id_base'] );
  88. check_ajax_referer( 'megamenu_save_widget_' . $widget_id );
  89. $saved = $this->save_widget( $id_base );
  90. if ( $saved ) {
  91. $this->send_json_success( sprintf( __("Saved %s", "megamenu"), $id_base ) );
  92. } else {
  93. $this->send_json_error( sprintf( __("Failed to save %s", "megamenu"), $id_base ) );
  94. }
  95. }
  96. /**
  97. * Update the number of mega columns for a widget
  98. *
  99. * @since 1.0
  100. */
  101. public function ajax_update_widget_columns() {
  102. check_ajax_referer( 'megamenu_edit' );
  103. $widget_id = sanitize_text_field( $_POST['id'] );
  104. $columns = absint( $_POST['columns'] );
  105. $updated = $this->update_widget_columns( $widget_id, $columns );
  106. if ( $updated ) {
  107. $this->send_json_success( sprintf( __( "Updated %s (new columns: %d)", "megamenu"), $widget_id, $columns ) );
  108. } else {
  109. $this->send_json_error( sprintf( __( "Failed to update %s", "megamenu"), $widget_id ) );
  110. }
  111. }
  112. /**
  113. * Update the number of mega columns for a widget
  114. *
  115. * @since 1.0
  116. */
  117. public function ajax_update_menu_item_columns() {
  118. check_ajax_referer( 'megamenu_edit' );
  119. $id = absint( $_POST['id'] );
  120. $columns = absint( $_POST['columns'] );
  121. $updated = $this->update_menu_item_columns( $id, $columns );
  122. if ( $updated ) {
  123. $this->send_json_success( sprintf( __( "Updated %s (new columns: %d)", "megamenu"), $id, $columns ) );
  124. } else {
  125. $this->send_json_error( sprintf( __( "Failed to update %s", "megamenu"), $id ) );
  126. }
  127. }
  128. /**
  129. * Add a widget to the panel
  130. *
  131. * @since 1.0
  132. */
  133. public function ajax_add_widget() {
  134. check_ajax_referer( 'megamenu_edit' );
  135. $id_base = sanitize_text_field( $_POST['id_base'] );
  136. $menu_item_id = absint( $_POST['menu_item_id'] );
  137. $title = sanitize_text_field( $_POST['title'] );
  138. $is_grid_widget = isset( $_POST['is_grid_widget'] ) && $_POST['is_grid_widget'] == 'true';
  139. $added = $this->add_widget( $id_base, $menu_item_id, $title, $is_grid_widget );
  140. if ( $added ) {
  141. $this->send_json_success( $added );
  142. } else {
  143. $this->send_json_error( sprintf( __("Failed to add %s to %d", "megamenu"), $id_base, $menu_item_id ) );
  144. }
  145. }
  146. /**
  147. * Deletes a widget
  148. *
  149. * @since 1.0
  150. */
  151. public function ajax_delete_widget() {
  152. check_ajax_referer( 'megamenu_edit' );
  153. $widget_id = sanitize_text_field( $_POST['widget_id'] );
  154. $deleted = $this->delete_widget( $widget_id );
  155. if ( $deleted ) {
  156. $this->send_json_success( sprintf( __( "Deleted %s", "megamenu"), $widget_id ) );
  157. } else {
  158. $this->send_json_error( sprintf( __( "Failed to delete %s", "megamenu"), $widget_id ) );
  159. }
  160. }
  161. /**
  162. * Moves a widget to a new position
  163. *
  164. * @since 1.0
  165. */
  166. public function ajax_reorder_items() {
  167. check_ajax_referer( 'megamenu_edit' );
  168. $items = isset( $_POST['items'] ) ? $_POST['items'] : false;
  169. $saved = false;
  170. if ( $items ) {
  171. $moved = $this->reorder_items( $items );
  172. }
  173. if ( $moved ) {
  174. $this->send_json_success( sprintf( __( "Moved (%s)", "megamenu"), json_encode( $items ) ) );
  175. } else {
  176. $this->send_json_error( sprintf( __( "Didn't move items", "megamenu"), json_encode( $items ) ) );
  177. }
  178. }
  179. /**
  180. * Moves a widget to a new position
  181. *
  182. * @since 2.4
  183. */
  184. public function ajax_save_grid_data() {
  185. check_ajax_referer( 'megamenu_edit' );
  186. $grid = isset( $_POST['grid'] ) ? $_POST['grid'] : false;
  187. $parent_menu_item_id = absint( $_POST['parent_menu_item'] );
  188. $saved = true;
  189. $existing_settings = get_post_meta( $parent_menu_item_id, '_megamenu', true);
  190. if ( is_array( $grid ) ) {
  191. $submitted_settings = array_merge( $existing_settings, array('grid' => $grid ) );
  192. }
  193. update_post_meta( $parent_menu_item_id, '_megamenu', $submitted_settings );
  194. if ( $saved ) {
  195. $this->send_json_success( sprintf( __( "Saved (%s)", "megamenu"), json_encode( $grid ) ) );
  196. } else {
  197. $this->send_json_error( sprintf( __( "Didn't save", "megamenu"), json_encode( $grid ) ) );
  198. }
  199. }
  200. /**
  201. * Returns an object representing all widgets registered in WordPress
  202. *
  203. * @since 1.0
  204. */
  205. public function get_available_widgets() {
  206. global $wp_widget_factory;
  207. $widgets = array();
  208. foreach( $wp_widget_factory->widgets as $widget ) {
  209. $disabled_widgets = array('maxmegamenu');
  210. $disabled_widgets = apply_filters( "megamenu_incompatible_widgets", $disabled_widgets );
  211. if ( ! in_array( $widget->id_base, $disabled_widgets ) ) {
  212. $widgets[] = array(
  213. 'text' => $widget->name,
  214. 'value' => $widget->id_base
  215. );
  216. }
  217. }
  218. uasort( $widgets, array( $this, 'sort_by_text' ) );
  219. return $widgets;
  220. }
  221. /**
  222. * Sorts a 2d array by the 'text' key
  223. *
  224. * @since 1.2
  225. * @param array $a
  226. * @param array $b
  227. */
  228. function sort_by_text( $a, $b ) {
  229. return strcmp( $a['text'], $b['text'] );
  230. }
  231. /**
  232. * Sorts a 2d array by the 'order' key
  233. *
  234. * @since 2.0
  235. * @param array $a
  236. * @param array $b
  237. */
  238. function sort_by_order( $a, $b ) {
  239. if ($a['order'] == $b['order']) {
  240. return 1;
  241. }
  242. return ($a['order'] < $b['order']) ? -1 : 1;
  243. }
  244. /**
  245. * Returns an array of immediate child menu items for the current item
  246. *
  247. * @since 1.5
  248. * @return array
  249. */
  250. private function get_second_level_menu_items( $parent_menu_item_id, $menu_id ) {
  251. $items = array();
  252. // check we're using a valid menu ID
  253. if ( ! is_nav_menu( $menu_id ) ) {
  254. return $items;
  255. }
  256. $menu = wp_get_nav_menu_items( $menu_id );
  257. if ( count( $menu ) ) {
  258. foreach ( $menu as $item ) {
  259. // find the child menu items
  260. if ( $item->menu_item_parent == $parent_menu_item_id ) {
  261. $saved_settings = array_filter( (array) get_post_meta( $item->ID, '_megamenu', true ) );
  262. $settings = array_merge( Mega_Menu_Nav_Menus::get_menu_item_defaults(), $saved_settings );
  263. $items[ $item->ID ] = array(
  264. 'id' => $item->ID,
  265. 'type' => 'menu_item',
  266. 'title' => $item->title,
  267. 'columns' => $settings['mega_menu_columns'],
  268. 'order' => isset( $settings['mega_menu_order'][ $parent_menu_item_id ] ) ? $settings['mega_menu_order'][ $parent_menu_item_id ] : 0
  269. );
  270. }
  271. }
  272. }
  273. return $items;
  274. }
  275. /**
  276. * Returns an array of all widgets belonging to a specified menu item ID.
  277. *
  278. * @since 1.0
  279. * @param int $menu_item_id
  280. */
  281. public function get_widgets_for_menu_id( $parent_menu_item_id, $menu_id ) {
  282. $widgets = array();
  283. if ( $mega_menu_widgets = $this->get_mega_menu_sidebar_widgets() ) {
  284. foreach ( $mega_menu_widgets as $widget_id ) {
  285. $settings = $this->get_settings_for_widget_id( $widget_id );
  286. if ( ! isset( $settings['mega_menu_is_grid_widget'] ) && isset( $settings['mega_menu_parent_menu_id'] ) && $settings['mega_menu_parent_menu_id'] == $parent_menu_item_id ) {
  287. $name = $this->get_name_for_widget_id( $widget_id );
  288. $widgets[ $widget_id ] = array(
  289. 'id' => $widget_id,
  290. 'type' => 'widget',
  291. 'title' => $name,
  292. 'columns' => $settings['mega_menu_columns'],
  293. 'order' => isset( $settings['mega_menu_order'][ $parent_menu_item_id ] ) ? $settings['mega_menu_order'][ $parent_menu_item_id ] : 0
  294. );
  295. }
  296. }
  297. }
  298. return $widgets;
  299. }
  300. /**
  301. * Returns an array of widgets and second level menu items for a specified parent menu item.
  302. * Used to display the widgets/menu items in the mega menu builder.
  303. *
  304. * @since 2.0
  305. * @param int $parent_menu_item_id
  306. * @param int $menu_id
  307. * @return array
  308. */
  309. public function get_widgets_and_menu_items_for_menu_id( $parent_menu_item_id, $menu_id ) {
  310. $menu_items = $this->get_second_level_menu_items( $parent_menu_item_id, $menu_id );
  311. $widgets = $this->get_widgets_for_menu_id( $parent_menu_item_id, $menu_id );
  312. $items = array_merge( $menu_items, $widgets );
  313. $parent_settings = get_post_meta( $parent_menu_item_id, '_megamenu', true );
  314. $ordering = isset( $parent_settings['submenu_ordering'] ) ? $parent_settings['submenu_ordering'] : 'natural';
  315. if ( $ordering == 'forced' ) {
  316. uasort( $items, array( $this, 'sort_by_order' ) );
  317. $new_items = $items;
  318. $end_items = array();
  319. foreach ( $items as $key => $value ) {
  320. if ( $value['order'] == 0 ) {
  321. unset( $new_items[$key] );
  322. $end_items[] = $value;
  323. }
  324. }
  325. $items = array_merge( $new_items, $end_items );
  326. }
  327. return $items;
  328. }
  329. /**
  330. * Return a sorted array of grid data representing rows, columns and items within each column.
  331. *
  332. * @param int $parent_menu_item_id
  333. * @param int $menu_id
  334. * @since 2.4
  335. * @return array
  336. */
  337. public function get_grid_widgets_and_menu_items_for_menu_id( $parent_menu_item_id, $menu_id ) {
  338. $meta = get_post_meta($parent_menu_item_id, '_megamenu', true);
  339. $saved_grid = array();
  340. if ( isset( $meta['grid'] ) ) {
  341. $saved_grid = $this->populate_saved_grid_data( $parent_menu_item_id, $menu_id, $meta['grid'] );
  342. } else {
  343. // return empty row
  344. $saved_grid[0]['columns'][0]['meta']['span'] = 3;
  345. $saved_grid = $this->populate_saved_grid_data( $parent_menu_item_id, $menu_id, $saved_grid );
  346. }
  347. return $saved_grid;
  348. }
  349. /**
  350. * Ensure the widgets that are within the grid data still exist and have not been deleted (through the Widgets screen)
  351. * Ensure second level menu items saved within the grid data are still actually second level menu itms within the menu structure
  352. *
  353. * @param $saved_grid - array representing rows, columns and widgets/menu items within each column
  354. * @param $second_level_menu_items - array of second level menu items beneath the current menu item
  355. * @since 2.4
  356. * @return array
  357. */
  358. public function populate_saved_grid_data( $parent_menu_item_id, $menu_id, $saved_grid ) {
  359. $second_level_menu_items = $this->get_second_level_menu_items( $parent_menu_item_id, $menu_id );
  360. $menu_items_included = array();
  361. foreach ($saved_grid as $row => $row_data ) {
  362. if ( isset( $row_data['columns'] ) ) {
  363. foreach ( $row_data['columns'] as $col => $col_data ) {
  364. if ( isset ( $col_data['items'] ) ) {
  365. foreach ( $col_data['items'] as $key => $item ) {
  366. if ( $item['type'] == 'item' ) {
  367. $menu_items_included[] = $item['id'];
  368. $is_child_of_parent = false;
  369. foreach ( $second_level_menu_items as $menu_item ) {
  370. if ( $menu_item['id'] == $item['id'] ) {
  371. $is_child_of_parent = true;
  372. }
  373. }
  374. if ( ! $is_child_of_parent ) {
  375. unset( $saved_grid[$row]['columns'][$col]['items'][$key] ); // menu item has been deleted or moved
  376. }
  377. } else {
  378. if ( ! $this->get_name_for_widget_id( $item['id'] ) ) {
  379. unset( $saved_grid[$row]['columns'][$col]['items'][$key] ); // widget no longer exists
  380. }
  381. }
  382. }
  383. }
  384. }
  385. }
  386. }
  387. // Find any second level menu items that have been added to the menu but are not yet within the grid data
  388. $orphaned_items = array();
  389. foreach ( $second_level_menu_items as $menu_item ) {
  390. if ( ! in_array($menu_item['id'], $menu_items_included ) ) {
  391. $orphaned_items[] = $menu_item;
  392. }
  393. }
  394. if ( ! isset( $saved_grid[0]['columns'][0]['items'][0])) {
  395. $index = 0; // grid is empty
  396. } else {
  397. $index = 999; // create new row
  398. }
  399. foreach ($orphaned_items as $key => $menu_item) {
  400. $saved_grid[$index]['columns'][0]['meta']['span'] = 3;
  401. $saved_grid[$index]['columns'][0]['items'][$key] = array(
  402. 'id' => $menu_item['id'],
  403. 'type'=> 'item',
  404. 'title' => $menu_item['title'],
  405. 'description' => __("Menu Item", "megamenu")
  406. );
  407. }
  408. if ( is_admin() ) {
  409. $saved_grid = $this->populate_grid_menu_item_titles( $saved_grid, $menu_id );
  410. }
  411. return $saved_grid;
  412. }
  413. /**
  414. * Loop through the grid data and apply titles and labels to each menu item and widget.
  415. *
  416. * @param array $saved_grid
  417. * @param int $menu_id
  418. * @since 2.4
  419. * @return array
  420. */
  421. public function populate_grid_menu_item_titles( $saved_grid, $menu_id ) {
  422. $menu_items = wp_get_nav_menu_items( $menu_id );
  423. $menu_item_title_map = array();
  424. foreach ( $menu_items as $item ) {
  425. $menu_item_title_map[ $item->ID ] = $item->title;
  426. }
  427. foreach ($saved_grid as $row => $row_data ) {
  428. if ( isset( $row_data['columns'] ) ) {
  429. foreach ( $row_data['columns'] as $col => $col_data ) {
  430. if ( isset ( $col_data['items'] ) ) {
  431. foreach ( $col_data['items'] as $key => $item ) {
  432. if ( $item['type'] == 'item' ) {
  433. if ( isset( $menu_item_title_map[$item['id']] ) ) {
  434. $title = $menu_item_title_map[$item['id']];
  435. } else {
  436. $title = __("(no label)");
  437. }
  438. $saved_grid[$row]['columns'][$col]['items'][$key]['title'] = $title;
  439. $saved_grid[$row]['columns'][$col]['items'][$key]['description'] = __("Menu Item", "megamenu");
  440. } else {
  441. $saved_grid[$row]['columns'][$col]['items'][$key]['title'] = $this->get_title_for_widget_id($item['id']);
  442. $saved_grid[$row]['columns'][$col]['items'][$key]['description'] = $this->get_name_for_widget_id($item['id']);
  443. }
  444. }
  445. }
  446. }
  447. }
  448. }
  449. return $saved_grid;
  450. }
  451. /**
  452. * Returns the widget data as stored in the options table
  453. *
  454. * @since 1.8.1
  455. * @param string $widget_id
  456. */
  457. public function get_settings_for_widget_id( $widget_id ) {
  458. $id_base = $this->get_id_base_for_widget_id( $widget_id );
  459. if ( ! $id_base ) {
  460. return false;
  461. }
  462. $widget_number = $this->get_widget_number_for_widget_id( $widget_id );
  463. $current_widgets = get_option( 'widget_' . $id_base );
  464. return $current_widgets[ $widget_number ];
  465. }
  466. /**
  467. * Returns the widget ID (number)
  468. *
  469. * @since 1.0
  470. * @param string $widget_id - id_base-ID (eg meta-3)
  471. * @return int
  472. */
  473. public function get_widget_number_for_widget_id( $widget_id ) {
  474. $parts = explode( "-", $widget_id );
  475. return absint( end( $parts ) );
  476. }
  477. /**
  478. * Returns the name/title of a Widget
  479. *
  480. * @since 1.0
  481. * @param $widget_id - id_base-ID (eg meta-3)
  482. * @return string e.g. "Custom HTML" or "Text"
  483. */
  484. public function get_name_for_widget_id( $widget_id ) {
  485. global $wp_registered_widgets;
  486. if ( ! isset( $wp_registered_widgets[$widget_id] ) ) {
  487. return false;
  488. }
  489. $registered_widget = $wp_registered_widgets[$widget_id];
  490. return $registered_widget['name'];
  491. }
  492. /**
  493. * Returns the title of a Widget
  494. *
  495. * @since 2.4
  496. * @param $widget_id - id_base-ID (eg meta-3)
  497. */
  498. public function get_title_for_widget_id( $widget_id ) {
  499. $instance = $this->get_settings_for_widget_id( $widget_id );
  500. if ( isset( $instance['title'] ) && strlen( $instance['title'] ) ) {
  501. return $instance['title'];
  502. }
  503. return $this->get_name_for_widget_id( $widget_id );
  504. }
  505. /**
  506. * Returns the id_base value for a Widget ID
  507. *
  508. * @since 1.0
  509. */
  510. public function get_id_base_for_widget_id( $widget_id ) {
  511. global $wp_registered_widget_controls;
  512. if ( ! isset( $wp_registered_widget_controls[ $widget_id ] ) ) {
  513. return false;
  514. }
  515. $control = $wp_registered_widget_controls[ $widget_id ];
  516. $id_base = isset( $control['id_base'] ) ? $control['id_base'] : $control['id'];
  517. return $id_base;
  518. }
  519. /**
  520. * Returns the HTML for a single widget instance.
  521. *
  522. * @since 1.0
  523. * @param string widget_id Something like meta-3
  524. */
  525. public function show_widget( $id ) {
  526. global $wp_registered_widgets;
  527. $params = array_merge(
  528. array( array_merge( array( 'widget_id' => $id, 'widget_name' => $wp_registered_widgets[$id]['name'] ) ) ),
  529. (array) $wp_registered_widgets[$id]['params']
  530. );
  531. $params[0]['id'] = 'mega-menu';
  532. $params[0]['before_title'] = apply_filters( "megamenu_before_widget_title", '<h4 class="mega-block-title">', $wp_registered_widgets[$id] );
  533. $params[0]['after_title'] = apply_filters( "megamenu_after_widget_title", '</h4>', $wp_registered_widgets[$id] );
  534. $params[0]['before_widget'] = apply_filters( "megamenu_before_widget", "", $wp_registered_widgets[$id] );
  535. $params[0]['after_widget'] = apply_filters( "megamenu_after_widget", "", $wp_registered_widgets[$id] );
  536. if ( defined("MEGAMENU_DYNAMIC_SIDEBAR_PARAMS") && MEGAMENU_DYNAMIC_SIDEBAR_PARAMS ) {
  537. $params[0]['before_widget'] = apply_filters( "megamenu_before_widget", '<div id="" class="">', $wp_registered_widgets[$id] );
  538. $params[0]['after_widget'] = apply_filters( "megamenu_after_widget", '</div>', $wp_registered_widgets[$id] );
  539. $params = apply_filters('dynamic_sidebar_params', $params);
  540. }
  541. $callback = $wp_registered_widgets[$id]['callback'];
  542. if ( is_callable( $callback ) ) {
  543. ob_start();
  544. call_user_func_array( $callback, $params );
  545. return ob_get_clean();
  546. }
  547. }
  548. /**
  549. * Returns the class name for a widget instance.
  550. *
  551. * @since 1.8.1
  552. * @param string widget_id Something like meta-3
  553. */
  554. public function get_widget_class( $id ) {
  555. global $wp_registered_widgets;
  556. if ( isset ( $wp_registered_widgets[$id]['classname'] ) ) {
  557. return $wp_registered_widgets[$id]['classname'];
  558. }
  559. return "";
  560. }
  561. /**
  562. * Shows the widget edit form for the specified widget.
  563. *
  564. * @since 1.0
  565. * @param $widget_id - id_base-ID (eg meta-3)
  566. */
  567. public function show_widget_form( $widget_id ) {
  568. global $wp_registered_widget_controls;
  569. $control = $wp_registered_widget_controls[ $widget_id ];
  570. $id_base = $this->get_id_base_for_widget_id( $widget_id );
  571. $widget_number = $this->get_widget_number_for_widget_id( $widget_id );
  572. $nonce = wp_create_nonce('megamenu_save_widget_' . $widget_id);
  573. ?>
  574. <form method='post'>
  575. <input type="hidden" name="widget-id" class="widget-id" value="<?php echo esc_attr( $widget_id ); ?>" />
  576. <input type='hidden' name='action' value='mm_save_widget' />
  577. <input type='hidden' name='id_base' class="id_base" value='<?php echo esc_attr( $id_base ); ?>' />
  578. <input type='hidden' name='widget_id' value='<?php echo esc_attr( $widget_id ) ?>' />
  579. <input type='hidden' name='_wpnonce' value='<?php echo esc_attr( $nonce ) ?>' />
  580. <div class='widget-content'>
  581. <?php
  582. if ( is_callable( $control['callback'] ) ) {
  583. call_user_func_array( $control['callback'], $control['params'] );
  584. }
  585. ?>
  586. <div class='widget-controls'>
  587. <a class='delete' href='#delete'><?php _e("Delete", "megamenu"); ?></a> |
  588. <a class='close' href='#close'><?php _e("Close", "megamenu"); ?></a>
  589. </div>
  590. <?php
  591. submit_button( __( 'Save' ), 'button-primary alignright', 'savewidget', false );
  592. ?>
  593. </div>
  594. </form>
  595. <?php
  596. }
  597. /**
  598. * Saves a widget. Calls the update callback on the widget.
  599. * The callback inspects the post values and updates all widget instances which match the base ID.
  600. *
  601. * @since 1.0
  602. * @param string $id_base - e.g. 'meta'
  603. * @return bool
  604. */
  605. public function save_widget( $id_base ) {
  606. global $wp_registered_widget_updates;
  607. $control = $wp_registered_widget_updates[$id_base];
  608. if ( is_callable( $control['callback'] ) ) {
  609. call_user_func_array( $control['callback'], $control['params'] );
  610. do_action( "megamenu_after_widget_save" );
  611. return true;
  612. }
  613. return false;
  614. }
  615. /**
  616. * Adds a widget to WordPress. First creates a new widget instance, then
  617. * adds the widget instance to the mega menu widget sidebar area.
  618. *
  619. * @since 1.0
  620. * @param string $id_base
  621. * @param int $menu_item_id
  622. * @param string $title
  623. */
  624. public function add_widget( $id_base, $menu_item_id, $title, $is_grid_widget ) {
  625. require_once( ABSPATH . 'wp-admin/includes/widgets.php' );
  626. $next_id = next_widget_id_number( $id_base );
  627. $this->add_widget_instance( $id_base, $next_id, $menu_item_id, $is_grid_widget );
  628. $widget_id = $this->add_widget_to_sidebar( $id_base, $next_id );
  629. $return = '<div class="widget" title="' . esc_attr( $title ) . '" data-columns="2" id="' . $widget_id . '" data-type="widget" data-id="' . $widget_id . '">';
  630. $return .= ' <div class="widget-top">';
  631. $return .= ' <div class="widget-title-action">';
  632. if ( ! $is_grid_widget ) {
  633. $return .= ' <a class="widget-option widget-contract" title="' . esc_attr( __("Contract", "megamenu") ) . '"></a>';
  634. $return .= ' <span class="widget-cols"><span class="widget-num-cols">2</span><span class="widget-of">/</span><span class="widget-total-cols">X</span></span>';
  635. $return .= ' <a class="widget-option widget-expand" title="' . esc_attr( __("Expand", "megamenu") ) . '"></a>';
  636. }
  637. $return .= ' <a class="widget-option widget-action" title="' . esc_attr( __("Edit", "megamenu") ) . '"></a>';
  638. $return .= ' </div>';
  639. $return .= ' <div class="widget-title">';
  640. $return .= ' <h4>' . esc_html( $title ) . '</h4>';
  641. if ( $is_grid_widget ) {
  642. $return .= ' <span class="widget-desc">' . esc_html( $title ) . '</span>';
  643. }
  644. $return .= ' </div>';
  645. $return .= ' </div>';
  646. $return .= ' <div class="widget-inner widget-inside"></div>';
  647. $return .= '</div>';
  648. return $return;
  649. }
  650. /**
  651. * Adds a new widget instance of the specified base ID to the database.
  652. *
  653. * @since 1.0
  654. * @param string $id_base
  655. * @param int $next_id
  656. * @param int $menu_item_id
  657. */
  658. private function add_widget_instance( $id_base, $next_id, $menu_item_id, $is_grid_widget ) {
  659. $current_widgets = get_option( 'widget_' . $id_base );
  660. $current_widgets[ $next_id ] = array(
  661. "mega_menu_columns" => 2,
  662. "mega_menu_parent_menu_id" => $menu_item_id
  663. );
  664. if ( $is_grid_widget ) {
  665. $current_widgets[ $next_id ] = array(
  666. "mega_menu_is_grid_widget" => 'true'
  667. );
  668. }
  669. update_option( 'widget_' . $id_base, $current_widgets );
  670. }
  671. /**
  672. * Removes a widget instance from the database
  673. *
  674. * @since 1.0
  675. * @param string $widget_id e.g. meta-3
  676. * @return bool. True if widget has been deleted.
  677. */
  678. private function remove_widget_instance( $widget_id ) {
  679. $id_base = $this->get_id_base_for_widget_id( $widget_id );
  680. $widget_number = $this->get_widget_number_for_widget_id( $widget_id );
  681. // add blank widget
  682. $current_widgets = get_option( 'widget_' . $id_base );
  683. if ( isset( $current_widgets[ $widget_number ] ) ) {
  684. unset( $current_widgets[ $widget_number ] );
  685. update_option( 'widget_' . $id_base, $current_widgets );
  686. return true;
  687. }
  688. return false;
  689. }
  690. /**
  691. * Updates the number of mega columns for a specified widget.
  692. *
  693. * @since 1.0
  694. * @param string $widget_id
  695. * @param int $columns
  696. */
  697. public function update_widget_columns( $widget_id, $columns ) {
  698. $id_base = $this->get_id_base_for_widget_id( $widget_id );
  699. $widget_number = $this->get_widget_number_for_widget_id( $widget_id );
  700. $current_widgets = get_option( 'widget_' . $id_base );
  701. $current_widgets[ $widget_number ]["mega_menu_columns"] = absint( $columns) ;
  702. update_option( 'widget_' . $id_base, $current_widgets );
  703. do_action( "megamenu_after_widget_save" );
  704. return true;
  705. }
  706. /**
  707. * Updates the number of mega columns for a specified widget.
  708. *
  709. * @since 1.10
  710. * @param string $menu_item_id
  711. * @param int $columns
  712. */
  713. public function update_menu_item_columns( $menu_item_id, $columns ) {
  714. $settings = get_post_meta( $menu_item_id, '_megamenu', true);
  715. $settings['mega_menu_columns'] = absint( $columns );
  716. update_post_meta( $menu_item_id, '_megamenu', $settings );
  717. return true;
  718. }
  719. /**
  720. * Updates the order of a specified widget.
  721. *
  722. * @since 1.10
  723. * @param string $widget_id
  724. * @param int $columns
  725. */
  726. public function update_widget_order( $widget_id, $order, $parent_menu_item_id ) {
  727. $id_base = $this->get_id_base_for_widget_id( $widget_id );
  728. $widget_number = $this->get_widget_number_for_widget_id( $widget_id );
  729. $current_widgets = get_option( 'widget_' . $id_base );
  730. $current_widgets[ $widget_number ]["mega_menu_order"] = array( $parent_menu_item_id => absint( $order ) );
  731. update_option( 'widget_' . $id_base, $current_widgets );
  732. return true;
  733. }
  734. /**
  735. * Updates the order of a specified menu item.
  736. *
  737. * @since 1.10
  738. * @param string $menu_item_id
  739. * @param int $order
  740. */
  741. public function update_menu_item_order( $menu_item_id, $order, $parent_menu_item_id ) {
  742. $submitted_settings['mega_menu_order'] = array( $parent_menu_item_id => absint( $order ) );
  743. $existing_settings = get_post_meta( $menu_item_id, '_megamenu', true);
  744. if ( is_array( $existing_settings ) ) {
  745. $submitted_settings = array_merge( $existing_settings, $submitted_settings );
  746. }
  747. update_post_meta( $menu_item_id, '_megamenu', $submitted_settings );
  748. return true;
  749. }
  750. /**
  751. * Deletes a widget from WordPress
  752. *
  753. * @since 1.0
  754. * @param string $widget_id e.g. meta-3
  755. */
  756. public function delete_widget( $widget_id ) {
  757. $this->remove_widget_from_sidebar( $widget_id );
  758. $this->remove_widget_instance( $widget_id );
  759. do_action( "megamenu_after_widget_delete" );
  760. return true;
  761. }
  762. /**
  763. * Moves a widget from one position to another.
  764. *
  765. * @since 1.10
  766. * @param array $items
  767. * @return string $widget_id. The widget that has been moved.
  768. */
  769. public function reorder_items( $items ) {
  770. foreach ( $items as $item ) {
  771. if ( $item['parent_menu_item'] ) {
  772. $submitted_settings = array( 'submenu_ordering' => 'forced' );
  773. $existing_settings = get_post_meta( $item['parent_menu_item'], '_megamenu', true );
  774. if ( is_array( $existing_settings ) ) {
  775. $submitted_settings = array_merge( $existing_settings, $submitted_settings );
  776. }
  777. update_post_meta( $item['parent_menu_item'], '_megamenu', $submitted_settings );
  778. }
  779. if ( $item['type'] == 'widget' ) {
  780. $this->update_widget_order( $item['id'], $item['order'], $item['parent_menu_item'] );
  781. }
  782. if ( $item['type'] == 'menu_item' ) {
  783. $this->update_menu_item_order( $item['id'], $item['order'], $item['parent_menu_item'] );
  784. }
  785. }
  786. return true;
  787. }
  788. /**
  789. * Adds a widget to the Mega Menu widget sidebar
  790. *
  791. * @since 1.0
  792. */
  793. private function add_widget_to_sidebar( $id_base, $next_id ) {
  794. $widget_id = $id_base . '-' . $next_id;
  795. $sidebar_widgets = $this->get_mega_menu_sidebar_widgets();
  796. $sidebar_widgets[] = $widget_id;
  797. $this->set_mega_menu_sidebar_widgets($sidebar_widgets);
  798. do_action( "megamenu_after_widget_add" );
  799. return $widget_id;
  800. }
  801. /**
  802. * Removes a widget from the Mega Menu widget sidebar
  803. *
  804. * @since 1.0
  805. * @return string The widget that was removed
  806. */
  807. private function remove_widget_from_sidebar($widget_id) {
  808. $widgets = $this->get_mega_menu_sidebar_widgets();
  809. $new_mega_menu_widgets = array();
  810. foreach ( $widgets as $widget ) {
  811. if ( $widget != $widget_id )
  812. $new_mega_menu_widgets[] = $widget;
  813. }
  814. $this->set_mega_menu_sidebar_widgets($new_mega_menu_widgets);
  815. return $widget_id;
  816. }
  817. /**
  818. * Returns an unfiltered array of all widgets in our sidebar
  819. *
  820. * @since 1.0
  821. * @return array
  822. */
  823. public function get_mega_menu_sidebar_widgets() {
  824. $sidebar_widgets = wp_get_sidebars_widgets();
  825. if ( ! isset( $sidebar_widgets[ 'mega-menu'] ) ) {
  826. return false;
  827. }
  828. return $sidebar_widgets[ 'mega-menu' ];
  829. }
  830. /**
  831. * Sets the sidebar widgets
  832. *
  833. * @since 1.0
  834. */
  835. private function set_mega_menu_sidebar_widgets( $widgets ) {
  836. $sidebar_widgets = wp_get_sidebars_widgets();
  837. $sidebar_widgets[ 'mega-menu' ] = $widgets;
  838. wp_set_sidebars_widgets( $sidebar_widgets );
  839. }
  840. /**
  841. * Clear the cache when the Mega Menu is updated.
  842. *
  843. * @since 1.0
  844. */
  845. public function clear_caches() {
  846. // https://wordpress.org/plugins/widget-output-cache/
  847. if ( function_exists( 'menu_output_cache_bump' ) ) {
  848. menu_output_cache_bump();
  849. }
  850. // https://wordpress.org/plugins/widget-output-cache/
  851. if ( function_exists( 'widget_output_cache_bump' ) ) {
  852. widget_output_cache_bump();
  853. }
  854. }
  855. /**
  856. * Send JSON response.
  857. *
  858. * Remove any warnings or output from other plugins which may corrupt the response
  859. *
  860. * @param string $json
  861. * @since 1.8
  862. */
  863. public function send_json_success( $json ) {
  864. if ( ob_get_contents() ) ob_clean();
  865. wp_send_json_success( $json );
  866. }
  867. /**
  868. * Send JSON response.
  869. *
  870. * Remove any warnings or output from other plugins which may corrupt the response
  871. *
  872. * @param string $json
  873. * @since 1.8
  874. */
  875. public function send_json_error( $json ) {
  876. if ( ob_get_contents() ) ob_clean();
  877. wp_send_json_error( $json );
  878. }
  879. }
  880. endif;