# Improving a system: Different types of WordPress admin pages

In a [previous article](https://carlalexander.ca/designing-system-wordpress-admin-pages/), we saw how to design a system for [WordPress admin pages](https://codex.wordpress.org/Administration_Menus). The article was an excellent resource for anyone looking for a system to solve that specific type of problem. But it was only a starting point. (That said, you should take the time to read it before reading this article.) All that we did was convert code from [another article](https://carlalexander.ca/designing-class-wordpress-admin-page/) into the WordPress admin page system. We didn't look at any other use cases besides the ones covered in that article already. But the reality is that there are a lot of them. For example, the article only looked at how to add submenu pages. But there are other types of admin pages besides that one. The admin page system should be able to handle them all. This is one of several advanced use cases that you might encounter with the WordPress admin page system. That's why the system as we saw it so far is a great place to start. But handing these uses cases like different types of admin pages might be necessary depending on the kind of project that you're working on.

# Top-level menus

The first type of admin page that we're going to look at is how to how to add a top-level menu page. Top-level menu pages are admin pages that appear in the sidebar of the WordPress admin panel. Most large plugins and themes have a top-level menu page, so it’s often essential to be able to handle that scenario. ## add\_menu\_page vs add\_submenu\_page

What distinguishes a top-level menu page from a submenu page is the function that you use to add the page. You register a top-level menu page using the [`add_menu_page`](https://developer.wordpress.org/reference/functions/add_menu_page/) function. Submenu pages use the [`add_submenu_page`](https://codex.wordpress.org/Function_Reference/add_submenu_page) function. This is the function that our WordPress admin page system uses at the moment. ## Splitting our interface into multiple ones

Going back to that system, we'd created the `MyPlugin_AdminPageInterface` interface. The job of that interface was to allow us to map arguments of the `add_submenu_page` function to our admin page objects. We're going to have to do the same thing for the `add_menu_page` function. This means that we're going to need two interfaces for admin pages instead of one. We're going to need one to identify top-level admin pages and one for submenu pages. So let's go ahead and do that. ```
/**
 * A WordPress top-level menu page.
 */
interface MyPlugin_MenuPageInterface
{

}

/**
 * A WordPress submenu page.
 */
interface MyPlugin_SubmenuPageInterface
{

}
```

So you can see the two interfaces above. The `MyPlugin_MenuPageInterface` interface will be the interface that we use with the `add_menu_page` function. Meanwhile, the `MyPlugin_SubmenuPageInterface` interface is the new interface that we'll use with the `add_submenu_page` function. Next, we have to decide what to do with our old `MyPlugin_AdminPageInterface` interface. Do we have to keep using it? The answer to that is "Yes, we have to keep using it." We need a way to identify both our `MyPlugin_MenuPageInterface` and `MyPlugin_SubmenuPageInterface` interfaces. And the best way to do that is by having both of them [inherit another interface](https://en.wikipedia.org/wiki/Subtyping). This is what we're going to use our old `MyPlugin_AdminPageInterface` interface for. ```
/**
 * A WordPress admin page.
 */
interface MyPlugin_AdminPageInterface
{
    // ...
}

/**
 * A WordPress top-level menu page.
 */
interface MyPlugin_MenuPageInterface extends MyPlugin_AdminPageInterface
{

}

/**
 * A WordPress submenu page.
 */
interface MyPlugin_SubmenuPageInterface extends MyPlugin_AdminPageInterface
{

}
```

So you can see the change to the `MyPlugin_MenuPageInterface` and `MyPlugin_SubmenuPageInterface` interfaces above. They both now inherit the `MyPlugin_AdminPageInterface` interface. This means that we can continue to use the `MyPlugin_AdminPageInterface` interface to identify WordPress admin page objects. It also means that we can keep all the [type hints](https://secure.php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration) that we had in the WordPress admin page system already. (Yay!) ## Rearranging all interface methods

Right now, the only interface with code in it is `MyPlugin_AdminPageInterface` interface. It still has all the methods that we'd added when it was the only interface used by the WordPress admin page system. But we can't keep it like this anymore. We need to modify it so that it only has the methods that are common to both `MyPlugin_MenuPageInterface` and `MyPlugin_SubmenuPageInterface` interfaces. We can figure out what those common methods are by looking at the arguments of the two functions that the interfaces map to. If we look at the `add_menu_page` and `add_submenu_page` functions, we find the following common arguments: - page\_title
- menu\_title
- capability
- menu\_slug
- callable

Now that we've figured out the arguments are common to both functions, we can edit the `MyPlugin_AdminPageInterface` interface. All that we have to do is remove the one non-common argument method which was the `get_parent_slug` method. You can see the updated `MyPlugin_AdminPageInterface` interface below: ```
/**
 * A WordPress admin page.
 */
interface MyPlugin_AdminPageInterface
{
    /**
     * Get the capability required to view the admin page.
     *
     * @return string
     */
    public function get_capability();

    /**
     * Get the title of the admin page in the WordPress admin menu.
     *
     * @return string
     */
    public function get_menu_title();

    /**
     * Get the title of the admin page.
     *
     * @return string
     */
    public function get_page_title();

    /**
     * Get the slug used by the admin page.
     *
     * @return string
     */
    public function get_slug();

    /**
     * Renders the admin page.
     */
    public function render_page();
}
```

The `get_parent_slug` method that we removed will go in the `MyPlugin_SubmenuPageInterface` interface like this: ```
```
/**
 * A WordPress submenu page.
 */
interface MyPlugin_SubmenuPageInterface extends MyPlugin_AdminPageInterface
{
    /**
     * Get the parent slug of the admin page.
     *
     * @return string
     */
    public function get_parent_slug();
}
```
```

The last step is to add the missing methods for the `add_menu_page` function to the `MyPlugin_MenuPageInterface` interface. Looking at the function, there are two missing arguments: `icon_url` and `position`. Let's add methods for those two arguments in the `MyPlugin_MenuPageInterface` interface. ```
/**
 * A WordPress top-level menu page.
 */
interface MyPlugin_MenuPageInterface extends MyPlugin_AdminPageInterface
{
    /**
     * Get the URL of the icon used by the admin page.
     *
     * You can also return:
     * - A base64-encoded SVG using a data URI, which will be colored to match the color scheme. This should begin with 'data:image/svg+xml;base64,'.
     * - The name of a Dashicons helper class to use a font icon, e.g. 'dashicons-chart-pie'.
     * - The value 'none' to leave div.wp-menu-image empty so an icon can be added via CSS.
     *
     * @return string
     */
    public function get_icon_url();

    /**
     * Get the position of the admin page in the menu order of the WordPress admin sidebar.
     *
     * @return int
     */
    public function get_position();
}
```

Above, you can see the two missing methods that we added to the `MyPlugin_MenuPageInterface` interface. The first one is the `get_icon_url` method which maps to the `icon_url` argument. The second one is the `get_position` which does the same thing but for the `position` argument. ## Updating our subscriber

Now that we've finished splitting our interfaces, we can start using them in our code. To do that, we're going to update the `MyPlugin_AdminPagesSubscriber` class. If you're using the `myplugin_add_admin_pages` function, the process will be the same as well. ```
/**
 * Subscriber that registers the plugin's admin pages with WordPress.
 */
class MyPlugin_AdminPagesSubscriber implements MyPlugin_SubscriberInterface
{
    // ...

    /**
     * Adds the plugin's admin pages to the WordPress admin.
     */
    public function add_admin_pages()
    {
        foreach ($this->admin_pages as $admin_page) {
            add_submenu_page(
                $admin_page->get_parent_slug(),
                $admin_page->get_page_title(),
                $admin_page->get_menu_title(),
                $admin_page->get_capability(),
                $admin_page->get_slug(),
                array($admin_page, 'render_page')
            );
        }
    }

    // ...
}
```

Above is the `add_admin_pages` method that we were using in the previous article to register our admin page objects with WordPress. It would loop through all the admin page objects in the `admin_pages` class variable. It would then call the `add_submenu_page` function using all the methods from the old `MyPlugin_AdminPageInterface` interface as the function arguments. ### Calling the right admin menu function

We're going to want to do a similar thing here. The big difference now is that we don't just call the `add_submenu_page` function anymore. We might want to call `add_menu_page` function instead. To determine which function we want to call, we're going to check which of our two interfaces the admin page implements. If it's the `MyPlugin_MenuPageInterface` interface, we're going to call the `add_menu_page` function. And if it's the `MyPlugin_SubmenuPageInterface` interface, we'll call the `add_submenu_page` function. ```
/**
 * Subscriber that registers the plugin's admin pages with WordPress.
 */
class MyPlugin_AdminPagesSubscriber implements MyPlugin_SubscriberInterface
{
    // ...

    /**
     * Adds the plugin's admin pages to the WordPress admin.
     */
    public function add_admin_pages()
    {
        foreach ($this->admin_pages as $admin_page) {
            if ($admin_page instanceof MyPlugin_MenuPageInterface) {
                add_menu_page(
                    $admin_page->get_page_title(),
                    $admin_page->get_menu_title(),
                    $admin_page->get_capability(),
                    $admin_page->get_slug(),
                    array($admin_page, 'render_page'),
                    $admin_page->get_icon_url(),
                    $admin_page->get_position()
                );
            } elseif ($admin_page instanceof MyPlugin_SubmenuPageInterface) {
                add_submenu_page(
                    $admin_page->get_parent_slug(),
                    $admin_page->get_page_title(),
                    $admin_page->get_menu_title(),
                    $admin_page->get_capability(),
                    $admin_page->get_slug(),
                    array($admin_page, 'render_page')
                );
            }
        }
    }

    // ...
}
```

And now you can see how that looks like in the updated `add_admin_pages` method above. We added a [conditional](https://carlalexander.ca/mastering-php-conditionals/) to our `foreach` loop. We start by checking if `admin_page` implements the `MyPlugin_MenuPageInterface` interface using the [`instanceof`](https://secure.php.net/manual/en/internals2.opcodes.instanceof.php) operator. If it does, we call the `add_menu_page` function using all our interface methods as arguments. If `admin_page` doesn't implement the `MyPlugin_MenuPageInterface` interface, we move to the next conditional statement. This one checks if `admin_page` implements the `MyPlugin_SubmenuPageInterface` interface. And if it does, we call the `add_submenu_page` function. It's worth noting that there's a bit more going on with our conditional than what we just described. First, we structured the conditional in a way that you can't register an admin page object as both a top-level menu page and a submenu page. Also if the admin page object implements both interfaces, we're going to register it as a top-level menu page over a submenu page. If it only implements the `MyPlugin_AdminPageInterface` interface, nothing will happen. This is another benefit of how we structured our conditional. We're making it enforce that only the classes that implement either the `MyPlugin_MenuPageInterface` interface or the `MyPlugin_SubmenuPageInterface` interface get added. Everything else gets ignored. # Network admin page

In a similar vein as a top-level menu page, we also have network admin pages. These are admin pages that show up in the [network administration dashboard](https://codex.wordpress.org/Network_Admin). You might want to have your admin page appear there as opposed to the regular admin screen when WordPress is configured as a [network](https://codex.wordpress.org/Glossary#Network). ## Leveraging inheritance

Adding a network admin page can be a lot easier than adding a top-level menu page. That's because there are no new functions to use. You can just make modifications to your existing classes using [inheritance](https://carlalexander.ca/using-inheritance-wordpress/). ```
class MyPlugin_AdminPage implements MyPlugin_SubmenuPageInterface
{
    // ...

    /**
     * Get the parent slug of the admin page.
     *
     * @return string
     */
    public function get_parent_slug()
    {
        return 'options-general.php';
    }

    // ...
}
```

First, let's imagine that we have an existing submenu page class like the `MyPlugin_AdminPage` class above. It implements our new `MyPlugin_SubmenuPageInterface` interface so that it gets added as submenu page. Its `get_parent_slug` method returns `options-general.php` which is the slug of the "Settings" menu in the regular WordPress admin dashboard. Now, let's say that you wanted to move this page to the "Settings" menu of the WordPress network admin dashboard. Well, you'd need to figure out what the `parent_slug` of the "Settings" menu is there. You can figure it out by looking at the URL of the "Settings" page in the WordPress network admin dashboard. If you did that, you'd see that the `parent_slug` is `settings.php`. So our network admin page class needs to return that instead of `options-general.php`. We can do that by extending the original `MyPlugin_AdminPage` class like this: ```
class MyPlugin_NetworkAdminPage extends MyPlugin_AdminPage
{
    // ...

    /**
     * Get the parent slug of the admin page.
     *
     * @return string
     */
    public function get_parent_slug()
    {
        return 'settings.php';
    }

    // ...
}
```

Above you can see the `MyPlugin_NetworkAdminPage` class which extends the `MyPlugin_AdminPage` class. Next, you'll notice that we [overrode](https://en.wikipedia.org/wiki/Method_overriding) the `get_parent_slug` method. The method now returns `settings.php` instead of `options-general.php`. ## Controlling who can see the network page

Now, overriding the `get_parent_slug` method was all that you needed to turn a submenu page into a network admin page. That said, you might still need to make further changes depending on how you control access to your admin page. Let's go back to the `MyPlugin_AdminPage` class. ```
class MyPlugin_AdminPage implements MyPlugin_SubmenuPageInterface
{
    // ...

    /**
     * Get the capability required to view the admin page.
     *
     * @return string
     */
    public function get_capability()
    {
        return 'install_plugins';
    }

    // ...
}
```

You can see above that the `get_capability` method of the `MyPlugin_AdminPage` class returns [`install_plugins`](https://codex.wordpress.org/Roles_and_Capabilities#install_plugins). This is the right capability to use for a regular WordPress site since only administrators will have it. But it's not as restrictive on a WordPress network installation. For network installations, you might want to use [`manage_network_plugins`](https://codex.wordpress.org/Roles_and_Capabilities#manage_network_plugins) instead. This would limit access to the admin page to only administrators of the entire WordPress network installation and not individual site administrators. So if you wanted to do that with the `MyPlugin_NetworkAdminPage` class, you'd just need to do this: ```
class MyPlugin_NetworkAdminPage extends MyPlugin_AdminPage
{
    // ...

    /**
     * Get the capability required to view the admin page.
     *
     * @return string
     */
    public function get_capability()
    {
        return 'manage_network_plugins';
    }

    // ...
}
```

We're doing the same thing that we did with the `get_parent_slug` method before. We overrode it in the `MyPlugin_NetworkAdminPage` class and changed its return value. It now returns `manage_network_plugins` instead of `install_plugins`. Again, this demonstrates why inheritance is such a powerful tool in object-oriented design. All that you had to do is overwrite the `get_capability` method, and you changed the behaviour of the class. That's why creating a network admin page is a lot easier than the top-level menu page. ## How to register the network admin page

The last thing that you need to do is determine when to register the correct admin page. To do that, you can just use the [`is_multisite`](https://codex.wordpress.org/is_multisite) function. If the `is_multisite` returns `true`, you use the `MyPlugin_NetworkAdminPage` class. Otherwise, you use the `MyPlugin_AdminPage` class. ```
$admin_page = is_multisite() 
            ? new MyPlugin_NetworkAdminPage()
            : new MyPlugin_AdminPage();
```

A simple way to use the `is_multisite` function is with the [ternary operator](https://www.php.net/manual/en/language.operators.comparison.php#language.operators.comparison.ternary) as shown above. We start by calling the `is_multisite` function. Then based on what it returns, we create a new `MyPlugin_NetworkAdminPage` object or a new `MyPlugin_AdminPage` object. ## Replacing inheritance with the ternary operator

Now, the introduction of the ternary operator allows us to explore another way of designing our admin page class. Instead of using inheritance to create our network admin page, we'll use the ternary operator inside the `MyPlugin_AdminPage` class. To begin, we're going to need to add a constructor to our `MyPlugin_AdminPage` class. ```
class MyPlugin_AdminPage implements MyPlugin_SubmenuPageInterface
{
    /**
     * Flag whether WordPress 
     *
     * @var bool
     */
    private $is_multisite;

    /**
     * Constructor.
     *
     * @param bool $is_multisite
     */
    public function __construct($is_multisite = false)
    {
        $this->is_multisite = $is_multisite;
    }

    // ...
}
```

The constructor has a single parameter: `is_multisite`. We want this to be the value returned by the `is_multisite` function. We then use the constructor to assign that value to the `is_multisite` class property. We then want to use that `is_multisite` class property with the `get_capability` and `get_parent_slug` methods. Instead of overriding both methods like we did earlier, we'll use the ternary operator to return the correct value for each method. You can see how in the updated `MyPlugin_AdminPage` class below. ```
class MyPlugin_AdminPage implements MyPlugin_SubmenuPageInterface
{
    /**
     * Flag whether WordPress 
     *
     * @var bool
     */
    private $is_multisite;

    /**
     * Constructor.
     *
     * @param bool $is_multisite
     */
    public function __construct($is_multisite = false)
    {
        $this->is_multisite = $is_multisite;
    }

    /**
     * Get the capability required to view the admin page.
     *
     * @return string
     */
    public function get_capability()
    {
        return $this->is_multisite ? 'manage_network_plugins' : 'install_plugins';
    }

    // ...

    /**
     * Get the parent slug of the admin page.
     *
     * @return string
     */
    public function get_parent_slug()
    {
        return $this->is_multisite ? 'settings.php' : 'options-general.php';
    }

    // ...
}
```

It's worth pointing out that neither approach is better than the other. If you prefer to use inheritance to solve this problem, you should. If the use of the ternary operator makes more sense to you, that's fine too! # Just one set of improvements

So this wraps up some of the improvements that you can make to the WordPress admin page system. These changes focused on how to add support to different types of WordPress admin pages. But there are other types of improvements that we can make to it. We'll explore those in other articles. *Photo Credit: [José Alejandro Cuffia](https://unsplash.com/photos/_4815u_ACqQ)*