TPR10: Plug It In: Writing Better WordPress Plugins

Curtiss Grymala 
University Webmaster, University of Mary Washington


The audio for this podcast can be downloaded at http://2011.highedweb.org/presentations/TPR10.mp3


 

Announcer: This is one in a series of podcasts from the HighEdWeb Conference in Austin, 2011.

Curtiss Grymala: Good morning, everybody. My name is Curtiss Grymala . I know I've talked to a lot of you individually throughout the conference and probably even in the past. But I just wanted to introduce myself quickly. I am the University Webmaster at the University of Mary Washington in Fredericksburg, Virginia.

We, two weeks ago, finally launched our brand new website. It is completely 100% built on WordPress. And it's an extremely unique setup in that it's not a single WordPress instance. It's not even a single WordPress multi-site instance. It's what called a WordPress Multi Network instance, which puts another level on top of multi-sites.

So you're still working from a single installation and a single database in our case, but you now have multiple multi-site instances within the install. So we have 33 multi-site instances within a single installation and across is we have 228 sites.

So, oh, I better close my TweetDeck.

[Laughter]

Curtiss Grymala: Let's see. All right, here we go. OK, and for those of you that might have just noticed, this presentation is in a browser. That's because this presentation is a WordPress theme. This is a WordPress archive page. And someday I'll do a presentation on how you can do that as well. But that's not for today.

So quickly, I want to roll through some of the basic concepts of writing a WordPress plugin but I'm going to try and kind of fly through this so that we have time to go over real world examples and talk about what you guys want to talk about.

But first, coffee? If anybody can tell me who the WordPress developer is that implemented the oEmbed system within WordPress, you got a $10 Starbucks gift card. Anybody?

02:06

Audience 1: Mark Jaquith something?

Curtiss Grymala: Nope. It's not Mark Jaquith. No, it's not me.

[Laughter]

Curtiss Grymala: All right. Well, you guys Google that while I'm presenting or Bing it or whatever.

Okay, quickly, and again, my slides, they are websites. I will send the PDF to HighEdWeb so that they can post it on the website. So if you want to review this stuff that I'm going to kind of fly through, because I imagine that at least 99% of you are able to read, I'm just going to kind of fly through the stuff that's on the prepared slides so we can get to the stuff you guys actually want to talk about.

But anyway, read the Codex. Figure out what it says about writing a plugin.

Read the "WordPress Coding Standards". They are extremely important. They may seem kind of stupid at first but when you start to use them, you realize there is good logic behind why they have those rules set up.

Then here's some basic concepts. I recommend using PHP classes for everything when you're writing a plugin even if it's only like two lines. Set it up in a class because you will need that later on. One of the big things is, if you ever want to extend your plugin and make it do something else without touching that particular plugin -- for instance, you wanna give your college or university it's own functionality based on that plugin but the rest of the plugin is like a public repository type thing -- if you're not using a class, it makes it infinitely more difficult to extend that capability within your plugin.

Avoid offering too many options. You see so many plugins that are more difficult to use than setting the clock on your VCR. One of my plugins is even guilty of that. Well, actually, probably two of them. But that's because they work with active directory where you need lots of setup and everything to configure it. But for the most part, you want to start probably even with no options when you first write a plugin. And then as you get a requests for features and changes and things like that, start building in options. You'll get a fewer support requests if you don't put in too many options.

04:21

Use native WordPress functions whenever possible. And that's what we're going to go over, kind of, in this presentation.

Do not create any new database tables unless it is 150% necessary.

Giving you a little bit more back story on our installation. Again, we have 228 sites, which normally in WordPress I think would comprise abut 2500 tables if you are working with like the 67 tables that WordPress creates for each site. We have one plugin that creates four new tables for every site in our database. So we have 4500 tables in our database instead, which really bothers me. And I think that plugin's about to go away. That is Broken Link Checker, which I've also been told as a resource hog.

