wp_insert_post went into infinity loop problem or return blank page

If you customize WordPress and used wp_insert_post to insert a post into WordPress manually, you might face a problem like i do. The page will go into infinity loop and you ended up on an empty or blank page instead of returning you to your original page/post.  You will start digging into your own code before looking into code. Finally, you found nothing seems to go wrong with your code and start digging into WordPress code. There, you will find wp_insert_post causing you this problem.

wp_insert_post problem

the problem lies with user capability when one use wp_insert_post to give other user the ability to insert post into WordPress. wp_insert_post uses the method current_user_can to check whether the given user has the capability to publish a post. After that i too have no idea what went wrong. However, this only happen when you set your post_status as publish. If you happen to change publish into other status such as draft, you will find that you can successfully insert your WordPress post without the problem of going into an infinity loop. However, this doesn't happen to every WordPress setup. Apparently my TEST environment doesn't seems to get such behavior compared to my LIVE one. Furthermore, if you dig deeper into WordPress wp_insert_post used methods, you will find that none of them are using any expensive loop or redirect function. Therefore, i suspect one of the method in wp_insert_post is performing a checking with user capability and publish post. a lighten me if you manage to find out what's wrong.

wp_insert_post solution

But i want my user to have the capability of a subscribers but is able to publish a post themselves. If you are like me, you may want to try to bypass the checking on publish post. If you look into the code implementation of wp_insert_post, you will find that most of the checking are looking for the keyword publish. Another alternative to publish your post without using the keyword publish is to use alternative keyword, future. The keyword future allows you to publish your post almost instantly similar to publish but it is a bit slower as the schedule will have to take care of publishing the article of yours. That's it, you shouldn't be getting an infinity loop after using future instead of publish! Good luck!

Tutorial: disable _blank attribute from opening new window

Today is an interesting day. I was working on my project and found that i have a hard time looking for a way to disable my target attribute, _blank, which will open a new window and display the item there.  Surprisingly, i found tons of how to create pop out box and many discussion regarding setting up a window.open instruction. But no one care to explain how to stop or disable it! Hence, i decide to write it out myself.

Pop out box

We all know how to enable a pop out box. We can do that either by window.open or using target attribute _blank to pass the work to another window. We can also do that for form submission but bringing out another box is not very professional unless you are showing certain information. Hence, most of us will go with ajax or iframe way of synchronize submission. Nonetheless, pop out box is a necessity in our web environment. While opening a new window box is easy but how about disabling it?

Disable _blank attribute

Disabling a pop out box or anything that will pop out is pretty simple. If you try to recall how you disable those anchor link, you might have an idea how you are going to disable all other types of link. Most pop out box is being initialize by a click. Hence, the event that we are interested with is onclick event handler. However, different people will have different ways of disabling an anchor link such as

<a href="JavaScript: void();">link</a>

or

<a href="#">link</a>

But we are interested in

<a href="JavaScript: return false;">link</a>

In order to disable pop out, it is the same as how we used to disable link. Hence, if you have a link such as this,

<a href="http://somewhere.com" target="_blank">link</a>

it will bring you to somewhere.com on a new pop out box. And if you want to prevent that, you will do this.

<a href="http://somewhere.com" target="_blank" onclick='return false;'>link</a>

That's it! To prevent window.open, we just remove that sentence! haha..

WordPress filter hook pre_comment_approved

I was really busy this whole month which makes me neglected Hungred Dot Com a lot. But i will still try to keep up with it as soon as possible. This article will definitely help ANYONE who uses wordpress filter hook 'pre_comment_approved'. I search Google for a while but couldn't get any help so i decided to investigate instead. The documentation of this hook is a bit screwed up. I'm sure they will edit it in the future. Nonetheless, for people who are having problem using this (you should be). Here is a solution i can provide.

Problem with pre_comment_approved

The problem i am having with pre_comment_approved is that i am UNABLE to retrieve the comment id that should be attached with this filter hook. Instead, the parameter for this action hook is just a simple status to tell you what status is being applied to a particular comment. The purpose of having this filter hook is to enable anyone to change the status into something else instead of approve eg, 'spam'. But some of us will want to do something else with it and we would like to know which comment has been approved. On the documentation, it said that there is a global variable $comment_ID which you can use in the function that you used to hook pre_comment_approved. Unfortunately, it doesn't exist. Try using the global variable $comment. You will get null (i have no idea why too). So how do you identify comment are we dealing with?

