WP-HidenMenuItems – плагин сокрытия пунктов меню сайта

WP-HidenMenuItems
WP-HidenMenuItems – это плагин для CMS WordPress, который обеспечивает сокрытые для всех посетителей выбранные пункты меню, страницы или посты, кроме отдельных посетителей сайта, которым доступ к скрытым элементам будет разрешен администратором сайта.

В официальном репозитории WordPress существуют немало плагинов, с помощью которых можно скрыть какую нибудь страницу или запись от посторонних глаз. Но что-то пошло не так… то ли плагины оказались уж очень сложны для меня, то ли я не разобрался. А мне надо было просто: сделать одну страничку на моем сайте: я пишу задание, внучка выполняет уроки, я смотрю результат. И что бы никто- (ни люди, ни роботы, ни rss, feed) не видели эту страничку, кроме меня и внучки. А если кто то и знает URL этой странички, то не смог бы получить доступ к ней.
Мне казалось, что задача простая. А впрочем, так оно и оказалось. Плагин получился небольшой, простой и думаю – надежный. Я знаю, как серьезно относится команда WordPress к вопросам безопасности. Поэтому, не смотря на маленький масштаб моего плагина, в нем безопасность на первом месте и так, что не будет мешать удобству пользователям и администраторам своих сайтов.
По этому – рекомендую.
После установки плагина WP-HidenMenuItems на сайт можно увидеть его на административной странице установленных плагинов и в кратком его описании есть ссылка “Setting“, которая прямо ведет на страницу настроек этого плагина. Хотя, можно попасть туда и традиционно: в админ панели – Appearance – Hiden menu items. Плагин универсальный в том смысле, что понимает: на сайте могут быть разные меню и в разном количестве.
Ниже представлена страница настроек плагина WP-HidenMenuItems
Settings WP-HidenMenuItems
Поэтому:
1. Select menu
Выбираете то меню, в котором Вы хотите что-то скрыть (страницу, пост). Нажимаете кнопку Select.
2. Select menu items to hide
Здесь находите нужный пункт меню, проверяете его нажав на ссылку этого пункта, ставите галочку слева и пишите пароль (до 12 символов) в правом поле.
3. Select users who are allowed access to hidden items menu
Здесь выбираете галочкой тех пользователей, которым доступ к скрытому пункту меню будет открыт.
Вот, собственно говоря и все. Дальше все происходит само, по канонам WordPress. Просмотр скрытого элемента меню будет недоступен для ВСЕХ, кроме определенных Вами пользователям, которым нужно будет один раз ввести пароль, он будет сохранен по канонам WordPress в cookie на стороне клиента в браузере и при повторном обращении к странице, посту сайт не будет спрашивать пароль. Как обычно. А если кто-то посторонний укажет прямой URL скрытой страницы, поста в адресной строке браузера – то получит 404 (not found).

Основные моменты в разработке плагина WP-HidenMenuItems

Ниже приведен фрагмент кода PHP создающий основной функционал плагина:

class HMI_Main {
	/**
	 * Objects of all menus and this structures.
	 *
	 * @var object
	 */
	public $hmi_menus;

	/**
	 * Constructor.
	 */
	public function __construct() {
		$this->hmi_menus = new HMI_StructureMenu();
		if ( is_admin() ) {
			// admin actions.
			add_action( 'admin_menu', array( $this, 'hmi_admin_menu' ) );
			add_action( 'admin_init', array( $this, 'hmi_register_mysettings' ) );
			add_action( 'admin_head', array( $this, 'hmi_screen_options' ) );
			add_filter( 'plugin_row_meta', array( $this, 'hmi_plugin_meta' ), 10, 2 );
		} else {
			// non-admin enqueues, actions, and filters.
			add_action( 'pre_get_posts', array( $this,'hmi_page_post_hiden' ) );
			add_filter( 'wp_get_nav_menu_items', array( $this, 'hmi_menu_items_hiden' ), 10, 3 );
		}
	}