So that's plugin's probably getting ready to go away. And who knows maybe in another six months, I'll have a solution I can release to the community. But I have a feeling that will be next on my list of things I need to do.

Only use what you need when you need it. If you're writing JavaScript for your plugin, you're writing CSS or even querying the database, don't do it unless you're actually going to use it. I see so many plugins that will include a a five-kilobyte JavaScript file on every single page you load, whether you're in the Admin Center or on the front end And it only inserts like a box on one one page on your site. That's a lot of overhead and that's an extra Apache request that you're making that can slow down your site. So only include it if you need it, only use it when you need it.

06:05

But if your plugin needs something, make sure you include it. Don't count on anything being there. Even functions and stuff like that, you should really test to make sure the function exists before you try to use them. I can't tell you how many times I've seen site throw out 500 errors because somebody was using a function they figured should exist but they didn't test to make sure that it did before they started relying on it.

I guess the resolution ended up being a little smaller that I thought it was.

So we only got one Master but there are quite a few people you can study. Viper007bond is one of them. Mark Jaquith. Let's see, Scribu and there are couple of other masters that you can kind of look at. And they all have very different coding styles. But they all have been writing, and Danaco O'heim, who I believe is Irish. They're all people that have very different coding styles but they know what they're doing and they write amazing plugins. And of course, Ron Rennick,as well.

So those are people that you probably want to look at, you want to study, and figure how their doing things. And again, you don't have to mask their coding style but see the way they set up plugins and use that.

Don't count on it. Stop using activation and deactivation hooks. WordPress provides those, but WordPress does not support them outside of single WordPress installations. If you set up multi-site and you want a network-activated plugin, those aren't going to fire except on one site in the network. So stop relying on activation and deactivation hooks. In addition to the fact that the deactivation hooks, hey, that's great if it's not something that vital to the deactivating plugin. But you know, I could delete the plugin manually and it would be gone and it would never fire that deactivation hook even on a regular one.

08:07

Don't hardcode anything. And again, if you're plugin needs it, include it.

And now we get in to some of the functions that you may use. So I want to take a quick moment to kind of pull the audience and ask you guys what you might be interested in hearing about. We got the settings API which basically is creating, retrieving and updating options. We got the metabox API which is fairly simple right now. But that's basically creating instead of trying to build out all your HTML for an admin page, you can just tell WordPress what you want out, puts it in its nice metabox format. So that in 3.3 when they refresh the Admin Center again, yours doesn't end up looking like 3.2 when someone upgrades to 3.3.

And then two very important classes in WordPress -- and if I I don't talk about them today, I want you to look them up because they are so important -- are the wpdb class which is how you query the database directly without using the helper functions. Yo use the methods that are built in to WPDB.

And then we also have WP_Http which is how you make http requests.

And I will tell you if you are using cURL in anything, stop now. WordPress has a helper class set up that will use cURL if cURL is also available but also has four fallbacks. So you're plugin won't die if someone doesn't have cURL on their server, which I've had to tell four or five plugin developers, because we don't have cURL on our server. I got to tell four or five plugin developers, "Hey guess what you're plugin doesn't work because you're relying on cURL." And they're all like, "Wow, I've never even heard of the WP_Http class." And I went, "Now you have, please start using it."

10:02

So those are kind of our options right now. I guess by show of hands, who would want to hear about the settings API?

OK. All right. By show of hands, who might want to hear a little bit about the metabox API?

OK. How about WPDB, the database class?

OK. All right. And then, the Http request class.

All right. So I think I probably got the most hands for the wpdb class. So we'll start with that and we'll roll into as much as we can do after that.

Actually, I don't have slides for that. I'm just going to show you WPDB. What WPDB is, again, it is a PHP class that they have set up within WordPress that will help you work with the database, no longer calling the PHP functions like MySQL Select and all those kinds of things. This is all set up within WordPress. However there are some things you need to know.