Solution to find comment ID in pre_comment_approved

After cracking my brain and stress for a while. I stare at the core code of WordPress and found some global variable to test (that is why i know $comment_ID and $comment was empty/null). There is a global variable $comment_id instead of $comment_ID in the core implementation of WordPress. But both of them gives you nothing. Hence, if you like to find the whole detail, your/my best bet will be the global variable $commentdata. Here is a simple way of getting your pre_comment_approved filter work.

add_filter('pre_comment_approved', array($this, 'alertUser'),);
public function alertUser($status){
	global $wpdb, $commentdata;
	echo $commentdata['comment_author'];
	echo $commentdata['comment_author_email'];
	echo $commentdata['comment_content'];
	echo $commentdata['comment_post_ID'];
	// etc..
}

With this, you should save some time figuring how and why the heck it is not working ($comment_ID, this is misleading).

Get Tag With Post ID In WordPress

Today i was writing my plugin and required each post tag to be placed into a variable outside of WordPress loop. I have a look at WordPress function reference api and did some Google and surprisingly i couldn't find it. After a long time of digging on Google, i finally found a clue. Apparently, the method get_the_tags takes in a post id! However, if you look at get_the_tags reference page, it doesn't mention at all. Hence, i decides to write it down here to ease people life in the future.

Get Tag In WordPress

Once you get hold of your Post ID you can easily retrieve the tag associated with the post with the following code.

			$postid = $post->ID;
			get_the_tags($postid);

It will return a list of tags. Hope it helps 🙂

Remove WordPress Admin Menu Without Affecting WordPress Core System

In WordPress, each user type have different capability. Sometimes, we want to change these capability and the most easiest way to do that is to remove what they can see when they logged in. Especially when someone wants to change WordPress into a powerful CMS and remove certain admin menu without touching the core system. In most CMS, there are so much restriction on their core system that makes it really inflexible. Unlike other system, WordPress is able to modify their core codes using plugin without affecting the fundamental codes in WordPress. Hence, we can continue to upgrade our system without having to worry about updates that will kill your modification. In this article, i will show you how i remove WordPress admin menu using plugin style without affecting WordPress Core codes.

WordPress Admin Menu

If you are able to dig into WordPress code, you will notice that their menu is created by a single function using two global variables as parameter. You can easily find this code located at wp-admin/menu-header.php, line 157-158.

_wp_menu_output( $menu, $submenu );
do_action( 'adminmenu' );

From the look of the method, you would have easily guess that this method is also a global method which takes in global variables $menu and $submenu to construct a full flag admin menu in WordPress. However, this method is like a loop that takes in a variable and loop through whatever is contain in the variable given. Hence, we will have to look at how each global variable is built to determine how to properly remove a WordPress admin menu.

Global Variable - $menu

If you dig deeper into WordPress, you will notice that the global variable $menu and $submenu are located at wp-admin/menu.php, line 25 onwards. This two variables play an important part in our objective as they create and remove main and submenu in WordPress. If you look at the code from line 28-115, you will notice that both menu and submenu is constructed first regardless of permission.

$menu[0] = array( __('Dashboard'), 'read', 'index.php', '', 'menu-top', 'menu-dashboard', 'div' );

$menu[4] = array( '', 'read', 'separator1', '', 'wp-menu-separator' );

$menu[5] = array( __('Posts'), 'edit_posts', 'edit.php', '', 'open-if-no-js menu-top', 'menu-posts', 'div' );
	$submenu['edit.php'][5]  = array( __('Edit'), 'edit_posts', 'edit.php' );
	/* translators: add new post */
	$submenu['edit.php'][10]  = array( _x('Add New', 'post'), 'edit_posts', 'post-new.php' );

	$i = 15;
	foreach ( $wp_taxonomies as $tax ) {
		if ( $tax->hierarchical || ! in_array('post', (array) $tax->object_type, true) )
			continue;

		$submenu['edit.php'][$i] = array( esc_attr($tax->label), 'manage_categories', 'edit-tags.php?taxonomy=' . $tax->name );
		++$i;
	}

	$submenu['edit.php'][50] = array( __('Categories'), 'manage_categories', 'categories.php' );

