PageRenderTime 57ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/wp-content/plugins/admin-menu-editor/includes/menu-editor-core.php

https://bitbucket.org/lgorence/quickpress
PHP | 1416 lines | 869 code | 192 blank | 355 comment | 138 complexity | 5909a5eb682125f7008750439bdc1bdd MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1, AGPL-1.0
  1. <?php
  2. //Can't have two different versions of the plugin active at the same time. It would be incredibly buggy.
  3. if (class_exists('WPMenuEditor')){
  4. trigger_error(
  5. 'Another version of Admin Menu Editor is already active. Please deactivate it before activating this one.',
  6. E_USER_ERROR
  7. );
  8. }
  9. //Load the "framework"
  10. require 'shadow_plugin_framework.php';
  11. if ( !class_exists('WPMenuEditor') ) :
  12. class WPMenuEditor extends MenuEd_ShadowPluginFramework {
  13. protected $default_wp_menu = null; //Holds the default WP menu for later use in the editor
  14. protected $default_wp_submenu = null; //Holds the default WP menu for later use
  15. private $filtered_wp_menu = null; //The final, ready-for-display top-level menu and sub-menu.
  16. private $filtered_wp_submenu = null;
  17. protected $title_lookups = array(); //A list of page titles indexed by $item['file']. Used to
  18. //fix the titles of moved plugin pages.
  19. private $custom_menu = null; //The current custom menu with defaults merged in
  20. public $menu_format_version = 4;
  21. private $templates = null; //Template arrays for various menu structures. See the constructor for details.
  22. //Our personal copy of the request vars, without any "magic quotes".
  23. private $post = array();
  24. private $get = array();
  25. function init(){
  26. //Determine if the plugin is active network-wide (i.e. either installed in
  27. //the /mu-plugins/ directory or activated "network wide" by the super admin.
  28. if ( $this->is_super_plugin() ){
  29. $this->sitewide_options = true;
  30. }
  31. //Set some plugin-specific options
  32. if ( empty($this->option_name) ){
  33. $this->option_name = 'ws_menu_editor';
  34. }
  35. $this->defaults = array(
  36. 'hide_advanced_settings' => true,
  37. 'menu_format_version' => 0,
  38. 'display_survey_notice' => true,
  39. 'first_install_time' => null,
  40. );
  41. $this->serialize_with_json = false; //(Don't) store the options in JSON format
  42. $this->settings_link = 'options-general.php?page=menu_editor';
  43. $this->magic_hooks = true;
  44. $this->magic_hook_priority = 99999;
  45. //Build some template arrays
  46. $this->templates['basic_defaults'] = array(
  47. 'page_title' => '',
  48. 'menu_title' => '',
  49. 'access_level' => 'read',
  50. 'file' => '',
  51. 'css_class' => '',
  52. 'hookname' => '',
  53. 'icon_url' => '',
  54. 'position' => 0,
  55. 'separator' => false,
  56. 'custom' => false,
  57. 'open_in' => 'same_window', //'new_window', 'iframe' or 'same_window' (the default)
  58. );
  59. //Template for a basic top-level menu
  60. $this->templates['blank_menu'] = array(
  61. 'page_title' => null,
  62. 'menu_title' => null,
  63. 'access_level' => null,
  64. 'file' => null,
  65. 'css_class' => null,
  66. 'hookname' => null,
  67. 'icon_url' => null,
  68. 'position' => null,
  69. 'separator' => null,
  70. 'custom' => null,
  71. 'open_in' => null,
  72. 'defaults' => $this->templates['basic_defaults'],
  73. 'items' => array(),
  74. );
  75. //Template for menu items
  76. $this->templates['blank_item'] = array(
  77. 'menu_title' => null,
  78. 'access_level' => null,
  79. 'file' => null,
  80. 'page_title' => null,
  81. 'position' => null,
  82. 'custom' => null,
  83. 'open_in' => null,
  84. 'defaults' => $this->templates['basic_defaults'],
  85. );
  86. //AJAXify screen options
  87. add_action( 'wp_ajax_ws_ame_save_screen_options', array(&$this,'ajax_save_screen_options') );
  88. //AJAXify hints
  89. add_action('wp_ajax_ws_ame_hide_hint', array($this, 'ajax_hide_hint'));
  90. //Make sure we have access to the original, un-mangled request data.
  91. //This is necessary because WordPress will stupidly apply "magic quotes"
  92. //to the request vars even if this PHP misfeature is disabled.
  93. add_action('plugins_loaded', array($this, 'capture_request_vars'));
  94. add_action('admin_enqueue_scripts', array($this, 'enqueue_menu_fix_script'));
  95. //User survey
  96. add_action('admin_notices', array($this, 'display_survey_notice'));
  97. }
  98. function init_finish() {
  99. parent::init_finish();
  100. if ( !isset($this->options['first_install_time']) ) {
  101. $this->options['first_install_time'] = time();
  102. $this->save_options();
  103. }
  104. }
  105. /**
  106. * Activation hook
  107. *
  108. * @return void
  109. */
  110. function activate(){
  111. //If we have no stored settings for this version of the plugin, try importing them
  112. //from other versions (i.e. the free or the Pro version).
  113. if ( !$this->load_options() ){
  114. $this->import_settings();
  115. }
  116. parent::activate();
  117. }
  118. /**
  119. * Import settings from a different version of the plugin.
  120. *
  121. * @return bool True if settings were imported successfully, False otherwise
  122. */
  123. function import_settings(){
  124. $possible_names = array('ws_menu_editor', 'ws_menu_editor_pro');
  125. foreach($possible_names as $option_name){
  126. if ( $this->load_options($option_name) ){
  127. return true;
  128. }
  129. }
  130. return false;
  131. }
  132. /**
  133. * Add the JS required by the editor to the page header
  134. *
  135. * @return void
  136. */
  137. function enqueue_scripts(){
  138. //jQuery JSON plugin
  139. wp_enqueue_script('jquery-json', plugins_url('js/jquery.json-1.3.js', $this->plugin_file), array('jquery'), '1.3');
  140. //jQuery sort plugin
  141. wp_enqueue_script('jquery-sort', plugins_url('js/jquery.sort.js', $this->plugin_file), array('jquery'));
  142. //jQuery UI Droppable
  143. wp_enqueue_script('jquery-ui-droppable');
  144. //Editor's scipts
  145. wp_enqueue_script(
  146. 'menu-editor',
  147. plugins_url('js/menu-editor.js', $this->plugin_file),
  148. array('jquery', 'jquery-ui-sortable', 'jquery-ui-dialog', 'jquery-form'),
  149. '1.1'
  150. );
  151. //The editor will need access to some of the plugin data and WP data.
  152. wp_localize_script(
  153. 'menu-editor',
  154. 'wsEditorData',
  155. array(
  156. 'adminAjaxUrl' => admin_url('admin-ajax.php'),
  157. 'showHints' => $this->get_hint_visibility(),
  158. )
  159. );
  160. }
  161. /**
  162. * Add the editor's CSS file to the page header
  163. *
  164. * @return void
  165. */
  166. function enqueue_styles(){
  167. wp_enqueue_style('menu-editor-style', plugins_url('css/menu-editor.css', $this->plugin_file), array(), '20120626');
  168. }
  169. /**
  170. * Create a configuration page and load the custom menu
  171. *
  172. * @return void
  173. */
  174. function hook_admin_menu(){
  175. global $menu, $submenu;
  176. //Menu reset (for emergencies). Executed by accessing http://example.com/wp-admin/?reset_admin_menu=1
  177. $reset_requested = isset($this->get['reset_admin_menu']) && $this->get['reset_admin_menu'];
  178. if ( $reset_requested && $this->current_user_can_edit_menu() ){
  179. $this->options['custom_menu'] = null;
  180. $this->save_options();
  181. }
  182. //The menu editor is only visible to users with the manage_options privilege.
  183. //Or, if the plugin is installed in mu-plugins, only to the site administrator(s).
  184. if ( $this->current_user_can_edit_menu() ){
  185. $page = add_options_page(
  186. apply_filters('admin_menu_editor-self_page_title', 'Menu Editor'),
  187. apply_filters('admin_menu_editor-self_menu_title', 'Menu Editor'),
  188. 'manage_options',
  189. 'menu_editor',
  190. array(&$this, 'page_menu_editor')
  191. );
  192. //Output our JS & CSS on that page only
  193. add_action("admin_print_scripts-$page", array(&$this, 'enqueue_scripts'));
  194. add_action("admin_print_styles-$page", array(&$this, 'enqueue_styles'));
  195. //Make a placeholder for our screen options (hacky)
  196. add_meta_box("ws-ame-screen-options", "You should never see this", array(&$this, 'noop'), $page);
  197. }
  198. //WP 3.0 in multisite mode has two separators with the same filename. This plugin
  199. //expects all top-level menus to have unique filenames/URLs.
  200. $first_separator1 = -1;
  201. $last_separator1 = -1;
  202. foreach($menu as $index => $item){
  203. if ( $item[2] == 'separator1' ){
  204. $last_separator1 = $index;
  205. if ( $first_separator1 == -1 ){
  206. $first_separator1 = $index;
  207. }
  208. }
  209. }
  210. if ( $first_separator1 != $last_separator1 ){
  211. $menu[$first_separator1][2] = 'separator0';
  212. }
  213. //Store the "original" menus for later use in the editor
  214. $this->default_wp_menu = $menu;
  215. $this->default_wp_submenu = $submenu;
  216. //Is there a custom menu to use?
  217. if ( !empty($this->options['custom_menu']) ){
  218. //Check if we need to upgrade the menu structure
  219. if ( empty($this->options['menu_format_version']) || ($this->options['menu_format_version'] < $this->menu_format_version) ){
  220. $this->options['custom_menu'] = $this->upgrade_menu_structure($this->options['custom_menu']);
  221. $this->options['menu_format_version'] = $this->menu_format_version;
  222. $this->save_options();
  223. }
  224. //Merge in data from the default menu
  225. $tree = $this->menu_merge($this->options['custom_menu'], $menu, $submenu);
  226. //Save for later - the editor page will need it
  227. $this->custom_menu = $tree;
  228. //Apply the custom menu
  229. list($menu, $submenu, $this->title_lookups) = $this->tree2wp($tree);
  230. //Re-filter the menu (silly WP should do that itself, oh well)
  231. $this->filter_menu();
  232. $this->filtered_wp_menu = $menu;
  233. $this->filtered_wp_submenu = $submenu;
  234. }
  235. }
  236. /**
  237. * Activate the 'menu_order' filter.
  238. *
  239. * @return bool
  240. */
  241. function hook_custom_menu_order(){
  242. return true;
  243. }
  244. /**
  245. * Override the order of the top-level menu entries.
  246. *
  247. * @param array $menu_order
  248. * @return array
  249. */
  250. function hook_menu_order($menu_order){
  251. if (empty($this->custom_menu)){
  252. return $menu_order;
  253. }
  254. $custom_menu_order = array();
  255. foreach($this->filtered_wp_menu as $topmenu){
  256. $filename = $topmenu[2];
  257. if ( in_array($filename, $menu_order) ){
  258. $custom_menu_order[] = $filename;
  259. }
  260. }
  261. return $custom_menu_order;
  262. }
  263. /**
  264. * Determine if the current user may use the menu editor.
  265. *
  266. * @return bool
  267. */
  268. function current_user_can_edit_menu(){
  269. if ( $this->is_super_plugin() ){
  270. return is_super_admin();
  271. } else {
  272. return current_user_can('manage_options');
  273. }
  274. }
  275. /**
  276. * Intercept a handy action to fix the page title for moved plugin pages.
  277. *
  278. * @return void
  279. */
  280. function hook_admin_xml_ns(){
  281. global $title;
  282. if ( empty($title) ){
  283. $title = $this->get_real_page_title();
  284. }
  285. }
  286. /**
  287. * Fix the page title for move plugin pages.
  288. * The 'admin_title' filter is only available in WP 3.1+
  289. *
  290. * @param string $admin_title The current admin title.
  291. * @param string $title The current page title.
  292. * @return string New admin title.
  293. */
  294. function hook_admin_title($admin_title, $title){
  295. if ( empty($title) ){
  296. $admin_title = $this->get_real_page_title() . $admin_title;
  297. }
  298. return $admin_title;
  299. }
  300. /**
  301. * Get the correct page title for a plugin page that's been moved to a different menu.
  302. *
  303. * @return string
  304. */
  305. function get_real_page_title(){
  306. global $title;
  307. global $pagenow;
  308. global $plugin_page;
  309. if ( empty($title) && !empty($plugin_page) && !empty($pagenow) ){
  310. $file = sprintf('%s?page=%s', $pagenow, $plugin_page);
  311. if ( isset($this->title_lookups[$file]) ){
  312. $title = esc_html( strip_tags( $this->title_lookups[$file] ) );
  313. }
  314. }
  315. return $title;
  316. }
  317. /**
  318. * Loop over the Dashboard submenus and remove pages for which the current user does not have privs.
  319. *
  320. * @return void
  321. */
  322. function filter_menu(){
  323. global $menu, $submenu, $_wp_submenu_nopriv, $_wp_menu_nopriv;
  324. foreach ( array( 'submenu' ) as $sub_loop ) {
  325. foreach ($$sub_loop as $parent => $sub) {
  326. foreach ($sub as $index => $data) {
  327. if ( ! current_user_can($data[1]) ) {
  328. unset(${$sub_loop}[$parent][$index]);
  329. $_wp_submenu_nopriv[$parent][$data[2]] = true;
  330. }
  331. }
  332. if ( empty(${$sub_loop}[$parent]) )
  333. unset(${$sub_loop}[$parent]);
  334. }
  335. }
  336. }
  337. /**
  338. * Encode a menu tree as JSON
  339. *
  340. * @param array $tree
  341. * @return string
  342. */
  343. function getMenuAsJS($tree){
  344. return $this->json_encode($tree);
  345. }
  346. /**
  347. * Convert a WP menu structure to an associative array
  348. *
  349. * @param array $item An element of the $menu array
  350. * @param integer $pos The position (index) of the menu item
  351. * @return array
  352. */
  353. function menu2assoc($item, $pos=0){
  354. $item = array(
  355. 'menu_title' => $item[0],
  356. 'access_level' => $item[1],
  357. 'file' => $item[2],
  358. 'page_title' => $item[3],
  359. 'css_class' => $item[4],
  360. 'hookname' => (isset($item[5])?$item[5]:''), //ID
  361. 'icon_url' => (isset($item[6])?$item[6]:''),
  362. 'position' => $pos,
  363. );
  364. $item['separator'] = strpos($item['css_class'], 'wp-menu-separator') !== false;
  365. //Flag plugin pages
  366. $item['is_plugin_page'] = (get_plugin_page_hook($item['file'], '') != null);
  367. return array_merge($this->templates['basic_defaults'], $item);
  368. }
  369. /**
  370. * Convert a WP submenu structure to an associative array
  371. *
  372. * @param array $item An element of the $submenu array
  373. * @param integer $pos The position (index) of that element
  374. * @param string $parent Parent file that this menu item belongs to.
  375. * @return array
  376. */
  377. function submenu2assoc($item, $pos = 0, $parent = ''){
  378. $item = array(
  379. 'menu_title' => $item[0],
  380. 'access_level' => $item[1],
  381. 'file' => $item[2],
  382. 'page_title' => (isset($item[3])?$item[3]:''),
  383. 'position' => $pos,
  384. );
  385. //Save the default parent menu
  386. $item['parent'] = $parent;
  387. //Flag plugin pages
  388. $item['is_plugin_page'] = (get_plugin_page_hook($item['file'], $parent) != null);
  389. return array_merge($this->templates['basic_defaults'], $item);
  390. }
  391. /**
  392. * Populate lookup arrays with default values from $menu and $submenu. Used later to merge
  393. * a custom menu with the native WordPress menu structure somewhat gracefully.
  394. *
  395. * @param array $menu
  396. * @param array $submenu
  397. * @return array An array with two elements containing menu and submenu defaults.
  398. */
  399. function build_lookups($menu, $submenu){
  400. //Process the top menu
  401. $menu_defaults = array();
  402. foreach($menu as $pos => $item){
  403. $item = $this->menu2assoc($item, $pos);
  404. $menu_defaults[$item['file']] = $item; //index by filename
  405. }
  406. //Process the submenu
  407. $submenu_defaults = array();
  408. foreach($submenu as $parent => $items){
  409. foreach($items as $pos => $item){
  410. $item = $this->submenu2assoc($item, $pos, $parent);
  411. //File itself is not guaranteed to be unique, so we use a surrogate ID to identify submenus.
  412. $uid = $this->unique_submenu_id($item['file'], $parent);
  413. $submenu_defaults[$uid] = $item;
  414. }
  415. }
  416. return array($menu_defaults, $submenu_defaults);
  417. }
  418. /**
  419. * Merge $menu and $submenu into the $tree. Adds/replaces defaults, inserts new items
  420. * and marks missing items as such.
  421. *
  422. * @param array $tree A menu in plugin's internal form
  423. * @param array $menu WordPress menu structure
  424. * @param array $submenu WordPress submenu structure
  425. * @return array Updated menu tree
  426. */
  427. function menu_merge($tree, $menu, $submenu){
  428. list($menu_defaults, $submenu_defaults) = $this->build_lookups($menu, $submenu);
  429. //Iterate over all menus and submenus and look up default values
  430. foreach ($tree as &$topmenu){
  431. $topfile = $this->get_menu_field($topmenu, 'file');
  432. //Is this menu present in the default WP menu?
  433. if (isset($menu_defaults[$topfile])){
  434. //Yes, load defaults from that item
  435. $topmenu['defaults'] = $menu_defaults[$topfile];
  436. //Note that the original item was used
  437. $menu_defaults[$topfile]['used'] = true;
  438. } else {
  439. //Record the menu as missing, unless it's a menu separator
  440. if ( empty($topmenu['separator']) ){
  441. $topmenu['missing'] = true;
  442. //[Nasty] Fill the 'defaults' array for menu's that don't have it.
  443. //This should never be required - saving a custom menu should set the defaults
  444. //for all menus it contains automatically.
  445. if ( empty($topmenu['defaults']) ){
  446. $tmp = $topmenu;
  447. $topmenu['defaults'] = $tmp;
  448. }
  449. }
  450. }
  451. if (isset($topmenu['items']) && is_array($topmenu['items'])) {
  452. //Iterate over submenu items
  453. foreach ($topmenu['items'] as $file => &$item){
  454. $uid = $this->unique_submenu_id($item, $topfile);
  455. //Is this item present in the default WP menu?
  456. if (isset($submenu_defaults[$uid])){
  457. //Yes, load defaults from that item
  458. $item['defaults'] = $submenu_defaults[$uid];
  459. $submenu_defaults[$uid]['used'] = true;
  460. } else {
  461. //Record as missing
  462. $item['missing'] = true;
  463. if ( empty($item['defaults']) ){
  464. $tmp = $item;
  465. $item['defaults'] = $tmp;
  466. }
  467. }
  468. }
  469. }
  470. }
  471. //If we don't unset these they will fuck up the next two loops where the same names are used.
  472. unset($topmenu);
  473. unset($item);
  474. //Note : Now we have some items marked as missing, and some items in lookup arrays
  475. //that are not marked as used. The missing items are handled elsewhere (e.g. tree2wp()),
  476. //but lets merge in the unused items now.
  477. //Find and merge unused toplevel menus
  478. foreach ($menu_defaults as $topfile => $topmenu){
  479. //Skip used menus and separators
  480. if ( !empty($topmenu['used']) || !empty($topmenu['separator'])) {
  481. continue;
  482. };
  483. //Found an unused item. Build the tree entry.
  484. $entry = $this->templates['blank_menu'];
  485. $entry['defaults'] = $topmenu;
  486. $entry['items'] = array(); //prepare a place for menu items, if any.
  487. //Note that this item is unused
  488. $entry['unused'] = true;
  489. //Add the new entry to the menu tree
  490. $tree[$topfile] = $entry;
  491. }
  492. unset($topmenu);
  493. //Find and merge submenu items
  494. foreach($submenu_defaults as $uid => $item){
  495. if ( !empty($item['used']) ) continue;
  496. //Found an unused item. Build an entry and attach it under the default toplevel menu.
  497. $entry = $this->templates['blank_item'];
  498. $entry['defaults'] = $item;
  499. //Note that this item is unused
  500. $entry['unused'] = true;
  501. //Check if the toplevel menu exists
  502. if (isset($tree[$item['parent']])) {
  503. //Okay, insert the item.
  504. $tree[$item['parent']]['items'][$item['file']] = $entry;
  505. } else {
  506. //Ooops? This should never happen. Some kind of inconsistency?
  507. }
  508. }
  509. //Resort the tree to ensure the found items are in the right spots
  510. $tree = $this->sort_menu_tree($tree);
  511. return $tree;
  512. }
  513. /**
  514. * Generate an ID that uniquely identifies a given submenu item.
  515. *
  516. * @param string|array $file Menu item in question
  517. * @param string $parent Parent menu. Optional. If $file is an array, the function will try to get the parent value from $file['defaults'] instead.
  518. * @return string Unique ID
  519. */
  520. function unique_submenu_id($file, $parent = ''){
  521. if ( is_array($file) ){
  522. if ( isset($file['defaults']) && isset($file['defaults']['parent']) ){
  523. $parent = $file['defaults']['parent'];
  524. }
  525. $file = $this->get_menu_field($file, 'file');
  526. }
  527. if ( !empty($parent) ){
  528. return $parent . '::' . $file;
  529. } else {
  530. return $file;
  531. }
  532. }
  533. /**
  534. * Convert the WP menu structure to the internal representation. All properties set as defaults.
  535. *
  536. * @param array $menu
  537. * @param array $submenu
  538. * @return array Menu in the internal tree format.
  539. */
  540. function wp2tree($menu, $submenu){
  541. $tree = array();
  542. $separator_count = 0;
  543. foreach ($menu as $pos => $item){
  544. $tree_item = $this->templates['blank_menu'];
  545. $tree_item['defaults'] = $this->menu2assoc($item, $pos);
  546. $tree_item['separator'] = empty($item[2]) || empty($item[0]) || (strpos($item[4], 'wp-menu-separator') !== false);
  547. if ( empty($tree_item['defaults']['file']) ){
  548. $tree_item['defaults']['file'] = 'separator_'.$separator_count;
  549. $separator_count++;
  550. }
  551. //Attach submenu items
  552. $parent = $tree_item['defaults']['file'];
  553. if ( isset($submenu[$parent]) ){
  554. foreach($submenu[$parent] as $subitem_pos => $subitem){
  555. $tree_item['items'][$subitem[2]] = array_merge(
  556. $this->templates['blank_item'],
  557. array('defaults' => $this->submenu2assoc($subitem, $subitem_pos, $parent))
  558. );
  559. }
  560. }
  561. $tree[$parent] = $tree_item;
  562. }
  563. $tree = $this->sort_menu_tree($tree);
  564. return $tree;
  565. }
  566. /**
  567. * Set all undefined menu fields to the default value
  568. *
  569. * @param array $item Menu item in the plugin's internal form
  570. * @return array
  571. */
  572. function apply_defaults($item){
  573. foreach($item as $key => $value){
  574. //Is the field set?
  575. if ($value === null){
  576. //Use default, if available
  577. if (isset($item['defaults']) && isset($item['defaults'][$key])){
  578. $item[$key] = $item['defaults'][$key];
  579. }
  580. }
  581. }
  582. return $item;
  583. }
  584. /**
  585. * Apply custom menu filters to an item of the custom menu.
  586. *
  587. * Calls two types of filters :
  588. * 'custom_admin_$item_type' with the entire $item passed as the argument.
  589. * 'custom_admin_$item_type-$field' with the value of a single field of $item as the argument.
  590. *
  591. * Used when converting the current custom menu to a WP-format menu.
  592. *
  593. * @param array $item Associative array representing one menu item (either top-level or submenu).
  594. * @param string $item_type 'menu' or 'submenu'
  595. * @param mixed $extra Optional extra data to pass to hooks.
  596. * @return array Filtered menu item.
  597. */
  598. function apply_menu_filters($item, $item_type = '', $extra = null){
  599. if ( empty($item_type) ){
  600. //Only top-level menus have an icon
  601. $item_type = isset($item['icon_url'])?'menu':'submenu';
  602. }
  603. $item = apply_filters("custom_admin_{$item_type}", $item, $extra);
  604. foreach($item as $field => $value){
  605. $item[$field] = apply_filters("custom_admin_{$item_type}-$field", $value, $extra);
  606. }
  607. return $item;
  608. }
  609. /**
  610. * Get the value of a menu/submenu field.
  611. * Will return the corresponding value from the 'defaults' entry of $item if the
  612. * specified field is not set in the item itself.
  613. *
  614. * @param array $item
  615. * @param string $field_name
  616. * @param mixed $default Returned if the requested field is not set and is not listed in $item['defaults']. Defaults to null.
  617. * @return mixed Field value.
  618. */
  619. function get_menu_field($item, $field_name, $default = null){
  620. if ( isset($item[$field_name]) && ($item[$field_name] !== null) ){
  621. return $item[$field_name];
  622. } else {
  623. if ( isset($item['defaults']) && isset($item['defaults'][$field_name]) ){
  624. return $item['defaults'][$field_name];
  625. } else {
  626. return $default;
  627. }
  628. }
  629. }
  630. /**
  631. * Custom comparison function that compares menu items based on their position in the menu.
  632. *
  633. * @param array $a
  634. * @param array $b
  635. * @return int
  636. */
  637. function compare_position($a, $b){
  638. if ($a['position']!==null) {
  639. $p1 = $a['position'];
  640. } else {
  641. if ( isset($a['defaults']['position']) ){
  642. $p1 = $a['defaults']['position'];
  643. } else {
  644. $p1 = 0;
  645. }
  646. }
  647. if ($b['position']!==null) {
  648. $p2 = $b['position'];
  649. } else {
  650. if ( isset($b['defaults']['position']) ){
  651. $p2 = $b['defaults']['position'];
  652. } else {
  653. $p2 = 0;
  654. }
  655. }
  656. return $p1 - $p2;
  657. }
  658. /**
  659. * Sort the menus and menu items of a given menu according to their positions
  660. *
  661. * @param array $tree A menu structure in the internal format
  662. * @return array Sorted menu in the internal format
  663. */
  664. function sort_menu_tree($tree){
  665. //Resort the tree to ensure the found items are in the right spots
  666. uasort($tree, array(&$this, 'compare_position'));
  667. //Resort all submenus as well
  668. foreach ($tree as &$topmenu){
  669. if (!empty($topmenu['items'])){
  670. uasort($topmenu['items'], array(&$this, 'compare_position'));
  671. }
  672. }
  673. return $tree;
  674. }
  675. /**
  676. * Convert internal menu representation to the form used by WP.
  677. *
  678. * Note : While this function doesn't cause any side effects of its own,
  679. * it executes several filters that may modify global state. Specifically,
  680. * IFrame-handling callbacks in 'extras.php' may insert items into the
  681. * global $menu and $submenu arrays.
  682. *
  683. * @param array $tree
  684. * @return array $menu and $submenu
  685. */
  686. function tree2wp($tree){
  687. $menu = array();
  688. $submenu = array();
  689. $title_lookup = array();
  690. //Sort the menu by position
  691. uasort($tree, array(&$this, 'compare_position'));
  692. //Prepare the top menu
  693. $first_nonseparator_found = false;
  694. foreach ($tree as $topmenu){
  695. //Skip missing menus, unless they're user-created and thus might point to a non-standard file
  696. $custom = $this->get_menu_field($topmenu, 'custom', false);
  697. if ( !empty($topmenu['missing']) && !$custom ) {
  698. continue;
  699. };
  700. //Skip leading menu separators. Fixes a superfluous separator showing up
  701. //in WP 3.0 (multisite mode) when there's a custom menu and the current user
  702. //can't access its first item ("Super Admin").
  703. if ( !empty($topmenu['separator']) && !$first_nonseparator_found ) continue;
  704. $first_nonseparator_found = true;
  705. //Apply defaults & filters
  706. $topmenu = $this->apply_defaults($topmenu);
  707. $topmenu = $this->apply_menu_filters($topmenu, 'menu');
  708. //Skip hidden entries
  709. if (!empty($topmenu['hidden'])) continue;
  710. //Build the menu structure that WP expects
  711. $menu[] = array(
  712. $topmenu['menu_title'],
  713. $topmenu['access_level'],
  714. $topmenu['file'],
  715. $topmenu['page_title'],
  716. $topmenu['css_class'],
  717. $topmenu['hookname'], //ID
  718. $topmenu['icon_url']
  719. );
  720. //Prepare the submenu of this menu
  721. if( !empty($topmenu['items']) ){
  722. $items = $topmenu['items'];
  723. //Sort by position
  724. uasort($items, array(&$this, 'compare_position'));
  725. foreach ($items as $item) {
  726. //Skip missing items, unless they're user-created
  727. $custom = $this->get_menu_field($item, 'custom', false);
  728. if ( !empty($item['missing']) && !$custom ) continue;
  729. //Special case : plugin pages that have been moved to a different menu.
  730. //If the file field hasn't already been modified, we'll need to adjust it
  731. //to point to the old parent. This is required because WP identifies
  732. //plugin pages using *both* the plugin file and the parent file.
  733. if ( $this->get_menu_field($item, 'is_plugin_page', false) && ($item['file'] === null) ){
  734. $default_parent = '';
  735. if ( isset($item['defaults']) && isset($item['defaults']['parent'])){
  736. $default_parent = $item['defaults']['parent'];
  737. }
  738. if ( $topmenu['file'] != $default_parent ){
  739. $item['file'] = $default_parent . '?page=' . $item['defaults']['file'];
  740. }
  741. }
  742. $item = $this->apply_defaults($item);
  743. $item = $this->apply_menu_filters($item, 'submenu', $topmenu['file']);
  744. //Skip hidden items
  745. if (!empty($item['hidden'])) {
  746. continue;
  747. }
  748. $submenu[$topmenu['file']][] = array(
  749. $item['menu_title'],
  750. $item['access_level'],
  751. $item['file'],
  752. $item['page_title'],
  753. );
  754. //Make a note of the page's correct title so we can fix it later
  755. //if necessary.
  756. $title_lookup[$item['file']] = $item['menu_title'];
  757. }
  758. }
  759. }
  760. return array($menu, $submenu, $title_lookup);
  761. }
  762. /**
  763. * Upgrade a menu tree to the currently used structure
  764. * Does nothing if the menu is already up to date.
  765. *
  766. * @param array $tree
  767. * @return array
  768. */
  769. function upgrade_menu_structure($tree){
  770. //Append new fields, if any
  771. foreach($tree as &$menu){
  772. $menu = array_merge($this->templates['blank_menu'], $menu);
  773. $menu['defaults'] = array_merge($this->templates['basic_defaults'], $menu['defaults']);
  774. foreach($menu['items'] as $item_file => $item){
  775. $item = array_merge($this->templates['blank_item'], $item);
  776. $item['defaults'] = array_merge($this->templates['basic_defaults'], $item['defaults']);
  777. $menu['items'][$item_file] = $item;
  778. }
  779. }
  780. return $tree;
  781. }
  782. /**
  783. * Output the menu editor page
  784. *
  785. * @return void
  786. */
  787. function page_menu_editor(){
  788. global $menu, $submenu;
  789. global $wp_roles;
  790. if ( !$this->current_user_can_edit_menu() ){
  791. die("Access denied");
  792. }
  793. $action = isset($this->post['action']) ? $this->post['action'] : (isset($this->get['action']) ? $this->get['action'] : '');
  794. do_action('admin_menu_editor_header', $action);
  795. //Handle form submissions
  796. if (isset($this->post['data'])){
  797. check_admin_referer('menu-editor-form');
  798. //Try to decode a menu tree encoded as JSON
  799. $data = $this->json_decode($this->post['data'], true);
  800. if (!$data || (count($data) < 2) ){
  801. $fixed = stripslashes($this->post['data']);
  802. $data = $this->json_decode( $fixed, true );
  803. }
  804. $url = remove_query_arg('noheader');
  805. if ($data){
  806. //Ensure the user doesn't change the required capability to something they themselves don't have.
  807. if ( isset($data['options-general.php']['items']['menu_editor']) ){
  808. $item = $data['options-general.php']['items']['menu_editor'];
  809. if ( !empty($item['access_level']) && !current_user_can($item['access_level']) ){
  810. $item['access_level'] = null;
  811. $data['options-general.php']['items']['menu_editor'] = $item;
  812. }
  813. }
  814. //Save the custom menu
  815. $this->options['custom_menu'] = $data;
  816. $this->save_options();
  817. //Redirect back to the editor and display the success message
  818. wp_redirect( add_query_arg('message', 1, $url) );
  819. } else {
  820. //Or redirect & display the error message
  821. wp_redirect( add_query_arg('message', 2, $url) );
  822. }
  823. die();
  824. }
  825. //Kindly remind the user to give me money
  826. if ( !apply_filters('admin_menu_editor_is_pro', false) ){
  827. $this->print_upgrade_notice();
  828. }
  829. ?>
  830. <div class="wrap">
  831. <h2>
  832. <?php echo apply_filters('admin_menu_editor-self_page_title', 'Menu Editor'); ?>
  833. </h2>
  834. <?php
  835. if ( !empty($this->get['message']) ){
  836. if ( intval($this->get['message']) == 1 ){
  837. echo '<div id="message" class="updated fade"><p><strong>Settings saved.</strong></p></div>';
  838. } elseif ( intval($this->get['message']) == 2 ) {
  839. echo '<div id="message" class="error"><p><strong>Failed to decode input! The menu wasn\'t modified.</strong></p></div>';
  840. }
  841. }
  842. //Build a tree struct. for the default menu
  843. $default_menu = $this->wp2tree($this->default_wp_menu, $this->default_wp_submenu);
  844. //Is there a custom menu?
  845. if (!empty($this->custom_menu)){
  846. $custom_menu = $this->custom_menu;
  847. } else {
  848. //Start out with the default menu if there is no user-created one
  849. $custom_menu = $default_menu;
  850. }
  851. //Encode both menus as JSON
  852. $default_menu_js = $this->getMenuAsJS($default_menu);
  853. $custom_menu_js = $this->getMenuAsJS($custom_menu);
  854. $plugin_url = $this->plugin_dir_url;
  855. $images_url = plugins_url('images', $this->plugin_file);
  856. //Create a list of all known capabilities and roles. Used for the dropdown list on the access field.
  857. $all_capabilities = $this->get_all_capabilities();
  858. //"level_X" capabilities are deprecated so we don't want people using them.
  859. //This would look better with array_filter() and an anonymous function as a callback.
  860. for($level = 0; $level <= 10; $level++){
  861. $cap = 'level_' . $level;
  862. if ( isset($all_capabilities[$cap]) ){
  863. unset($all_capabilities[$cap]);
  864. }
  865. }
  866. $all_capabilities = array_keys($all_capabilities);
  867. natcasesort($all_capabilities);
  868. $all_roles = $this->get_all_roles();
  869. //Multi-site installs also get the virtual "Super Admin" role
  870. if ( is_multisite() ){
  871. $all_roles['super_admin'] = 'Super Admin';
  872. }
  873. asort($all_roles);
  874. ?>
  875. <div id='ws_menu_editor'>
  876. <div class='ws_main_container'>
  877. <div class='ws_toolbar'>
  878. <div class="ws_button_container">
  879. <a id='ws_cut_menu' class='ws_button' href='javascript:void(0)' title='Cut'><img src='<?php echo $images_url; ?>/cut.png' alt="Cut" /></a>
  880. <a id='ws_copy_menu' class='ws_button' href='javascript:void(0)' title='Copy'><img src='<?php echo $images_url; ?>/page_white_copy.png' alt="Copy" /></a>
  881. <a id='ws_paste_menu' class='ws_button' href='javascript:void(0)' title='Paste'><img src='<?php echo $images_url; ?>/page_white_paste.png' alt="Paste" /></a>
  882. <div class="ws_separator">&nbsp;</div>
  883. <a id='ws_new_menu' class='ws_button' href='javascript:void(0)' title='New menu'><img src='<?php echo $images_url; ?>/page_white_add.png' alt="New menu" /></a>
  884. <a id='ws_hide_menu' class='ws_button' href='javascript:void(0)' title='Show/Hide'><img src='<?php echo $images_url; ?>/plugin_disabled.png' alt="Show/Hide" /></a>
  885. <a id='ws_delete_menu' class='ws_button' href='javascript:void(0)' title='Delete menu'><img src='<?php echo $images_url; ?>/page_white_delete.png' alt="Delete menu" /></a>
  886. <div class="ws_separator">&nbsp;</div>
  887. <a id='ws_new_separator' class='ws_button' href='javascript:void(0)' title='New separator'><img src='<?php echo $images_url; ?>/separator_add.png' alt="New separator" /></a>
  888. </div>
  889. </div>
  890. <div id='ws_menu_box' class="ws_box">
  891. </div>
  892. </div>
  893. <div class='ws_main_container'>
  894. <div class='ws_toolbar'>
  895. <div class="ws_button_container">
  896. <a id='ws_cut_item' class='ws_button' href='javascript:void(0)' title='Cut'><img src='<?php echo $images_url; ?>/cut.png' alt="Cut" /></a>
  897. <a id='ws_copy_item' class='ws_button' href='javascript:void(0)' title='Copy'><img src='<?php echo $images_url; ?>/page_white_copy.png' alt="Copy" /></a>
  898. <a id='ws_paste_item' class='ws_button' href='javascript:void(0)' title='Paste'><img src='<?php echo $images_url; ?>/page_white_paste.png' alt="Paste" /></a>
  899. <div class="ws_separator">&nbsp;</div>
  900. <a id='ws_new_item' class='ws_button' href='javascript:void(0)' title='New menu item'><img src='<?php echo $images_url; ?>/page_white_add.png' alt="New menu item" /></a>
  901. <a id='ws_hide_item' class='ws_button' href='javascript:void(0)' title='Show/Hide'><img src='<?php echo $images_url; ?>/plugin_disabled.png' alt="Show/Hide" /></a>
  902. <a id='ws_delete_item' class='ws_button' href='javascript:void(0)' title='Delete menu item'><img src='<?php echo $images_url; ?>/page_white_delete.png' alt="Delete menu item" /></a>
  903. <div class="ws_separator">&nbsp;</div>
  904. <a id='ws_sort_ascending' class='ws_button' href='javascript:void(0)' title='Sort ascending'>
  905. <img src='<?php echo $images_url; ?>/sort_ascending.png' alt="Sort ascending" />
  906. </a>
  907. <a id='ws_sort_descending' class='ws_button' href='javascript:void(0)' title='Sort descending'>
  908. <img src='<?php echo $images_url; ?>/sort_descending.png' alt="Sort descending" />
  909. </a>
  910. </div>
  911. </div>
  912. <div id='ws_submenu_box' class="ws_box">
  913. </div>
  914. </div>
  915. </div>
  916. <div class="ws_main_container" id="ws_editor_sidebar">
  917. <form method="post" action="<?php echo admin_url('options-general.php?page=menu_editor&noheader=1'); ?>" id='ws_main_form' name='ws_main_form'>
  918. <?php wp_nonce_field('menu-editor-form'); ?>
  919. <input type="hidden" name="data" id="ws_data" value="">
  920. <input type="button" id='ws_save_menu' class="button-primary ws_main_button" value="Save Changes" />
  921. </form>
  922. <input type="button" id='ws_reset_menu' value="Undo changes" class="button ws_main_button" />
  923. <input type="button" id='ws_load_menu' value="Load default menu" class="button ws_main_button" />
  924. <?php
  925. do_action('admin_menu_editor_sidebar');
  926. ?>
  927. </div>
  928. <?php
  929. $show_hints = $this->get_hint_visibility();
  930. $hint_id = 'ws_sidebar_pro_ad';
  931. $show_pro_benefits = !apply_filters('admin_menu_editor_is_pro', false) && (!isset($show_hints[$hint_id]) || $show_hints[$hint_id]);
  932. if ( $show_pro_benefits ):
  933. ?>
  934. <div class="clear"></div>
  935. <div class="ws_hint" id="<?php echo esc_attr($hint_id); ?>">
  936. <div class="ws_hint_close" title="Close">x</div>
  937. <div class="ws_hint_content">
  938. <strong>Upgrade to Pro:</strong>
  939. <ul>
  940. <li>Menu export & import.</li>
  941. <li>Per-role menu permissions.</li>
  942. <li>Drag items between menu levels.</li>
  943. </ul>
  944. <a href="http://w-shadow.com/admin-menu-editor-pro/upgrade-to-pro/?utm_source=Admin%2BMenu%2BEditor%2Bfree&utm_medium=text_link&utm_content=sidebar_link&utm_campaign=Plugins" target="_blank">Learn more</a>
  945. </div>
  946. </div>
  947. <?php
  948. endif;
  949. ?>
  950. </div>
  951. <?php
  952. //Create a pop-up capability selector
  953. $capSelector = array('<select id="ws_cap_selector" class="ws_dropdown" size="10">');
  954. $capSelector[] = '<optgroup label="Roles">';
  955. foreach($all_roles as $role_id => $role_name){
  956. $capSelector[] = sprintf(
  957. '<option value="%s">%s</option>',
  958. esc_attr($role_id),
  959. $role_name
  960. );
  961. }
  962. $capSelector[] = '</optgroup>';
  963. $capSelector[] = '<optgroup label="Capabilities">';
  964. foreach($all_capabilities as $cap){
  965. $capSelector[] = sprintf(
  966. '<option value="%s">%s</option>',
  967. esc_attr($cap),
  968. $cap
  969. );
  970. }
  971. $capSelector[] = '</optgroup>';
  972. $capSelector[] = '</select>';
  973. echo implode("\n", $capSelector);
  974. //Create a pop-up page selector
  975. $pageSelector = array('<select id="ws_page_selector" class="ws_dropdown" size="10">');
  976. foreach($default_menu as $toplevel){
  977. if ( $toplevel['separator'] ) continue;
  978. $top_title = strip_tags( preg_replace('@<span[^>]*>.*</span>@i', '', $this->get_menu_field($toplevel, 'menu_title')) );
  979. if ( empty($toplevel['items'])) {
  980. //This menu has no items, so it can only link to itself
  981. $pageSelector[] = sprintf(
  982. '<option value="%s">%s -&gt; %s</option>',
  983. esc_attr($this->get_menu_field($toplevel, 'file')),
  984. $top_title,
  985. $top_title
  986. );
  987. } else {
  988. //When a menu has some items, it's own URL is ignored by WP and the first item is used instead.
  989. foreach($toplevel['items'] as $subitem){
  990. $sub_title = strip_tags( preg_replace('@<span[^>]*>.*</span>@i', '', $this->get_menu_field($subitem, 'menu_title')) );
  991. $pageSelector[] = sprintf(
  992. '<option value="%s">%s -&gt; %s</option>',
  993. esc_attr($this->get_menu_field($subitem, 'file')),
  994. $top_title,
  995. $sub_title
  996. );
  997. }
  998. }
  999. }
  1000. $pageSelector[] = '</select>';
  1001. echo implode("\n", $pageSelector);
  1002. ?>
  1003. <span id="ws-ame-screen-meta-contents" style="display:none;">
  1004. <label for="ws-hide-advanced-settings">
  1005. <input type="checkbox" id="ws-hide-advanced-settings"<?php
  1006. if ( $this->options['hide_advanced_settings'] ){
  1007. echo ' checked="checked"';
  1008. }
  1009. ?> /> Hide advanced options
  1010. </label>
  1011. </span>
  1012. <script type='text/javascript'>
  1013. var defaultMenu = <?php echo $default_menu_js; ?>;
  1014. var customMenu = <?php echo $custom_menu_js; ?>;
  1015. var imagesUrl = "<?php echo esc_js($images_url); ?>";
  1016. var adminAjaxUrl = "<?php echo esc_js(admin_url('admin-ajax.php')); ?>";
  1017. var hideAdvancedSettings = <?php echo $this->options['hide_advanced_settings']?'true':'false'; ?>;
  1018. var hideAdvancedSettingsNonce = '<?php echo esc_js(wp_create_nonce('ws_ame_save_screen_options')); ?>';
  1019. var captionShowAdvanced = 'Show advanced options';
  1020. var captionHideAdvanced = 'Hide advanced options';
  1021. window.wsMenuEditorPro = false; //Will be overwritten if extras are loaded
  1022. </script>
  1023. <?php
  1024. //Let the Pro version script output it's extra HTML & scripts.
  1025. do_action('admin_menu_editor_footer');
  1026. }
  1027. /**
  1028. * Retrieve a list of all known capabilities of all roles
  1029. *
  1030. * @return array Associative array with capability names as keys
  1031. */
  1032. function get_all_capabilities(){
  1033. /** @var WP_Roles $wp_roles */
  1034. global $wp_roles;
  1035. $capabilities = array();
  1036. if ( !isset($wp_roles) || !isset($wp_roles->roles) ){
  1037. return $capabilities;
  1038. }
  1039. //Iterate over all known roles and collect their capabilities
  1040. foreach($wp_roles->roles as $role){
  1041. if ( !empty($role['capabilities']) && is_array($role['capabilities']) ){ //Being defensive here
  1042. $capabilities = array_merge($capabilities, $role['capabilities']);
  1043. }
  1044. }
  1045. //Add multisite-specific capabilities (not listed in any roles in WP 3.0)
  1046. $multisite_caps = array(
  1047. 'manage_sites' => 1,
  1048. 'manage_network' => 1,
  1049. 'manage_network_users' => 1,
  1050. 'manage_network_themes' => 1,
  1051. 'manage_network_options' => 1,
  1052. 'manage_network_plugins' => 1,
  1053. );
  1054. $capabilities = array_merge($capabilities, $multisite_caps);
  1055. return $capabilities;
  1056. }
  1057. /**
  1058. * Retrieve a list of all known roles
  1059. *
  1060. * @return array Associative array with role IDs as keys and role display names as values
  1061. */
  1062. function get_all_roles(){
  1063. /** @var WP_Roles $wp_roles */
  1064. global $wp_roles;
  1065. $roles = array();
  1066. if ( !isset($wp_roles) || !isset($wp_roles->roles) ){
  1067. return $roles;
  1068. }
  1069. foreach($wp_roles->roles as $role_id => $role){
  1070. $roles[$role_id] = $role['name'];
  1071. }
  1072. return $roles;
  1073. }
  1074. /**
  1075. * Create a virtual 'super_admin' capability that only super admins have.
  1076. * This function accomplishes that by by filtering 'user_has_cap' calls.
  1077. *
  1078. * @param array $allcaps All capabilities belonging to the current user, cap => true/false.
  1079. * @param array $required_caps The required capabilities.
  1080. * @param array $args The capability passed to current_user_can, the current user's ID, and other args.
  1081. * @return array Filtered version of $allcaps
  1082. */
  1083. function hook_user_has_cap($allcaps, $required_caps, $args){
  1084. //Be careful not to overwrite a super_admin cap added by other plugins
  1085. //For example, Advanced Access Manager also adds this capability.
  1086. if ( in_array('super_admin', $required_caps) && !isset($allcaps['super_admin']) ){
  1087. $allcaps['super_admin'] = is_multisite() && is_super_admin($args[1]);
  1088. }
  1089. return $allcaps;
  1090. }
  1091. /**
  1092. * Output the "Upgrade to Pro" message
  1093. *
  1094. * @return void
  1095. */
  1096. function print_upgrade_notice(){
  1097. ?>
  1098. <script type="text/javascript">
  1099. (function($){
  1100. $('#screen-meta-links').append(
  1101. '<div id="ws-pro-version-notice">' +
  1102. '<a href="http://w-shadow.com/admin-menu-editor-pro/?utm_source=Admin%2BMenu%2BEditor%2Bfree&utm_medium=text_link&utm_content=top_upgrade_link&utm_campaign=Plugins" id="ws-pro-version-notice-link" class="show-settings" target="_blank" title="View Pro version details">Upgrade to Pro</a>' +
  1103. '</div>'
  1104. );
  1105. })(jQuery);
  1106. </script>
  1107. <?php
  1108. }
  1109. /**
  1110. * AJAX callback for saving screen options (whether to show or to hide advanced menu options).
  1111. *
  1112. * Handles the 'ws_ame_save_screen_options' action. The new option value
  1113. * is read from $_POST['hide_advanced_settings'].
  1114. *
  1115. * @return void
  1116. */
  1117. function ajax_save_screen_options(){
  1118. if (!current_user_can('manage_options') || !check_ajax_referer('ws_ame_save_screen_options', false, false)){
  1119. die( $this->json_encode( array(
  1120. 'error' => "You're not allowed to do that!"
  1121. )));
  1122. }
  1123. $this->options['hide_advanced_settings'] = !empty($this->post['hide_advanced_settings']);
  1124. $this->save_options();
  1125. die('1');
  1126. }
  1127. public function ajax_hide_hint() {
  1128. if ( !isset($this->post['hint']) || !$this->current_user_can_edit_menu() ){
  1129. die("You're not allowed to do that!");
  1130. }
  1131. $show_hints = $this->get_hint_visibility();
  1132. $show_hints[strval($this->post['hint'])] = false;
  1133. $this->set_hint_visibility($show_hints);
  1134. die("OK");
  1135. }
  1136. private function get_hint_visibility() {
  1137. $user = wp_get_current_user();
  1138. $show_hints = get_user_meta($user->ID, 'ame_show_hints', true);
  1139. if ( !is_array($show_hints) ) {
  1140. $show_hints = array();
  1141. }
  1142. $defaults = array(
  1143. 'ws_sidebar_pro_ad' => true,
  1144. //'ws_whats_new_120' => true, //Set upon activation, default not needed.
  1145. 'ws_hint_menu_permissions' => true,
  1146. );
  1147. return array_merge($defaults, $show_hints);
  1148. }
  1149. private function set_hint_visibility($show_hints) {
  1150. $user = wp_get_current_user();
  1151. update_user_meta($user->ID, 'ame_show_hints', $show_hints);
  1152. }
  1153. /**
  1154. * Enqueue a script that fixes a bug where pages moved to a different menu
  1155. * would not be highlighted properly when the user visits them.
  1156. */
  1157. public function enqueue_menu_fix_script() {
  1158. wp_enqueue_script(
  1159. 'ame-menu-fix',
  1160. plugins_url('js/menu-highlight-fix.js', $this->plugin_file),
  1161. array('jquery'),
  1162. '20120709',
  1163. true
  1164. );
  1165. }
  1166. /**
  1167. * A callback for the stub meta box added to the plugin's page. Does nothing.
  1168. *
  1169. * @return void
  1170. */
  1171. function noop(){
  1172. //nihil
  1173. }
  1174. public function display_survey_notice() {
  1175. //Handle the survey notice
  1176. $hide_param_name = 'ame_hide_survey_notice';
  1177. if ( isset($this->get[$hide_param_name]) ) {
  1178. $this->options['display_survey_notice'] = empty($this->get[$hide_param_name]);
  1179. $this->save_options();
  1180. }
  1181. $display_notice = $this->options['display_survey_notice'] && $this->current_user_can_edit_menu();
  1182. if ( isset($this->options['first_install_time']) ) {
  1183. $minimum_usage_period = 3*24*3600;
  1184. $display_notice = $display_notice && ((time() - $this->options['first_install_time']) > $minimum_usage_period);
  1185. }
  1186. if ( $display_notice ) {
  1187. $free_survey_url = 'https://docs.google.com/spreadsheet/viewform?formkey=dERyeDk0OWhlbkxYcEY4QTNaMnlTQUE6MQ';
  1188. $pro_survey_url = 'https://docs.google.com/spreadsheet/viewform?formkey=dHl4MnlHaVI3NE5JdVFDWG01SkRKTWc6MA';
  1189. if ( apply_filters('admin_menu_editor_is_pro', false) ) {
  1190. $survey_url = $pro_survey_url;
  1191. } else {
  1192. $survey_url = $free_survey_url;
  1193. }
  1194. $hide_url = add_query_arg($hide_param_name, 1);
  1195. printf(
  1196. '<div class="updated">
  1197. <p><strong>Help improve Admin Menu Editor - take the user survey!</strong></p>
  1198. <p><a href="%s" target="_blank" title="Opens in a new window">Take the survey</a></p>
  1199. <p><a href="%s">Hide this notice</a></p>
  1200. </div>',
  1201. esc_attr($survey_url),
  1202. esc_attr($hide_url)
  1203. );
  1204. }
  1205. }
  1206. /**
  1207. * Capture $_GET and $_POST in $this->get and $this->post.
  1208. * Slashes added by "magic quotes" will be stripped.
  1209. *
  1210. * @return void
  1211. */
  1212. function capture_request_vars(){
  1213. $this->post = $_POST;
  1214. $this->get = $_GET;
  1215. if ( function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc() ) {
  1216. $this->post = stripslashes_deep($this->post);
  1217. $this->get = stripslashes_deep($this->get);
  1218. }
  1219. }
  1220. } //class
  1221. endif;
  1222. ?>