Obviously, one of them is that WordPress has helper functions. So if you're just retrieving an option or something like that from the database, most of the time you want to use like get_option instead of querying the database directly.

Wow. OK, let's try that in a different browser. OK. All right, so I'll talk for a minute while that's figuring out what it's doing.

WPDB , there are multiple methods set up within it. You know, there are five or six different methods you can use to actually retrieve information from the database. You got get_ver which will retrieve a single variable from the database, a single cell. So obviously, if you're trying to retrieve four or five columns of data or four or five rows of data, get_ver is not the function you want to use.

12:09

But then you got get_col which will retrieve a column from the database and you got get_row which will retrieve a row from the database. And then you've also got get_results which will just retrieve generic results. It doesn't care if it's multiple rows, it doesn't care if its multiple column. It basically like running a standard MySQL query.

Then, what we have are... Let's try this again. Then what we have are... You know, open it in Firefox. That's fine.

There are variables or properties as they're called when you're talking about classes within the WPDB object. And what they will do for you, for instance, every cable that you use in the database is stored as a property of the wpdb class. So, if you need to query for instance the site metatable which is again, that's only multi-site but that's where they store metadata about a network, about the multi-site installation. That's where they store the metadata for that.

Instead of trying to write in like wp_site meta which of course can change depending on how you install your WordPress, you just use the variable, the global variable, WPDB and then call the site meta property of the WPDB object. Because they store these tables. Basically, whatever the table name is it's the same name as the property. And so then, you can just call that database, that table, directly from the database.

One really helpful function that you don't see a whole lot of information about but if you're working with multi-site, you want to get to know this function. It is called set blog id and it's set_blog_id. Again, it's a method of the wpdb class and what this does...

14:14

OK, WordPress has helper functions to switch from one site in a multi-site network to another site so that you can retrieve data from it. Which is fantastic, except that you have to understand that when you use switch_to_blog and restore_current_blog which are those helper functions, they're not just changing database information. They are dumping WordPress' cache, rebuilding it and then adjusting all of the helper functions so that they'll work.

It can be extremely helpful bit I would warn you, please don't ever use it on the front end. If you have to switch between blogs, do it on the back end when the administrative action is happening, rather than trying to retrieve stuff on the front end. as much as possible.

But again, so it dumps all that cache into really, really resource-intensive function. So it gets really bad when you're trying to, for instance, loop through 228 sites. Instead what you can do, and still when you're trying to loop through 228 sites, you're still wouldn't extremely efficient. But what you can do is you can just use the set_blog_id method within WPDB.

And what that does is it just basically adjusts all of those properties with the table names and tells WordPress that you want to use the tables for the blog with this ID instead of the blog with the original ID that you were querying. So that then, you can query the tables still using the same property name. So you'd still use WPDB and the options property of WPDB. But this time it would query the options table for the new site that you switched to. And then you can just switch back. And what that does is it leave all the cache in place so that all the options were initially retrieved when you loaded the page for the blog that you're actually viewing or the site that you're actually viewing. All those things will remain in cache so you're not clearing that out and having to rebuild twice when you switch from one site to another and then back again.

16:23

The other thing to know is that the set_blog_id method will actually return the ID of the blog that you switched from. So if you need to store that so you can restore whatever you were using, it returns that ID so you can store it as a variable when you set the blog ID. And again, that helps so that when you need to go back your regular queries, you just set_blog_id again back to whatever it return the first time.

Restore_current_blog which is the helper function that goes back will... I'm trying to remember exact how it works but there's a lot of discussion right now about it. I believe it will go back to the original blog that you switched from. So if you switched to blog four times, restore_current_blog I believe is going to go all the way back to the first one it switched from rather than going back one step at a time. It may be the other way around, I have to look that up. I apologize for that. But there's an active discussion right now on how that should work. Because WordPress doesn't have a function of any kind to do the other side of it.