$menu[10] = array( __('Media'), 'upload_files', 'upload.php', '', 'menu-top', 'menu-media', 'div' );
	$submenu['upload.php'][5] = array( __('Library'), 'upload_files', 'upload.php');
	/* translators: add new file */
	$submenu['upload.php'][10] = array( _x('Add New', 'file'), 'upload_files', 'media-new.php');

$menu[15] = array( __('Links'), 'manage_links', 'link-manager.php', '', 'menu-top', 'menu-links', 'div' );
	$submenu['link-manager.php'][5] = array( __('Edit'), 'manage_links', 'link-manager.php' );
	/* translators: add new links */
	$submenu['link-manager.php'][10] = array( _x('Add New', 'links'), 'manage_links', 'link-add.php' );
	$submenu['link-manager.php'][15] = array( __('Link Categories'), 'manage_categories', 'edit-link-categories.php' );

$menu[20] = array( __('Pages'), 'edit_pages', 'edit-pages.php', '', 'menu-top', 'menu-pages', 'div' );
	$submenu['edit-pages.php'][5] = array( __('Edit'), 'edit_pages', 'edit-pages.php' );
	/* translators: add new page */
	$submenu['edit-pages.php'][10] = array( _x('Add New', 'page'), 'edit_pages', 'page-new.php' );

$menu[25] = array( sprintf( __('Comments %s'), "<span id='awaiting-mod' class='count-$awaiting_mod'><span class='pending-count'>" . number_format_i18n($awaiting_mod) . "</span></span>" ), 'edit_posts', 'edit-comments.php', '', 'menu-top', 'menu-comments', 'div' );

$_wp_last_object_menu = 25; // The index of the last top-level menu in the object menu group

$menu[59] = array( '', 'read', 'separator2', '', 'wp-menu-separator' );

$menu[60] = array( __('Appearance'), 'switch_themes', 'themes.php', '', 'menu-top', 'menu-appearance', 'div' );
	$submenu['themes.php'][5]  = array(__('Themes'), 'switch_themes', 'themes.php');
	$submenu['themes.php'][10] = array(__('Editor'), 'edit_themes', 'theme-editor.php');
	$submenu['themes.php'][15] = array(__('Add New Themes'), 'install_themes', 'theme-install.php');

$update_plugins = get_transient( 'update_plugins' );
$update_count = 0;
if ( !empty($update_plugins->response) )
	$update_count = count( $update_plugins->response );

$menu[65] = array( sprintf( __('Plugins %s'), "<span class='update-plugins count-$update_count'><span class='plugin-count'>" . number_format_i18n($update_count) . "</span></span>" ), 'activate_plugins', 'plugins.php', '', 'menu-top', 'menu-plugins', 'div' );
	$submenu['plugins.php'][5]  = array( __('Installed'), 'activate_plugins', 'plugins.php' );
	/* translators: add new plugin */
	$submenu['plugins.php'][10] = array(_x('Add New', 'plugin'), 'install_plugins', 'plugin-install.php');
	$submenu['plugins.php'][15] = array( __('Editor'), 'edit_plugins', 'plugin-editor.php' );

if ( current_user_can('edit_users') )
	$menu[70] = array( __('Users'), 'edit_users', 'users.php', '', 'menu-top', 'menu-users', 'div' );
else
	$menu[70] = array( __('Profile'), 'read', 'profile.php', '', 'menu-top', 'menu-users', 'div' );

if ( current_user_can('edit_users') ) {
	$_wp_real_parent_file['profile.php'] = 'users.php'; // Back-compat for plugins adding submenus to profile.php.
	$submenu['users.php'][5] = array(__('Authors & Users'), 'edit_users', 'users.php');
	$submenu['users.php'][10] = array(__('Add New'), 'create_users', 'user-new.php');
	$submenu['users.php'][15] = array(__('Your Profile'), 'read', 'profile.php');
} else {
	$_wp_real_parent_file['users.php'] = 'profile.php';
	$submenu['profile.php'][5] = array(__('Your Profile'), 'read', 'profile.php');
}

$menu[75] = array( __('Tools'), 'read', 'tools.php', '', 'menu-top', 'menu-tools', 'div' );
	$submenu['tools.php'][5] = array( __('Tools'), 'read', 'tools.php' );
	$submenu['tools.php'][10] = array( __('Import'), 'import', 'import.php' );
	$submenu['tools.php'][15] = array( __('Export'), 'import', 'export.php' );
	$submenu['tools.php'][20] = array( __('Upgrade'), 'install_plugins',  'update-core.php');