	/**
	 * Forbide the display of a hidden page or post.
	 *
	 * @param object Object WP_Query.
	 */
	public function hmi_page_post_hiden( $query ) {
		$cur_user_id       = get_current_user_id();
		$hmi_options       = get_option('hmi_setting_fields', []);
		$arrs_chkbox_items = ( [] !== $hmi_options ) ? $hmi_options[0] : [];
		$arrs_users_items  = ( [] !== $hmi_options ) ? $hmi_options[1] : [];

		if( ! is_admin() && $query->is_main_query() ) {
			foreach ( $arrs_chkbox_items as $arr_key => $arr_chkbox_items ) {
				foreach ( $arr_chkbox_items as $key_chkbox => $value_chkbox ) {
					if ( $query->queried_object->ID === $key_chkbox && $value_chkbox ) {
						if ( [] !== $arrs_users_items ) {
							foreach ( $arrs_users_items[$arr_key] as $key_users => $value_users ) {
								if ( is_user_logged_in() && $cur_user_id === $key_users && $value_users ) {
									break 3;
								}
								if ( is_user_logged_in() && $cur_user_id === $key_users && ! $value_users ) {
									$query->is_404 = true;
									break 3;
								}
								if ( ! is_user_logged_in() ) {
									$query->is_404 = true;
									break 3;
								}
							}
						}
					}
				}
			}
		}
	}