So if it does restore all the way back to beginning, it doesn't have a way to iterate back through. If it reiterates back through it doesn't have a way to go all the way back to the beginning. So they're working on that right now.

17:56

So like I said, set_blog_id can be one of your best friends. Now, getting back to database queries, I already told you about the get_ver and the get_col and the get_row and the get_results. If you need to just run a query, and not necessarily even return any results or even if you do want to return results, whatever, you just want to run a query on the database. Again, instead of using PHP, MySQL query and MySQL get, I guess, retrieve... It's been so long since I've used the PHP native functions. I don't remember what they are anymore. But you can just use Query and it will run just the general query on the database.

Now, a note about the wpdb class is it also has a method to help you avoid SQL injection. So instead of just using MySQL escape result or whatever it was in PHP again, I can't remember anymore, you can use wpdb->prepare. And what that does is you plug in, you prepare your query and you plug in your query but instead of actually inserting the actual, instead of putting the variable that you're trying to send to the query inside of the query, you use the printf notation.

So you will do, for instance, select ID from wpdb arrow options where option name equals and then you'll just put in a %s instead of actually putting in whatever variable you're trying to grab. And then you put a comma and you put in the value of whatever that %s is or percent %d or, you know. I think %s and %d are basically all the tools you use for that kind of notation. But then, WordPress will go in and go, "OK, this is a string. We need to surround it by quotes.We need to make sure it escape." It will do all that kind of stuff. "OK, this is an integer. We don't need to surround it by anything. We can just throw it right back to the query." And it does all the escaping for you.

20:19

So that's a really handy function to use. There are some things you need to watch out for. One of them is that if you're trying to use like an N, so like select from where ID N whatever and it's a comma separated list, that can get a little tricky when you're using the prepare. You may have to just throw that list, that comma separated list directly into the query.

And then, it can also get a little tricky when you're trying to use LIKE operators, when you use the % operant. Most of the time, if you put the % operant inside of the value parameter, WordPress will handle it properly. But you just have to remember that obviously, inside of the query %s are going to be first parsed to see if they're part of the printf notation before % as part of the query. So be careful with the % operators when you're using wpdb->prepare. But again, that will do everything that the mysql_real_escape query or whatever the real_escape string does and more.

So you can see an example here where they're actually insert into post metatable. These are your columns and then your values are just %d, %s and %s. And that's your query that you're sending and then WordPress sends it through printf or s printf or vs printf or whatever it does. And so you got a decimal here, puts that in. You got string and string.

And the other nice thing is, obviously, if you say that's a decimal and you send it as string, WordPress is going to basically say, "We're not going to do that" and it's going to throw back an error to you. So, if somebody's trying to inject something into something that should be a decimal or an integer, a number, WordPress is going to warn you about that.

22:15

All right. Now, let's see, I will roll down.

You can also see, we have quite a few properties here, the class variables. You can do $show_errors which is a Boolean just value. If you set it to 'true' within WPDB, it will start showing errors when you try to run things that don't work. $num_queries just holds the number of queries you've run. $last_query shows you actually the last query that was executed.

But one thing to keep in mind is that a lot of times, especially when you're using the helper functions, WordPress will run like 15 queries in a row to retrieve something. So even though you think it was selecting like posts from the post table when you're trying to show the last query, it will say it's selected something from the taxonomy table or something like that. Because it has to build all of those things in together. So if you're not directly using the wpdb class and you try and look at the last query, it most likely will not match what you expect it to.

Now, $queries, this is an interesting one. There is a debug constant you can set in your config file for this class object to save all of its queries. It saves it in this $queries variable. Normally, that $queries variable is empty, basically. But if you have that debug constant set, it will save every single query it runs. So if you've got something that's running really a lot slower that you would expect it to, you set SAVEQUERIES constant to 'true' within your config file.

23:58