$menu[80] = array( __('Settings'), 'manage_options', 'options-general.php', '', 'menu-top', 'menu-settings', 'div' );
	$submenu['options-general.php'][10] = array(__('General'), 'manage_options', 'options-general.php');
	$submenu['options-general.php'][15] = array(__('Writing'), 'manage_options', 'options-writing.php');
	$submenu['options-general.php'][20] = array(__('Reading'), 'manage_options', 'options-reading.php');
	$submenu['options-general.php'][25] = array(__('Discussion'), 'manage_options', 'options-discussion.php');
	$submenu['options-general.php'][30] = array(__('Media'), 'manage_options', 'options-media.php');
	$submenu['options-general.php'][35] = array(__('Privacy'), 'manage_options', 'options-privacy.php');
	$submenu['options-general.php'][40] = array(__('Permalinks'), 'manage_options', 'options-permalink.php');
	$submenu['options-general.php'][45] = array(__('Miscellaneous'), 'manage_options', 'options-misc.php');

Furthermore, its being done neat and nicely. After that a few loop is conducted to remove the menu and submenu according to the user permission. You can see that on line 152 - 209.

$_wp_submenu_nopriv = array();
$_wp_menu_nopriv = array();
// Loop over submenus and remove pages for which the user does not have privs.
foreach ( array( 'submenu' ) as $sub_loop ) {
	foreach ($$sub_loop as $parent => $sub) {
		foreach ($sub as $index => $data) {
			if ( ! current_user_can($data[1]) ) {
				unset(${$sub_loop}[$parent][$index]);
				$_wp_submenu_nopriv[$parent][$data[2]] = true;
			}
		}

		if ( empty(${$sub_loop}[$parent]) )
			unset(${$sub_loop}[$parent]);
	}
}

// Loop over the top-level menu.
// Menus for which the original parent is not acessible due to lack of privs will have the next
// submenu in line be assigned as the new menu parent.
foreach ( $menu as $id => $data ) {
	if ( empty($submenu[$data[2]]) )
		continue;
	$subs = $submenu[$data[2]];
	$first_sub = array_shift($subs);
	$old_parent = $data[2];
	$new_parent = $first_sub[2];
	// If the first submenu is not the same as the assigned parent,
	// make the first submenu the new parent.
	if ( $new_parent != $old_parent ) {
		$_wp_real_parent_file[$old_parent] = $new_parent;
		$menu[$id][2] = $new_parent;

		foreach ($submenu[$old_parent] as $index => $data) {
			$submenu[$new_parent][$index] = $submenu[$old_parent][$index];
			unset($submenu[$old_parent][$index]);
		}
		unset($submenu[$old_parent]);

		if ( isset($_wp_submenu_nopriv[$old_parent]) )
			$_wp_submenu_nopriv[$new_parent] = $_wp_submenu_nopriv[$old_parent];
	}
}

do_action('admin_menu', '');

// Remove menus that have no accessible submenus and require privs that the user does not have.
// Run re-parent loop again.
foreach ( $menu as $id => $data ) {
	// If submenu is empty...
	if ( empty($submenu[$data[2]]) ) {
		// And user doesn't have privs, remove menu.
		if ( ! current_user_can($data[1]) ) {
			$_wp_menu_nopriv[$data[2]] = true;
			unset($menu[$id]);
		}
	}
}

Now we have a basic understanding of how WordPress handle their admin menu according to user access. We are ready to remove or modify any user access to alter WordPress user capability to view a particular menu in WordPress.

Removing WordPress Admin Menu

After having you wasting your time reading all the way from the top to here, i finally getting back to track and write what this article is about. We understand from all the rubbish on top that the global variable $menu array contains all the top level menu item and the global variable $submenu array contains all submenu page of each top level menu item. We can perform a few methods to remove a WordPress Admin Menu. The first simple and very basic way of removing a WordPress admin menu is to unset the menu resist in the global array.

function remove_submenu() {
global $submenu;
//remove Theme editor
unset($submenu['themes.php'][10]);
}

function remove_menu() {
global $menu;
//remove post top level menu
unset($menu[5]);
}
add_action('admin_head', 'remove_menu');
add_action('admin_head', 'remove_submenu');

We can remove a set of admin menu by doing this.