	/**
	 * Hide items menu.
	 *
	 * @param string $items An array of menu item post objects.
	 * @param string $menu  The menu object.
	 * @param string $args  An array of arguments used to retrieve menu item objects.
	 * @return object $filtered_items Мenu items object.
	 */
	public function hmi_menu_items_hiden( $items, $menu, $args ){
		$hmi_options      = get_option('hmi_setting_fields', []);
		$arr_chkbox_items = ( [] !== $hmi_options ) ? $hmi_options[0][$menu->term_id] : [];
		$arr_users_items  = ( [] !== $hmi_options ) ? $hmi_options[1][$menu->term_id] : [];
		$cur_user_id      = get_current_user_id();

		foreach ( $items as $item ) {
			if ( ! $arr_chkbox_items[$item->object_id] ) {
				$filtered_items[] = $item;
			} else {
				if ( is_user_logged_in() && $arr_users_items[$cur_user_id] ) {
					$filtered_items[] = $item;
				}
			}
		}
		return $filtered_items;
	}

Эта круговерть с foreach связана с тем, что захотелось сделать сохраненные настройки плагина в БД очень компактными, например, сохраненные данные опции для двух меню сайта выглядят так:

// structure of option 'hmi_setting_fields' (for example)
// a:2:{
// 	 i:0;a:2:{ //menu items
// 		i:2;a:5:{i:11;b:0;i:16;b:0;i:31;b:0;i:33;b:1;i:36;b:0;} //items for menu id 2
// 		i:3;a:4:{i:1;b:0;i:19;b:0;i:22;b:1;i:41;b:0;}			//items for menu id 3
// 	 }
// 	 i:1;a:2:{ //users
// 		i:2;a:6:{i:1;b:1;i:2;b:1;i:3;b:0;i:4;b:0;i:5;b:0;i:6;b:0;} //users for menu id 2
// 		i:3;a:6:{i:1;b:0;i:2;b:1;i:3;b:0;i:4;b:0;i:5;b:0;i:6;b:0;} //users for menu id 3
// 	 }
// }

Ну и старый, добрый Ajax.
Этот модуль (ajax-menu-items-hiden.php) принимает данные от клиента (браузера) и сохраняет их по нажатию кнопки Save в option ‘hmi_setting_fields’ (строчки 21-45) и отправляет клиенту данные по нажатию кнопки Select (строчки 46-62):

/**
 * Save $arr[0][$hmi_selectMenuTermID] = $hmi_itemsObjectID  to option( 'hmi_setting_fields', $arr ).
 * Save $arr[1][$hmi_selectMenuTermID] = $hmi_itemsUsersSite  to option( 'hmi_setting_fields', $arr ).
 * Save post_password to wp_posts table of db.
 * Send $itemsObjectID, $hmi_chkboxItems, $hmi_itemsUsersSite, $arrPwd of selected menu from server.
 */
function hmi_menu_items() {
	$_hmi_selectedMenu     = filter_input( INPUT_POST, 'selectedMenu', FILTER_SANITIZE_STRING );
	$_hmi_selectMenuTermID = filter_input( INPUT_POST, 'selectMenuTermID', FILTER_SANITIZE_STRING );
	$_hmi_itemsObjectID    = filter_input( INPUT_POST, 'itemsObjectID', FILTER_SANITIZE_STRING );
	$_hmi_itemsPwd         = filter_input( INPUT_POST, 'itemsPwd', FILTER_SANITIZE_STRING );
	$_hmi_itemsUsersSite   = filter_input( INPUT_POST, 'itemsUsersSite', FILTER_SANITIZE_STRING );
	$_hmi_nonce            = filter_input( INPUT_POST, 'nonce', FILTER_SANITIZE_STRING );

	if ( !isset($_hmi_nonce) ) {
		wp_die( 'ajax-menu-items-hiden: nonce is empty.' );
	}
	if ( $_hmi_nonce !== wp_create_nonce( 'hmi' ) ) {
		wp_die( 'ajax-menu-items-hiden: nonce is incorrect.' );
	}
	// Save settings of plugin to server.
	if ( $_hmi_selectMenuTermID &&  $_hmi_itemsObjectID && $_hmi_itemsUsersSite ) {
		$hmi_selectMenuTermID = json_decode( htmlspecialchars_decode( $_hmi_selectMenuTermID ), false );
		$hmi_itemsObjectID    = json_decode( htmlspecialchars_decode( $_hmi_itemsObjectID ), true  );
		$hmi_itemsPwd         = json_decode( htmlspecialchars_decode( $_hmi_itemsPwd ), true  );
		$hmi_itemsUsersSite   = json_decode( htmlspecialchars_decode( $_hmi_itemsUsersSite ), true  );
		// Save settings to option( 'hmi_setting_fields' )
		$hmi_options = [];
		$hmi_options = get_option( 'hmi_setting_fields', [] );
		$hmi_options[0][$hmi_selectMenuTermID] = $hmi_itemsObjectID;
		$hmi_options[1][$hmi_selectMenuTermID] = $hmi_itemsUsersSite;
		update_option( 'hmi_setting_fields', $hmi_options );
		// Save post_password
		$my_post = array();
		foreach ( $hmi_itemsPwd as $key => $hmi_itemPwd ) {
			$my_post['ID'] = $key;
			$my_post['post_password'] = $hmi_itemPwd;

			if ( ! wp_is_post_revision( $key ) ){
				wp_update_post( wp_slash($my_post) );
			}
		}

		echo("ok");
	}
	// Send itemsObjectID of selected menu from server.
	if ( $_hmi_selectedMenu ) {
		$hmi_menus     = new HMI_StructureMenu();
		$itemsObjectID = $hmi_menus->getItemsObjectID( json_decode( htmlspecialchars_decode( $_hmi_selectedMenu ) ) );

		$hmi_options = get_option( 'hmi_setting_fields', [] );
		$hmi_chkboxItems    = ( [] !== $hmi_options ) ? $hmi_options[0] : [];
		$hmi_itemsUsersSite = ( [] !== $hmi_options ) ? $hmi_options[1] : [];

		$arrPwd = [];
		foreach ( (array) $itemsObjectID as $key => $item ) {
			$metaPost = get_post( $item->object_id, ARRAY_A  );
			$arrPwd[$item->object_id] = $metaPost['post_password'];
		}

		echo( json_encode( [$itemsObjectID, $hmi_chkboxItems, $hmi_itemsUsersSite, $arrPwd] ) );
	}

	wp_die();
}

Плагин WP-HidenMenuItems опубликован в официальном репозитории WordPress.