And then, for instance, like in footer action, you can tell it to dump this $queries variable to a file or into the footer of your page or whatever and you can start seeing what's going on. And I've been using that a lot recently to try and debug which plugins are running things that they shouldn't be running on pages where they shouldn't be running. I found a couple of plugins that seem to be running about 30 or 40 queries on every page that really didn't need to be happening because that plugin wasn't even being used on that page.

So again, that goes back to that whole don't use it unless you need it thing.

$last_result, that's, of course, whatever was retrieved most recently. $col_info, that's going to be some information about your column. Again, you guys, can read. The $prefix and $base_prefix properties, the $prefix should be set to whatever the actual prefix is that goes in front of the table name. So if you're querying the options table, the prefix of the blog with an ID of like 7, the prefix could be like wp_7_. Of course, remember, not everybody uses wp_ their prefix, so do not rely on that. That's why these properties exist.

You know, we had one plugin where the wp_ was hard coded into the plugin. And so when we put it on our site -- where of course for security reasons, it's like, the first security recommendations you get in WordPress is not to use that standard prefix -- the plugin did nothing for us because we weren't using the standard prefix.

And then, $base_prefix is whatever you actually have set in your config file as the base prefix. In most WordPress installations, if it's just a single site installation you are working on, your $prefix and your $base_prefix are going to be the same thing. But when you go over to multi-site, your $base_prefix and your $prefix are going to start to change because that $prefix changes on a site by site basis, based on the idea of the site.

26:08

Let's see. Then, $blogid, that generally will match whatever your global current_blog_id variable is. However, sometimes, for instance, if you use the set_blog_id method within wpdb, it's going to change what this blog_id property is set to. But it may not change what the current blog_id global is set to. So you need to be careful about that. But again, when you set things back with set_blog_id, it will return whatever the blog id was of whatever you were switching from.

And then, they got the whole list of tables that are available. Again, these are actually are just the single site tables. They don't have the multi-site tables listed here. I probably have to go in and edit the Codex so that I can add that information. Which is another beautiful thing about WordPress, the documentation is complete in the CrowdSource. This is a Wiki, you can go in and edit the information as much as you need. But anyway, you can see a list of the tables that are available within a regular site setup. And then, when you set up multi-site, then there's also a blogs table and a site metatable.

And one more note, if you go the route that we did and you set up a multi-network setup, I do have some functions available that you can put in that will install one extra table, that is the M-network metatable. Normally in a multi-site installation, the site metadata is where your global options are stored. But when you go to a multi-network setup you have multiple sites. And so that site metatable is no longer your global options table. So I've got one more table that I set up and basically copy that option's API to work with multi-networks so that you still have those global options available.

28:14

And I'm more than happy to share that with anybody. It may be ugly as hell. And of course, I'm always looking for advice on how to fix things and make things better. I will never ever tell you that I am a perfect coder. I just try my best. So, you see something dumb when you're looking at my code, please let me know.

But anyway, so that's a basic review of what WPDB is.

Again, please don't ever try and use the MySQL_Select and mysql_query, all that kind of stuff unless there's a big caveat here. If you're retrieving a lot of information from the database, if you remember the properties that I was showing you down at the bottom, the variables that stores within WPDB, WPDB will store everything you retrieve in a single variable. So if you're retrieving a lot of information from your database, it could exhaust your memory or time out.

But that said, try it with WPDB first. And then, if that doesn't work, you can switch over to the PHP native functions. But again, you'd have to be pulling a lot of information from the database in order to exhaust that memory, in most cases. So WPDB will be more efficient, at least cleaner, than using MySQL.

In addition to the fact that I'm sure if you had a unique setup of WordPress, for instance, if you were using Postgre's SQL or if you were using Microsoft SQL or something like that, as long as the back end has been modified to work in that kind of setup, the wpdb class should still run the appropriate query that will convert them to whatever query language you're using. So that can make it a little more available for things.

30:14

All right. So, yes, question. Yeah.

Audience 2: Quick question, what were the WordPress helper functions to operate those applications?