function remove_menus () {
global $menu;
		$restricted = array(__('Dashboard'), __('Posts'), __('Media'), __('Links'), __('Pages'), __('Appearance'), __('Tools'), __('Users'), __('Settings'), __('Comments'), __('Plugins'));
		end ($menu);
		while (prev($menu)){
			$value = explode(' ',$menu[key($menu)][0]);
			if(in_array($value[0] != NULL?$value[0]:"" , $restricted)){unset($menu[key($menu)]);}
		}
}
add_action('admin_menu', 'remove_menus');

The above code can be further reduce by 4 lines but i will keep it as it is.

This is pretty simple but not all user have these access and you might want to do some checking before accessing an invalid array to be unset which might give an error to be display. Although this is simple and the objective is achieve by removing the display of the menu/submenu, the user will still be able to direct access via the url. Hence, we need something better. Something that will disable all admin menu and sub menu tab from accessing and viewing.

function remove_menus () {
global $menu, $submenu, $user_ID;
	$the_user = new WP_User($user_ID);
	$valid_page = "admin.php?page=contact-form-7/admin/admin.php";
	$restricted = array('index.php','edit.php','categories.php','upload.php','link-manager.php','edit-pages.php','edit-comments.php', 'themes.php', 'plugins.php', 'users.php', 'profile.php', 'tools.php', 'options-general.php');
	$restricted_str = 'widgets.php';
	end ($menu);
	while (prev($menu)){
		$menu_item = $menu[key($menu)];
		$restricted_str .= '|'.$menu_item[2];
		if(in_array($menu_item[2] , $restricted)){
			$submenu_item = $submenu[$menu_item[2]];
			if($submenu_item != NULL){
				$tmp = $submenu_item;
				$max = array_pop(array_keys($tmp));
				for($i = $max; $i > 0;$i-=5){

					 if($submenu_item[$i] != NULL){
						$restricted_str .= '|'.$submenu[$menu_item[2]][$i][2];
						unset($submenu[$menu_item[2]][$i]);
					}
				}
			}
			unset($menu[key($menu)]);
		}
	}
	$result = preg_match('/(.*?)\/wp-admin\/?('.$restricted_str.')??(('.$restricted_str.'){1})(.*?)/',$_SERVER['REQUEST_URI']);
	if ($result != 0 && $result != FALSE){
		wp_redirect(get_option('siteurl') . '/wp-admin/' . $valid_page);
		exit(0);
	}
}
add_action('admin_menu', 'remove_menus');

The above function did just the thing. We will only required to provide the file name of the top level category and it will automatically disable all access to the subsequence admin sub menu. However, we will have to provide a valid page for user to gain access for the first time. The above code will disable ALL ADMIN MENU AND SUB MENU FOR ALL WORDPRESS ACCESS. The only page that was left accessible are the custom admin menu created by our user such as TweetMeme or Contact 7 form admin menu.

Easiest way to remove sub menu

After version 3.1, you are provided with the following methods to remove submenu

add_action( 'admin_menu', 'my_remove_menu_pages' );
function my_remove_menu_pages() {
        remove_menu_page('link-manager.php');
        //remove_menu_page('themes.php');
        remove_submenu_page( 'themes.php', 'themes.php' );
        remove_submenu_page( 'themes.php', 'theme-editor.php' );
        remove_submenu_page( 'themes.php', 'themes.php?page=custom-background' );
        remove_submenu_page( 'widgets.php', 'theme-editor.php' );
        remove_menu_page('tools.php');
        remove_menu_page('upload.php');
        remove_menu_page('edit-comments.php');
        remove_menu_page('plugins.php');
        remove_menu_page('admin.php?page=w3tc_general');
        remove_menu_page('admin.php?page=better_wp_security');
        remove_menu_page('admin.php?page=wpcf7');
        remove_submenu_page( 'index.php', 'update-core.php' );
        remove_submenu_page( 'options-general.php', 'options-discussion.php' );
        remove_submenu_page( 'options-general.php', 'options-writing.php' );
        remove_submenu_page( 'options-general.php', 'options-reading.php' );
        remove_submenu_page( 'options-general.php', 'options-permalink.php' );
        remove_submenu_page( 'options-general.php', 'options-media.php' );
}

Pretty simple and direct, first parameter is the file name or parent file name of the page you want to remove, second parameter is the submenu you wish to remove.

Concolusion

This is the way i use to remove admin menu in WordPress. We do not have to check for user access as the user will already be redirected to the specific page before they can enter the permission denial page. Have fun 🙂