Curtiss Grymala: Well, I mean, again, it's not really really something whether helper functions were displaying what you're retrieving with WPDB. Right. But if you were... There are a lot of different options, functions that you can use. There's get_option and get_site_option and, let's see. But the other thing is that, when you get to...

Well, I'm here. Let's see. I'm trying to find...

Yeah, those are your various options things. But even when you are running, for instance, the loop - the wp_query - which is a different function and a different class. The wp_query is generally the main thing that polls information, post information from the database. When you're running wp_query, it's running a whole bunch of queries all at once. And it's building it all together as objects and then, it's got its helper functions like the_content and the_title and all those things. But those are only working from the wp_query. Those will not work from direct queries to the database. Those aren't set up.

You can kind of, in a roundabout way, if you set the variable to post and then you run a function called setup_postdata, then it will make all those functions work. But you have to be careful changing that because that will change globally what that post object is. And then, when you try and exit out of whatever you were doing and go back into your regular loop, you've destroyed your post object so you can't get back to what you're doing.

32:17

And WordPress can get really confused sometimes. If you modified that post object or you modified the wp_query object inside of a loop somewhere and WordPress gets really confused and doesn't know how to finish rendering the page, because you were working with one object at the top and you were working with a completely different object down at the bottom. So be careful modifying that original query and that original post object.

All right. I think, at this point, I would like to just open it up to questions from you, guys. Because I'm sure I covered a lot, I didn't cover a lot. So I kind of just want to open it up to questions. We've got about ten minutes left. I'd love to hear your other questions and what you'd like me to talk about for a minute.

Yeah. Go ahead.

Audience 3: [indiscernible]

Curtiss Grymala: We do use some. There's one or two instances where we're using custom post types and custom taxonomies. For the most part, those are actually for our faculty experts program. I built a plugin basically basically that hold their bio information and everything from our old database. And then pulled all their contact information and built it in to their WordPress profile. And then, in the back end, when you load that page, it kind of combines their bio-information with their profile information and shows that it's one item. And so, there is some custom query, custom taxonomy and post type information going on there.

And let me just tell you, the taxonomy in WordPress, that's probably its weakest point as far as the database structure goes. Because in order to get a single taxonomy object, you have to query three tables. So it can become quite a nightmare when you start setting up the joints to run that query to retrieve like members of a single taxonomy.

34:27

Of course, if you have the idea of the taxonomy, generally you can do it with only two table cols. But if you don't have the ID, you have to hit three tables before you find the right ID and the right taxonomy before you can even start querying for post that match that taxonomy. So be careful with that. It can be extremely inefficient.

Yes.

Audience 4: When you're in a multi-network setup, can you give us an idea on how many plugins you are using and how many [indiscernible]?

Curtiss Grymala: Yeah, let's see. I believe we are using about 25 different plugins on a global basis. Then, we are also using probably about 15 like MU plugins, which MU plugins since I didn't really go over this. I will go over it in my post-recessional a little bit if you want to stop by later. MU plugins are, they used to mean they were for the WordPress MU which what became WordPress Multi-site. But now, since WordPress Multi-site is baked in to WordPress, what they've done with that is they've changed MU from multi-user to must-use.

What it means basically is, you create this folder in the WordPress folder structure. You put a PHP file in there and WordPress executes that PHP file. So they cannot be deactivated. They work everywhere you load a page. This MU plugin, at least, whatever code you got to execute automatically will execute automatically every time you load a page. So they can be extremely helpful. We got about 15 of those.

36:16

And I would say, between regular plugins and MU plugins, we are probably using about 12 to 15 plugins that I wrote. There are about four or five that we're using that are in the WordPress repository. I've released, I think, seven plugins in the WordPress repository. And then there are couple of others that I wrote basically, specifically for Mary Washington.

But again, as part of our credo at Mary Washington, we believe that as a public liberal arts institution, we're getting paid basically by the public and so the work we do, we're putting out to the public. So even if it is specific to Mary Washington, we are more than happy to share our code with you guys. I have it actually up in public sub-version repositories where you can pull it down. It's just since it's so specific to our institution, there wasn't much point releasing it to the public repository.

But let's see. We are running 29 network active plugins and we have 22 must-use plugins working on the system right now.

All right. Yeah.

Audience 5: Any commercial plug-ins?

Curtiss Grymala: Yes, we did purchased the suite from PluginBuddy, which mainly we purchased that for BackupBuddy usage. It does a decent job of backing things up and also allows you to migrate and duplicate sites. So it's been a fairly handy plugin.

38:00

And then, we also ended up using their Accordion and stuff like that. Because, the other thing is, the Accordion on our homepage, we were initially using a plugin called Tabbed Widgets. What that plugin does is it invokes the front end widget for every plugin that creates a widget when you're on the back end When you're viewing the widget page, it will invoke the front end It will invoke the actual widget code. I have no idea why.

But it does. It tries to build the widget somewhere in the background. And for coders, again, that are doing things properly and not using things where they shouldn't be using them so you're not using your front end JavaScript on the back end It started throwing errors all over the place and our widget page just stopped working when we had certain plugins activated with Tabbed Widgets.

So we had to kind of do a roundabout with Accordion because Accordion actually use these custom posts. So we kind of have to drag some widgets into a special widget area that I created, and then insert those widgets into custom posts and then pull those those custom posts into the Accordion. So it's a little muddied but it doesn't stop our administrative deskport from working. So that's a plus.

And then we also WPtouch, the developer license for that. We have gravity Forms, the developer license for that. And I think those are about the only commercial plugins we're using at this point. And we also commissioned a plugin from Ron Rennick. For those of you that haven't seen it, it's called Document Repository. And what it does is it lets you manage all of your documents from a site within a multi-site setup. And it make sure that all of your documents basically have permalinks.

So when you upload a new copy of the document, it stores the old one as a revision of that post and the link to the document doesn't change, no matter what filename they use when they uploaded it. It uses WordPress permalinks so that the document link doesn't change. So that we can have document managers manage the documents they own and then, you can insert those documents from anywhere in the multi-network setup and that link will never die because no matter what they upload back into that Document Repository, the links going to stay the same.

All right. Other questions. Yes.

40:24

Audience 6: One problem set that's kind of insidious with multi-site is capabilities and the difference between the capabilities in a regular, stand-alone site, WordPress multi-site, WordPress official page doesn't document it well. And I haven't had time to sit down and try to understand what the difference is. So for example, sites were using edit theme. Check edit theme capabilities to see if someone was allowed to see the theme options, but that actually allows you to edit

Curtiss Grymala: Yeah, right.

Audience 6: So the right thing is theme options and I haven't known what a good explanation as to the difference between an admin on a single site and an admin on a multi-site.

Curtiss Grymala: Yeah, there are a few capabilities that could get taken away. The edit_theme and edit_plugins are there. Delete, just about anything except for posts. It's taken away. So you can't delete users, you can't delete plugins, you can't delete themes. Unless you are Super Admin.

And that actually brings up a really good point that I forgot to make. And again, you'll probably see it when you're reading my slides if you choose to. But one of the things that is vitally important that very few plugin developer seem to do is use capabilities that makes sense to what your plugin actually does.

I mean, if your plugin modifies users, don't use the manage_options capability. Use edit_users. It seems to make sense but for some reason, plugin developers, probably 95% of the plugins in the repository just use the manage options capability no matter what their plugin does. Which can get really frustrating when you're trying to adjust permissions for various things. And then, of course, you got a few that when you get back to the back end, they're still using numerical capabilities.

Anyway, I think that's about all the time that I have. But I'm more than happy to talk with anybody about anything else throughout the rest of the conference. I will be here through tomorrow afternoon. So whenever you want to talk, that's great.