 Right. Good morning or good evening or good afternoon, wherever you are in the world today. Welcome to today's let's code section session. It's my first for the year so happy new year to those of you who are joining us for the first time this year, maybe some of you have done workshops already this year but it's my first. So looking forward to kicking the year off. While you're joining. If you're going to code along with me today please make sure you've got your local WordPress installation ready ready. You've got your coded it's ready and then you can go ahead and download the plugin zip file and store that on your WordPress site. This is a very simple little plugin that I wrote a number of years ago when I presented this same topic at a word camp. That I met Thelma who is my co is today so a little bit of a tie in there for you Thelma. Cool. So happy new year to everybody who's joining. Let us know where you're joining us from those of you who are coming in for the first time. My name is Jonathan I'm from Cape Town in South Africa. I'm not going to run through all the bits and pieces of who I am. I just want to mention before we get started I am extremely tired today I was saying this to film earlier. I had a very bad night sleep last night and I've been drinking way too much coffee just to stay awake so I apologize if things go a little bit wonky today I'm going to do my best. Okay. There we go. So just going through some announcements. Again welcome and happy new year to everybody. Thank you to Thelma who's co hosting with me today. We are as always presenting in focus mode but please do feel free to enable your video if you'd like to meet see you at least focus mode is just to prevent any kind of zoom bombing from happening we unfortunately had a situation where one of our other workshop presenters with zoom bump yesterday so we're sticking with focus mode from 2023. It does seem to work pretty well. As always you are welcome to ask questions. And you're welcome to either post them in the chat or unmute ask questions. The only thing that I do ask is if you please can keep your questions. If they're related to what we're doing on screen you're welcome to stop me and ask the question like something isn't clear. You're welcome to ask questions otherwise if your question is not specifically related to what we're doing on screen. Then just keep it for one of the pauses that we will that we will leave. So today I'm going to do things a little bit differently we're going to, I'm going to show you the code that we're working with. And then I'm going to as you're working through the workshop I'm going to ask for your input as to where you think we should implement these, these security practices that we're going to be implementing today so hopefully you'll be able to interact a little bit more today. Then, if you haven't already, if you want to code along with me please do install the plugin it's a very simple one, one PHP file one JavaScript file plugin. So if I'm going too fast please do slow me down let me know. There is a chance of that happening today because of the tiredness and the, and the coffee. But we will be posting the session to WordPress TV afterwards, sometimes in the course of the day tomorrow. And if you're looking for any other WordPress focus content, please do visit learn WordPress.org which is where all of this information tutorials, lesson plans workshops are all linked or hosted through. Okay. Thank you for listening to you on 1.5 in the recording so you sound very slow Miller. Thank you. It's something that I've really tried to work on it's just kind of slowing myself down because I tend to get very excited to talk very fast and rather have a lot of fun. Okay, so today we're going to be focusing specifically on plug in security, we're going back to the world of PHP today I'm not doing. We're going to be talking a lot of JavaScript but mostly PHP today because that's where all the data is sent back and forth. We're going to be talking about sanitizing our inputs, doing data validation, escaping our outputs, preventing invalid requests and how to prevent authenticated unauthenticated users from performing actions. I'm just diving into those topics super super in depth. I'm going to be giving you a very quick overview of each of these and hopefully it'll give you some documentation and some reading material that you can go further down deeper rabbit holes. Plug in security when you're developing plugins or if you're developing themes that have plugin like functionality is something that should always be at the forefront of your mind when you're writing code. So good ideas understand all of these things understand all the basics of how they work. And then when you're working with different types of data you can think about well, how should I sanitize this input or how should I validate this data or how should I prevent something from going wrong. So today is we're going to install and look at the plugin that I wrote and we will see that it is a perfectly functional plugin, but it has no security, so thinking about security at all so it's open to all kinds of vulnerabilities. And then we're going to look at any kind of incoming data that we're receiving from the user and how we would sanitize that. We're going to also look at how we can validate that data now the data that we're working with today is not very complex but there's a couple of options that we're going to look at for data validation. And then we're going to look at how we escape data being rented to the browser and why you would do that. Then we're going to look at securing form submissions because those submissions are being posted and we need to secure those those fields. And we're going to secure an Ajax request as well. And we'll talk a little bit about Ajax and REST API at the same time. And then we're also going to look at how you can ensure that any admin specific actions can only be performed by an admin. And then we're going to look at how you can secure form submissions and user capabilities within the WordPress space. Before we get started, I want to share something with you that I just thought of that I think would be useful and I think it's hack, I'm not sure. I'm not going to find it now. But basically one of the, I don't mind sharing my Twitter. I think it's hack. Yeah, yes. So one of the secure one of the WordPress security companies patch stack. They've created a Twitter account, a Twitter WAPU called Haku and Haku regularly shares any security vulnerabilities and plugins in the wild. So if you're the kind of person who's using plugins that are downloadable, and you want to know about these vulnerabilities when they come out, I do recommend following the stricter profile. And if you go through them, you'll see a lot of the kind of vulnerabilities that they talk about cross cross site scripting, privileged escalation, SQL injection. They're, they're, they're very, very common that they happen a lot. A lot of this is cross transcripting again. There's cross transcripting again. Untrusted data. And if you, if you follow the four or five principles that we're going to be talking about today, you will prevent the majority of these kind of issues in any kind of plugins or themes with plugin like functionality you create. So I do recommend following the stricter account if you are on Twitter. And keeping up to date with these kind of vulnerabilities so that you can understand what it means when they talk about these different kind of things and when you're developing your plugins, it's at the forefront of your mind. So I'm going to pop that link in the chat quickly, just so that folks have it if they, if they want to follow that account. Okay. So, let's go through the plugin itself before we get started so we can understand what it's doing if you want to have a look at the code on GitHub this is the URL for it. I have created both a insecure and a secure version so if you click on releases. And you'll see that there is this insecure plugin which is the one you've downloaded and installed today on your site if you're following along. And then there's a secure version which you can download after the session, and you can have a look at the code basically what we're going to be coding today is the secure version anyway. But if you're just following along and you want to see what the secure code looks like you can get it from there. It's also in a branch in this repository so if you go to the little branch drop down on the top left there, you can switch to the feature more secure plugin branch. And then you can have a look through the plugin code and you can see all the changes, and I'm going to leave that open so I can refer back to it if I can get something later. Okay. So what does the plugin do let's open up the plugin code first so we can see what it does so open up my visual code studio. And I'm going to browse over to the WP learn plugin security directory in the plugins directory and WordPress and I'm going to open up the plugin security PHP file. I'm going to run through this code fairly quickly, and then I'll pause at the end if there are any questions. There are a couple of constants being created that I'm using at the top of this plugin here. The one is I'm going to just keep it there. The first two are just URL slugs that I'm using to redirect users around and I'll show you how those get used in a second. The second two are just URL and path constants that I like to set up in all of my plugins. So I can reuse those if I need to link to a file in the plugin URL or find a file in the plugin path. Then I'm setting up a custom table. The main reason I'm doing a custom table for this plugin is purely just because it's easier to insert simple data into a custom table. There's not too much about what that's doing today. We're not focusing on creating tables today. We're going to be doing a future workshop on that. I'm sure at some point in time, but that's the code for how that works. But basically it's a table called form submissions, and it just has a name field and an email field, and then an ID primary key. That's all that exists there. So we're going to be creating a form that requires the name and email address and then stores it in the database. So there is the good old admin in Q scripts action, which if you've worked with plugins or WordPress themes at all before you'll know that this sets up any kind of custom JavaScript or CSS or whatever you need. And it's basically in queuing this assets admin.js file which we'll look at in a second. And it's creating the Ajax object which is used for any kind of Ajax functionality. If you've never seen this before don't worry too much I'll show you how this is working in a second. Elizabeth says are you screen sharing can you see your video but not your screen. I've had folks had this have this problem before Elizabeth. Are you perhaps running on a Linux workstation by chance. I am screen sharing and I think most folks are seamless. Yes, so this is a known problem that I'm still trying to solve. I need to test this with one of my, one of my colleagues I have an Ubuntu workstation so you're right next to me here, but I present on a Mac. I'm experiencing problems with the zoom on Linux having some issues. So I've got it on my to do list that I'm hoping to get to this year but unfortunately I don't have a work around for you right now so I'm sorry. If you have the code open you should be able to follow along while I'm talking. And let me know if you get stuck but I do apologize and I will, I will figure it out this year. Okay, so, so that's the in queuing of the JavaScript assets. This is the submission form code. And I'm just using a very simple short code. In this case it's WP underscore learn underscore form underscore short code. If you're following along and you and you're looking at the code it's online 71. And all that's doing is outputting a very simple HTML form with a hidden value, a name field and email field and a separate button that's nothing special. It's using output buffering to just kind of gather the content and then split it up to the screen. In line 98, I am hooking into the WP action, and I am hooking that to the WP learn maybe process form function. And what that does is that checks whether the hidden field WP learn form has been submitted in a request. And if it has been submitted then we need to process the form. And this is a common way to handle form submissions using PHP in a WordPress environment is hook into one of the early actions check if the field is there and then process that submission. There are other ways of doing it but this is just one way. What I'm doing in this function is I am getting the name and email values from the posted form, which is this dollar underscore post variable. It's an array. And I basically create an instance of the access an instance of the WordPress database object. And I insert the name and email fields into the database record, as a record it's the database. If everything goes fine, then I redirect to the learn slug, sorry, the success slug. If there's any problems then I redirect to the error slug and I'll show you how the redirection works in a second. So I create an admin menu sub menu item in the dashboard. And that's basically going to take us to a list of all the submissions in the admin interface. This is this is fairly straightforward if you have seen this before if you haven't, it just basically creating this WP learn admin page link. It's saying that the permissions are for anybody who can manage options or in other words an administrator. And it has a callback function function, which then calls the function just below that which is the WP learn render admin page function that just renders an admin page. It gets all the form submissions from the database using this WP learn get form submissions function, and then creates a dove with some data loops through the submissions and present someone screen. And then the learn learn get form submissions function that basically just pulls the submissions from the database. And then finally there's an Ajax hook to delete form submissions. So you'll see in the render admin page function there is a delete submission anchor with a class delete submission. If you click on that it triggers some JavaScript, which, which creates an Ajax request to this learn delete form submission function, which gets the ID from the Ajax request and deletes that record based on the ID in the database. So that's all the PHP code some of you might have seen some of the security issues they are ready. If you have great makes and loads when I ask you later if you have it not to worry. And then the last bit of code I want to look at is the admin.js JavaScript file in this plugin. This effectively just handles the Ajax request so when you click on the delete submission anchor for any of the records, it will get the button. It'll get the ID from the data attribute in the button. And then it makes a post request to the WordPress Ajax URL, which if you look back in the PHP code that Ajax URL is set up right at the top here. When we in queue the admin.js file, we use this localized script so what that basically does it creates an object called WP learn Ajax and passes any information in this array as object properties in the in the code and I'll show you what that looks like in a second. And it then will post specifically this action delete form submission which is how Ajax works in WordPress, and then it sends through the ID value as well. And then once that's finished then it just says forms mission deleted, if everything worked correctly in this Ajax post I don't have a failure set up on purpose I've done it that way on purpose, but you would typically have like a success event and a failure event and various other things I'm trying to keep this as simple as possible. Okay, while you're looking through this code or while I've discussed this code with you all is there any questions around what things are doing did you see anything that you maybe didn't understand. We can't dive too much into the functionality of how it works and what the different pieces are today because that's going to be many different workshops, but is there anything that didn't quite make sense about how I explained the code I'll show you all the functionality in a second. So make sure there are any questions around this code at this point in time. Okay, so they don't seem to be any questions there that's good. I'm sure they might question questions that come up later but let's have a look and see what this code actually does. So to make this code work if you want to test this code on your side there's a couple of pages you need to create first. The first page you'll need to create is called the submission form page. What I would like you to do if you are creating these pages is please create them with the same so first step before we do this first step. Please make sure that you have permalinks enabled in some shape or form on your local environment. So don't leave it on the first if you go into settings permalinks. Don't leave it on the plain option which is the default that ships with WordPress switch it to the either the post name or day and name month name whatever I generally stick it on post name because I like to keep it simple. The reason I want to do that is because you want to use the page slugs in the code and it'll make it easier to tie those things up. So if you haven't already please go into your settings permalinks and switch your permalink structure to post name for today to make everything easier and then scroll to the bottom and hit save changes. Okay, then once you've done that then I'd like you to create a page. The first page you can create I want you to create with a title of submission form. And the reason I want to do that is because then it will create a slug called submission hyphen form we're going to use this mission for today. Inside of that form I want you to add the shortcode. So I'm using the block editor which means I need to add the shortcode block if you're using the classic editor you can just add the shortcode to the content either way works. So let's add the shortcode block so I add the shortcode block here, and then I pop the shortcode with the square brackets so it's WP underscore learn underscore form underscore shortcode, and I paste that in there. Then I'm going to publish this page or in my case I'm updating it. And if you then view that page, you should see something that looks like this. It's a very simple form. I've got no cool styling so it's just all jammed up on top of each other it looks horrible. It's a name and it's requiring a name and an email address requiring an email address, and there's a submit button so that's what you should be seeing. I'm going to run through the pages very quickly. And then you can stop if there are any problems as we go through the second page I'd like you to create is the success page. And I would like you to create it with a title form success page, specifically so that it sets up a permalink slug of form hyphen success hyphen page. And the reason for that is because it then matches the slug that we've set in the plugin. If your page slug is something else you can either update the slug in the plugin, or you can update the slug yourself manually. But otherwise it's not going to work so either create it with just form success pages the slug, or update it yourself in either the plugin code or in the way. And then just give it a message mind just says thank you for your submission or the form was submitted successfully whatever the case maybe. This is a very simple way of creating success in error pages there's there's better ways of doing this, but I'm just keeping it very simple today. Okay, so if I click on that page I can actually go straight to that page. It'll show me the message. I typically wouldn't link that anywhere to find it but it's there just for the purposes today. Then the third and last page I'd like you to create is the form error page. And for the same reasons as we did for the form success page, please call this page form error page. The slug is form hyphen error hyphen page, which as I mentioned same with the success matches the slug we specified in the plugin. And that can have some kind of error message if you like, something went wrong whatever you would like to do, create and publish that page. Once all those pages are created what you should see when you own the other thing I forgot to mention make sure you have the plugin activated installed and activated so it's there it's ready to go. I have it activated on my site. Once that's all working what should happen is if you click on the submission form and you type in a name so I'm going to put my name here. Jonathan passenger and I'm going to put down my email address because I don't mind my my personal email address being public because Gmail is very good with spam filters. And I had submit it's going to take that information it's going to save it into the database and it's going to give me the form success page response. The way that I can check whether it's in the database is as I mentioned earlier the plugin is also creating a dashboard menu which I first scroll right to the bottom here below settings on my site. There is the WP learn admin page link. If I click on that it takes me to a very simple page, which has a couple of entries on my side already, but I've got my John Doe my Jando and my Jonathan passenger at gmouth.com. Okay. Is anybody who's doing all of this following along as anybody struggling with this I'm going to take a break if anybody struggling with getting their things working. And then I'm going to show you what the delete functionality does running out of coffee but I shouldn't have anymore so. Okay, so Ryan's got his working that's excellent. I kept it there's there's many better ways to handle messages you know you could display it in line on the form so folks can do it again. I'm just keeping it very simple because I want to do very simple directions the focus today is on plug in security not on, you know, building forms. Okay, what I'd like to just quickly show you guys I'm going to hit F 12 on my keyboard to open up my dev tools my browser dev tools. Depending on your browser it will be a different set of keyboard combinations in Chrome. You should also be able to click on the three, either dots or lines or the hamburger menu or whatever it is and go down to I think it's more tools and then developer tools. Or use any one of the keyboard combinations. And then if we click on over to the elements area. And in this area you can do a control command F and you can search for WP learn. Now you're going to look at it look for it in the code because I cannot remember. I'm looking for this object WP learn Ajax there it is. Okay let's make this code a little bit bigger so we can see it. So this is the, the, the JavaScript object that we create by using the WP localized script function. And you'll see that it has this Ajax URL variable, or at least item there it is Ajax URL, and it passes in the path to my local site, WP admin admin Ajax PHP so that's the file in WordPress that accepts Ajax requests. So that's what is that is what's happening when we use this WP localized script business over here. So that's great so we know that that's working. And what's going to happen when I as I mentioned before when I click on delete here. Let's switch back to the code. This code will run, it'll get the ID value and it's going to do some console logging so we can see when the delete button is clicked, we can see what the delete submission ID is, and then we can see the response. So let's go back to the console. So they're plugging security admin JS is loaded. When we hit delete. There it is delete button is clicked the delete submission ID is five. And then it has a response array and then it says form submission deleted. And it refreshes the page. Okay, so that's one way that it's doing deletions. So if you have a look at this code you'll see what it's doing is it logs the response. It alerts and says form the mission deleted and then it does a document location reload so it refreshes the page. Okay. So that works so that's great plugin works we can ship at 100% 100% happy. Okay, the problem is this plugin has issues. So, let's go and switch back to our documentation. Before we continue any questions around the functionality is everybody got their functionality working are they able to add and delete and everything's working fine. If anybody's plugins are not working please let me know but it should all be good if your page is working. Okay. So plugin security is so important that the WordPress developer plugin handbook has an entire section on plugin security. And that section you'll see if you go to developer dot WordPress or plugins slash plugins slash security has actually been redirected to a special security page in the common API is handbook. So when I first started developing plugins. It was actually all in the old WordPress codex. And that that the stuff from the WordPress codex was moved over to the developer handbook. And I used to spend. So funny enough my timing of entering the WordPress community was when that move was happening. So I used to spend a lot of time jumping between the two because things are still being changed and things are still being updated. But then the plugin security section in the handbook became sort of my go to when I was checking on these things because I can never remember where everything was. I haven't developed plugins for a while but I see that they've moved it now to a separate security page. And that's over here. And what's really nice about the security page is it includes some further information about why plugin security is important. And it also talks about developing a security mindset which I think is really useful and really important. And I want to read through that section with you today just so that we understand the things that we need to think of. So it is important to consider security as you add functionality, use the following principles as you progress through your development efforts. Number one, don't trust any data. Okay. For those of you who might remember a movie called I think it's Glenn Gary again Ross there's a there's a ABC always always be closing line from that movie it's got to do with selling. I have, I have adopted that ABC for my development purposes and I have changed to always be checking. So I am always checking everything all the time. Don't trust anything don't trust user input don't trust your users to put the right thing in the field. Don't pressure trust your third party API to send you the right data. Don't trust your data in your database that you might have put there. So don't trust anything and that's the first thing that should always always be thinking about when you're working. Always make sure to validate and sanitize user input and to escape output. And there's a concept of and we'll talk about in a second escape as late as possible and sanitize as early as possible. Okay, then it says rely on the WordPress API and so many core WordPress functions provide the built in functionality of validating and sanitizing data which we're going to learn about today. So don't use, you know, your own code rely on what WordPress is doing many security experts have spent numerous hours updating these core functions to make this sure they're secure. So why not rely on them. And then keep your code up to date so as technology evolves so does the potential for new security also stay vigilant as I said earlier, keep an eye on security platforms keep an eye on security sites. Follow that Twitter account so that you know what's going on in the world of security. And then talks about guiding principles. So again, never trust user input, escape as late as possible which we'll talk about in a second, escape anything from untrusted sources so databases and users third parties. Never assume anything never assume it overstate isn't fine it's in my database look it'll be fine. And then it says Senate sanitation is okay, but validation rejection is even better so we'll talk about that in a second as we go through through the plugin. Okay. So let's start with the first page in the section called sanitizing data and sanitizing data is basically taking any information that we're putting into our code and making sure that it's clean and safe and secure. So what I would like to do now is if anybody feels like answering this question if we have a think about or have a look at the plugin code. What are the first obvious places where we could be sanitizing data as it's as it's flowing through that plugin functionality. You're welcome to unmute and answer if you would like you're welcome to paste your answers in the chat. Okay, so she bomb has got the jump on the gun name and email input 100%. So that is the very first thing we should be sanitizing is the name and email inputs. Okay. So let's have a look at that anyway also anybody can think of while they're looking through the code. So there aren't really many more fields than the name and input. And the reason that that is the most obvious one is let's have a look at what that code is doing when we get the name and email. So first of all here, we're just getting it from the poster. Now the poster is created when a full HTML form is posted to PHP. And all PHP does is get the form fields and put them into an array. That's it. It doesn't do anything else. It doesn't check if they're valid. It doesn't check if they're safe. It doesn't check if anybody's adding anything that they shouldn't. So what I want to do is an example. And this probably will work but I'm not sure because I haven't tested it. But if I for example do john, and then I pass in a script tag, and I do alerts. Hello. So this is basically just JavaScript code that I'm popping in here, and I do a script here. I put in just a regular old email address. And let's see what happens to that. Okay, so that's a fine happy days. Let's refresh our admin page. And look at that, we have some JavaScript code running because we inserted into the database. Okay, now that's obviously a very simple example but that is how easily it happens. If it is not being sanitized people can inject JavaScript code which could then include something which calls an API and does a whole bunch of things. And it's really that simple. Now, what you should also do is you should also before the data gets stored in the database do some kind of sanitization and escaping. But if you sanitize the name and the email early, then you don't have to worry about them. So let's, let's look at. And funny enough, when I, when I used to do plug in vetting when I worked for a company called codable. That's one of the first things we used to test we should just test, can I insert script hello. And you'll be surprised how many so called WordPress experts allow that to happen. So it still happens in the business. Okay. So for example, they say say we have an input field name title, we could use validation, we can't use validation here because the text field is too general, we'll talk about validation in a second. So it could be anything so we sanitize input data with the sanitize text field function. Okay, the sanitization functions all listed below here. So you can use all kinds of functions in WordPress you can sanitize emails file name hex colors HTML classes, pretty much anything that you can think about that might get posted you there's a sanitize function for us. So we're going to use the sanitize text field function for our name. So I'm going to copy that code out over there. I'm going to switch back to my plugin and all I need to do is wrap this post name array with sanitize text field. Okay. And what I am going to need to do before I do that is just delete the bad one with the, with the dodgy JavaScript. So that we make sure that doesn't happen anymore so they have just deleted that one. So in my plugin code I've got I'm sanitizing the name now. And what that does is that runs it through a series of functions that cleans out any scripts, any dodgy things, and it should allow us to simply post that. So let's see if that's what works. So let's pop back on over here. And let me find my sorry. I decided to make my life difficult. Go away zoom. Sorry folks. Let's go back to the form. Here we go. Okay, so now let's go back to submission form and let's do that same test again. So let's do a poll. And let's try and insert a script. We'll make it high this time. And then we'll close the script. And then we'll give it an email address. Just try that. Actually, I was Paul. So let's make it Paul. And we submit that. But now if we refresh, we shouldn't see any kind of errors. Excellent. So Paul was sanitized. It's all nice and clean. We can keep that data. Okay. So the other one is the email as Shabams mentioned. And in the list over here, you will see that there's also a sanitize email field so we can use that specifically for emails. So if we go back into our code. We can sanitize the email. That's great. There we go. Email has been sanitized. So now if we try to do any kind of JavaScript or any funny funny things in there, that'll also clean that up. Okay. So that's step one done. So we've sanitized our fields. Now, as mentioned, if we go back here. It's not listed here, but there used to be a sort of a saying somewhere I remember reading about sanitize early and escape late. So what that means is as soon as I capture this data, I want to be sanitizing. I could do this in two ways. I could either leave it the way I've got it here where I'm sanitizing the post name, or I could do something like this and I could just go name. Equals and just say post name. And then I could do it again, and then sanitize. We get that size text field. And I could do something like this. Now this seems a little bit redundant. But sometimes you might need to do this because you've got other variables happening or floating around or whatever the case may be so there's multiple ways that you can do this. But it's super, super important to make sure you do it as early as possible. I shouldn't be sanitizing here at this point. I should be sanitizing as soon as I'm capturing that data from the post or the get or the request, whatever type of request it is. Okay. So that's sanitizing data. Any questions around sanitizing data? Cool. There don't seem to be any questions. The great thing about the security page here in the Common API Sandbook as with all the rest we can look at today is there's lots of nice examples. So all the functions. So I recommend reading through these and then going and reading up about those functions and what they do and understanding how they work. It would be nice if these linked through to the function itself. But that doesn't seem to be the case. Okay. The next thing to look at is validating data. Now what do we mean by validating data so validating input is the process of testing the data against a predefined pattern or patterns. Okay. So for example, let's say you are receiving a social security number or ID number or something like that. Those numbers in South Africa it's an ID number in the States I think it's called a social security number. Those numbers follow or a credit card number is another good example. You can really follow a specific format and there are functions that you can either write or find on the internet that will validate those fields against those types of formats. And simple validations include checking that the fields have not been left blank. For example, checking that a phone number only contains numbers and punctuation checking that a requested string is one of five options so let's say you have a dropdown and the options are 12345 make sure that six hasn't been posted because then you don't know what's going to happen or checking that a quantity field or ID field or integer field is actually a valid integer. And here it speaks about also the data validation should be performed as early as possible so just as we should sanitize our data as early as possible, we should perform validation as early as possible. Now, in the code that we're working with there is one field that exists I think in two places that we should validate to make sure it's correct. I think of where that might be. Give me a shot in the chat or via via audio if you would like to. Elizabeth says maybe email address, not a bad idea. The nice thing about sanitize email is it will do sort of data validation as well. It'll check that it is a valid format I can't check that it's an actual valid email address, but it'll check that it's in a valid format. But there's another field in there that we could do some additional validation to we don't need to sanitize it, but we need to let me give you a clue we need to validate that it is of a specific type. So have a look through the code think about the actions that have been performed. We've done the form submission what are what are the actions are happening in the code that we could that we could consider validating against. It's not a trick question that don't feel bad if you don't get it. We're going to go down have a look. Okay, so the answer is something on the admin page. Yes, something on the admin page you are correct. The answer is this ID. Okay. So this ID is the ID that is posted to the Ajax request to the function that deletes the record. And if I was able to take the code here so the reason this is one of the reasons why I left. The queries and the custom tables the way they were. Because let's say I was to pass and those of you I'm going to share a cartoon with you in a second so you enjoyed some humor about this one. But let's say the ID field ended up being something like this. I'm going to hard go there. That's great copilot it for me. Let's say the ID ended up being ID one semi colon drop table WP for submission semi colon. Okay, and I then pass the value of that into this query. That would mean that my query would end up being and I'm going to put it in quotes here. I'm sorry not in quotes in comments, delete from WP for submissions where ID equals whatever the value is, and then drop the table. So it's going to completely destroy my database table completely. Okay, now I have to share this with you because I've seen this many, many times over the years. I should have actually added it to my slides, but it's a little cartoon by by cartoonist called XK CD, and it's called Bobby tables. I'll read it out to you for those who might not be able to see it but it's a lady on the phone. And she's receiving a phone call from her son school and says hi this is your son school, we're having some computer trouble and the mom says oh dear did he break something. And the school says in a way, did you really name your son Robert quotes brackets semi colon drop table students semi colon dash dash, and the mom says oh yes little Bobby tables we call him. And then the school says yes well we've lost this year's student records I hope you're happy. And then the mom says and I hope you've learned to sanitize your database inputs. Okay. So whenever this kind of conversation comes up I always reference this comic. I'm going to copy this into the chat so that everybody can have a laugh. But basically whenever you are receiving data that you're going to run against a database query, you need to validate that data. So the simple way that I can validate this ID now what am I expecting it to be I'm expecting it to be a value an integer value one to whatever x in PHP not specifically WordPress but in PHP what I can do is I can just cast it as an integer by doing this, just open close brackets and the word in the short word int. And what that will then do is if somebody passes anything else but an integer. It'll, it'll strip that out and it'll actually make it zero and then it'll, it'll, it won't work. So then what'll happen is my query will end up being something like that, which there will be no table record zero so the query won't run. And it'll just return the result and it won't affect my data. What's interesting is what this doesn't do is this doesn't prevent somebody from passing in a higher ID by by intercepting the request. So we'll talk about how we fix that in a second but for now this is enough just to validate that the ID that we're expecting is still going to be an integer value. And therefore when we pass it to the table query it'll remain an integer, and it'll delete a valid record with that ID. Okay. So that was data validation. Any questions on data validation before we move on there are some good examples in the validating data documentation they talk about safe lists. So only accept dating from a finite and known list of trusted values that's another good way of doing things. So a good example of that will be to say let's say you've got some options on a form. You would actually create an array of options and say if the past values in that options array, then do something if it's not don't do anything with it. Let's take some good some good examples down here. They talk about block lists format detections and great examples of how you could do things here but we just did a very simple example today. Okay. There don't seem to be any questions so let's move on to escaping data. All right. Now, you might think to yourself, okay. So let's talk about escaping data first escaping data is the process of securing any output by stripping out on one to date. In this case, they talk about malformed HTML or scripting. But now you might be thinking to myself, okay, but if I'm building this plugin, and I'm making sure that I am sanitizing my inputs and validating my data. Why do I have to worry about escaping data. And so the answer to that is quite simple. We're using WordPress, which powers 40 odd or whatever percentage of the internet. As a plugin developer, I don't know what other plugins are installed on my client site. I don't know what themes are installed. I don't know what custom code has been installed. So I don't know what other functionality could inject data anyway. So I want to make sure that even though my plugin code is validated and sanitized if I output anything to the screen that is also safe, because I have no control over other things that happen. And so escaping functions are basically similar to sanitization functions. But instead of running them on inputs, you run them on anything you're going to output. Okay. So while we're talking about escaping functions, are there any examples you can think of in our code where we could do some escaping? Anybody got any ideas? Not a problem if you don't, I will go through them with you. Okay, so one says while showing the data on the admin page, and that is the correct answer. Okay, so let's have a look at that function. So the WP learn render admin page function. It's on line 138. That's the function that outputs the admin page. You'll see it gets the form submissions into an array. And then it loops through the array and outputs the name, the email, and the ID. And we should escape those outputs so that nothing that might get injected even though we know we're storing the data safely. And this might come along at some point in time and affect that data. So we want to make sure we escape those outputs. So you'll see in the documentation, the very first function they talk about is the escape HTML. And that's used anytime an HTML element encloses a section of data being displayed, and this will remove HTML. Then there's escape JS escape URL, just like there are multiple sanitization ones, there are multiple escaping options. You can escape raw URL, you can escape an attribute. So if you're doing a class name or a class ID. But the most common one is to use escape HTML. You'll see here it's being used in the title. So we can do exactly this on those two fields. So here we can say echo escape HTML, and we can escape that. There's my control C. Here we go. And then do exactly the same on the email. C HTML. And close that there. And there we go. I didn't spell that correctly. Okay. And all that's going to do is it's going to run the name and the email gathered from the database as it's looping through the submissions and just check. Is there any malicious content in here? If so, strip it out. Okay. Adrian says so escaping is like closing or quitting something, not quite. So escaping is like escaping actually comes from. Well, I don't know where it originally comes from, but the first time I learned about it was when you when you input values into a database that have a an apostrophe. Not an apostrophe sorry, a single quote, and a single quote in a string in the database causes havoc. So you escape that single quote by using a slash and basically then picks it up as like a string. It's basically a way of taking anything that is that is malformed or shouldn't be there and basically stripping it out. Just basically another way you could you could think about it is cleaning up the data. So just as we clean it when we sanitize sanitizing sounds more like cleaning up you know I sanitize I cleaned it up. Escaping is another way of cleaning it up but it's before you output it. I think they just use the term escaping which comes from a different way of doing things. So what what they used to recommend so this is in the very early days of the web before we had CMS and all this kind of stuff. You would you would save the data. But then I think I think when it was saved with the single quote was fine but when you used to have to try and output that data to skip problems. So you would escape it by using the slash and then it would render fine in the browser. So that's where that functionality in that terminology country. Okay, so you're always always sanitizing your inputs making sure they're clean. And then escaping them also making sure they're clean before you render them to the browser because the browser can't handle anything that shouldn't be there. Okay, and then the other thing that I want to escape is the ID just in case just in case it's not a it's not a value. And so for that one I can't remember actually what I used for this. So I'm actually going to cheat horrendously here and going to have a look at my code. There we go. I just did the same integer casting here. So this is this is using the same casting. But instead of instead of using for validation I'm using it for escaping so I'm making sure that the ID is an ID. And it's output as an ID to the browser and therefore it'll show as a number in that field. Okay, so we've done sanitization. We've done validation. We've done escaping. Let's make sure this all still works though, because we've made a whole bunch of changes we've tested nothing. So let's go back to our site. And let's go back to our submission page. And let's check that everything still works. So let's say John Deere, because I'm terrible with thinking up names. I've got Deere at gmail.com. And we'll submit that. Okay, that seems to work. That's excellent. And if we refresh here, there it is John Deere. So that's all saved correctly. And if we inspect this one, and we look at the disease, it's got the set the correct ID. So that's a week. So now we're sanitizing our data, which is great. We're sanitizing our input, should I say. We're escaping our outputs. And we are validating any IDs that are being passed around. So if we hit delete here, that should still work. That's great. And we're happy days. Okay, so those are the top three. Cool, any questions on all of that before we move on to the next one. And I highly recommend if you can after this workshop or when you get a chance to read through all of these pages in the security section. There's way too much to go into in one workshop. So I recommend reading through. Okay, so Adrian says, could you go back to your code around 152. We can do that. There we go. Happy days. Cool. Okay, I'm going to grab some coffee before we move on. Okay. Now, so far we've been focusing on pure post requests. So in other words, a standard form request data is submitted to the server PHP picks it up does something with it in the code. So it's the database. When we view the admin page, it connects to the database gets the data outputs the screen. But what happens when you start passing data around in JavaScript land. Okay. So one of the common ways that that is done is using an Ajax request. Another way that that can be done is using the rest API. I'm focusing on Ajax request stage because they're a little bit simpler than managing the rest API, setting up things in the rest API requires a little bit more information, a little bit more detail. So I'm focusing on Ajax request today. So the let me go here to do. They are. We've got the form submission working. And that's great. And it's working on the page. But what happened, let's say, if some other website were to try and submit to this form. First question is, is that possible? And second question is, how do we prevent that? And the answer to the first question is, yes, it is possible. Okay, it is possible for another URL somewhere to submit a post request. In different in the way different browsers work and there's a thing called cross origin policy and all these kind of things that limits that functionality. But it is still possible for something to make an standard HTTP post request to a form and try and submit data to it. And this is one of the most common ways that sites are hacked or made vulnerable. It's called a cross site request forgery, where it might pick up whatever the form fields are and submit something to it, and then start, you know, DDoS in your site or whatever the case may be. So you want to make sure that any requests that are made to this form to the script that process the form or your site at least that your site knows that the request is made from itself. So if you're working in Larival land, there's a there's a CSRF token that you set up in the form. If you're working in WordPress land there's a thing called a nonce. So the next page in the security documentation is all about nonces. So if you want to click on that in the security section so there's there's sanitizing, validating, escaping and then nonces. So if you are from the UK, or anywhere UK adjacent or anything that knows when I first heard the word nonce I took a little bit of a second take, because to me a nonce is generally like a stupid the silly person. It's a nickname for for something that there's other uses for it. But in software terms and nonces and number used once. So basically it's a number that's generated, and it's attached to the form. If the form is posted, you can run a check on that nonce. And you can say, is this the nonce that I expect for this form. If so, then let this happen. You can do the same on Ajax requests, you can say set up the nonce. You can set up the nonce you can also pass it around in your Ajax request, and then you can check so it's basically aware of the code saying, did this request come from a trusted source in other words me, myself as the website. Did I make this request, or has it come from somewhere externally. So the two ways that there are two places that we can do it to kind of given it away already. The one is on the form. So here on the form itself, we can implement a nonce and then check it when we post the form. And the other way we can do is we can set up a nonce. Let me scroll back up over here for the Ajax object, we can create a nonce there. And then when we receive that Ajax object we can pass the nonce to that as well. Okay, so let's start with the form itself. I will mention information here about how nonces work. But the first one I want to show you here is how you added nonce to a form. So if we scroll down there's a section called adding a nonce to a form. To add a nonce to a form you call the WP nonce field, and you specify a string representing the action. By default WP nonce field generates two hidden fields one whose value is the nonce itself, and one whose value is the current URL in other words the refer in other words, where I'm receiving the data from. So for example, this is what the code might look like WP nonce field and then you give it a string. So I'm going to copy this code out. And here in my form. Just below the form tag, just above the hidden input. I'm going to open up PHP tags. And I'm going to use WP nonce field. And then for the purposes of this code, I'm going to use various it here. Again, I'm cheating out registry because I can't remember these things. Yeah. So we're going to call WP learn nonce action and nonce field. So let me show you what that looks like. Yeah, so it's WP learn form nonce action and WP learn form nonce field. That's all I'm going to do passing in those two variables. Let me show you what that does to the form. You can't physically see anything. But if we inspect this code, you'll see there are some additional hidden fields that have been added to the form. Let me move that out the way. There is the WP learn form nonce field. It has a name of WP learn form nonce field and then it has specific value. So that that value is the nonce value. And then it has the WP HTTP referrer with the value of the current submission form. Okay. So that deletes the input information from the database. No, this is for creating the form. We'll talk about the deleting in a second Linda. So bear with us when we get this is just for creating the form. Okay. So now when we post this form, these additional hidden fields are going to be passed. So if we get to, and I want to show you what happens if I refresh this page, I'm going to add, let's say, Bob Mali. And I'm going to go Bob at gmail.com. And I'm going to submit this. And it gives me a success. But if I go into. Oh, yeah, sorry. So that all works. Because I'm not doing any kind of checks. I'm not saying does this come from a place that I'm happy that it comes from. I haven't done that yet. So to do that, I now need to verify the nonce. So if we scroll down a little bit, we use this check admin referrer function. Okay. So the function checks the nonce and the referrer and if the check fails, it takes a normal action terminating the script. If you did not use the default name when you created the nonce, you specify the field name. So we did that. We specified the action and the field. So we're going to copy this out. And if we go back here, we're going to go into the process for. And the first thing we're going to do once we've checked if this form is being posted before we do the sanitization, we're going to check that it's coming from the same place. And to do that, I'm going to check my code here again. That's what it looks like. I'll talk you through what I'm doing here. Okay. So the first thing I'm doing is I'm checking, has that nonce field been set? Is it being posted? Yeah. So if it's not being posted, or it's not being verified, nonce field. And then, sorry, I can't, you guys can't see because this is a bit small. Make it a bit smaller. Hopefully you can read that. So first is saying, if it's not set, or it's not verified using the nonce field and the non-section, then you redirect to the error. Okay. So now how do we test that? Well, we can test that by taking the nonce field out. So there we go. So now the nonce field is not going to be set up, but it's going to be calling this functionality. So let's test that out. Let's go back to the submission page. So now let's try and add a new person. Now I'm running out of names. I'm just going to make it simple, jnxgmail.com. And if we have a look at this, we'll see there's no nonce fields there. And we submit, and it gives us an error. So now if anybody else tries to submit without the valid nonce field and the valid value for that field, it's going to throw an error. It's not going to try and insert that data. Okay. Now you could just do the verify nonce stuff. The code that I was referring to here was talking about the checking admin referrer. You can also do the verifying the nonce the way I've done it. There's multiple ways to do it. I prefer using verify nonce. Just because that's me. But you can also use check admin referrer in the same way. Okay. Anybody have questions around how this is working, what it's doing, what it should or shouldn't be doing before we move on. Okay. We don't seem to have any questions there. So now the other thing that we need to look at is, well, that's great for the form. So the form is now as secure as we can make it. We're making sure that it's being submitted from the right page by passing the nonce field. We're making sure that the inputs are being validated. We're making sure that the outputs are being escaped. The last thing we need to think about is this Ajax request. Now here's another good example. I could again make a request to this Ajax request to the URL and pass in whatever I want to. And because there's no checking, it's going to give me some errors. So the two things that we need to do. First of all, we need to implement the checking. And that's where we can use the check admin refer. In this case, it's called check Ajax refer. Okay. So we use that in the code and you'll see I'm doing this pretty early. So I'm doing it even before I check the ID. I'm saying check the Ajax refer using the WP learn Ajax nonce. But now I need to set this nonce up. So where would I do that? When I am setting up my learn Ajax object. So over here, I create a nonce value. And then the function in the code, just find it quickly. You can do it in the same create nonce way. So let me show you what I'm doing there. Find it quickly. Here we go. There's create nonce there. So there I'm creating an Ajax nonce using WP create nonce. And then passing in WP learn Ajax nonce. And then I want to pass this nonce to the nonce value here. I hate saying the word nonce so many times. It ends up becoming rather annoying. Can you make, yeah, I can make it bigger. I can make it smaller because it's difficult to see the whole line of code. Is that better? Okay. Awesome. I'm using visual code studio in case you're wondering. Okay. So for the Ajax request, we have the Ajax nonce, which we've created using WP create nonce. And then we just pass in the nonce field name, if you will. And then further down in the code, we're checking against it. We're checking against that WP learn Ajax nonce. Okay. So while we're here, this is something that I've often struggled with struggled remembering. And the reason I want to show you the two different ways to show you how the two different things works. So when you're using, when you're sitting it up for Ajax, let's focus on that for a second. When you're sitting it up for Ajax, it's a case of using WP create nonce and passing in the field name. And then passing that in your Ajax object. Then you use the same field name in your function. And you do check Ajax refer. Okay. The other thing you're going to need to do is you're going to need to get this nonce and pass it in the Ajax request itself because when we do it now, you're going to see it's going to fail. So let me show you what I'm talking about. With that code implemented. If I go back to my admin page. And I refresh this. And I try and delete, let's say Bob over here. You'll see nothing happens. If you come my console, it says 403 forbidden. I don't have permission to make this request. And that's because the nonce is not being passed around. So in the admin code, in the admin.js code, we need to pass the nonce in the object of data that is being passed to the URL. Okay. And in the code. There used to be, I don't know if they have a cheer. I don't think they have it on this functionality here, but there used to be a way that you could. Let me just find the check Ajax refer. Function. Might be in there. There used to be some examples. So you'll see here, it's check Ajax refers prefer the field. And then you'll see it's passing in security. And over here, it's passing in the security in the data. So that's one way you can do it. You can specify your own one. The other way you can do it is the way that I've done it over here. So if we have a look at this admin code, let me find it for you. The default value is underscore Ajax underscore nonce. And then you're passing the nonce to that. So there's two ways that you can do it. I'm going to use this way just to, just to make sure that I'm happy with it. So let's go back over here. And in this data object, I'm going to pass in underscore Ajax underscore nonce. And then I want to pass in the WP learn Ajax nonce, which is the same value that I've passed in here. And now when I refresh and run this, it should work. So let's test that. Now I'm going to remember to do a hard refresh because my JavaScript has changed. There we go. And if I now try and delete this, my form submission is deleted. So now I know my deletion action is protected. So nobody can come from anywhere else and try and make a request to that, that Ajax endpoint and try and delete my code. Okay. So nonces are something that I have struggled with a lot over time. I struggled to remember the function calls. I struggled to remember the formats that they work in. I struggled to remember which one to use forms and which one to use for Ajax requests and which one to use everywhere else. The one advantage of using, for example, the REST API is the REST API is really authenticated for any kind of admin users. So you don't have to pass nonces around. So I do recommend reading through the nonce documentation, making sure you understand it. And then using these simple examples that we've looked at today and looking at how the code works and how it passes through. I would also suggest that if anybody has any questions around this, if they're working with a development code, feel free to reach out on this plugin repository, open up an issue and say, I can't get the nonces working. And I'll help you work through that because it's something that I've always, always struggled with because of how the files work and how the functions work in the naming conventions. Okay. So that is nonces, basically making sure that the requests from the current sites are verified, coming from the current referrer, and we know that they are safe. Any questions around that before we move on to the last one for today? Cool. I will do that right now. So the Git repository is just github.com, Jonathan Bussinger, WP Learn Security plugin, or WP Plugin Security. It is also linked at the bottom in the resources page of my, actually of my slides. So you can find the link there. And then feel free to shout if you've done understanding. I'll share a fun story. I struggled with these nonces so much, the last time I presented this, I actually forgot how to do it in the workshop and I had to go back and read the docs. It's just something that I just, I can't get it to stick in my brain. So I always have this code around to remind me how it all works. Okay. And then the last thing we want to cover today is user roles and capabilities. So it says right at the top of this page, if your plugin allows users to submit data, be it in the admin side or the public side, make sure that it's checking for user capabilities. Now, our submission form, we're not too worried about because in our use case, we want to just gather data from people on the public space of our website. However, the deletion form, I don't want another user to stumble across the URL and make a request to it. And there have been a couple of plugin vulnerabilities that have been released lately. They call it a privilege escalation vulnerability. So let's say you are a author user on a WordPress site. If the function that deletes the record for this plugin doesn't check that you are an admin user or not, you might still be able to trigger the code. You might not. If you have a look at the code, when I set up the admin page, the permission value that I set in the third parameter specifically manage options. So it won't add the submenu for author users. But if author user happens to stumble across the URL to post that data, maybe someone else they know is an admin and they're looking at the code or whatever the case may be, they could create a request using tools. And I've seen this in action. I've actually used the tools that PEN testers use. And I've seen this in action. They could create a request or interact with a request and submit the ID to that endpoint and then delete records without your knowledge. So whenever you're doing things that are submitting data or changing data or making changes to the database, always make sure that you're doing a user capability check. And the code for that is basically just a case of doing something called, where is it now? I'm trying to find a good example for it. Here we go. It's using the current user can function. So current user can basically checks whether this user has the permission to do certain things and if they do then allow them to run this code. So let me show you what current user can looks like. In fact, it might be easier if we go to user roles and capabilities. Current user can. Here we go. So current user can is a wrapper function for user can. Using the current user object is used parameter. You basically just call current user can and then pass it in the capability. The capabilities are listed some way. I can't remember them now. But they are listed possibly in the stock. No, they're not here. But here's an example. For example. So can the current user edit posts as an option or can the gives the code because reference should have all the capabilities. So let's have a look at that. Yeah, here they go. Here are all the capabilities. So can they switch themes, edit themes. And there's a nice table here. If we scroll up a bit that has the capability and then which admins and users can use it. So let me share this in the chat. So it's very handy to have that handy, very useful to have that handy. And so for example, an administrator can upload plugins and administrator can edit files, edit plugins. Or in the case of our learn submenu, we've used the manage options capability. So now we need to do the same thing for the deletion. So we under this check age, check age extra for now we can do the current user can. So we can do something like this. We can say if not current user can. And then we can say manage options. And we say if that's not the case, then we would want to return some kind of error result or whatever so that it doesn't. It doesn't allow this person to. I can't remember how my. Format singing visual coast lyrics. And I think it was, I think. Who was it? Adrian said it was right click reformat. So we can today. Anyway, so basically what I'm doing is I'm checking can the current user do the set have the same capabilities as the person viewing the submenu. If they can't then return an error. If they can then carry on with the code. And so whenever you're working with any way that's working with data submitting data, retrieving data, removing data, you should always make sure that your user it's logged in has the capabilities to do that. And you can use the WordPress current user can function to do that. And then passing the capability and you're good to go. Okay. The reason we're returning a JSON array here is because it's using Ajax. So we need to return it to the result. Okay. So those are the five things that you should be thinking about when. Working with plug-in security. It's a thing that. You do need to be aware of so that you're thinking about it. It can become very easy to just write code and get functionality going and you see the functionality working and there's no bugs and you're happy that it's good. But these little vulnerabilities could slip in. So as a wrap up, remember to sanitize early. Remember to validate just as early. Remember to escape late and escape any output late. Remember to use your nonces. If you're moving data back and forth submitting forms, Ajax request whatever the case would be. And then remember to check your user roles and capabilities. The other nice thing about this page is there are some common common vulnerabilities. And you'll see there's Bobby tables. So this is a little bit of a web-based documentation. So it talks about SQL injection. It talks about cross-site scripting and how you can prevent that. And it's all of these things we've spoken about. There's cross-site request for jury. Using nonces. And then there's some links at the bottom for how to stay current. So there's a word for security white paper. The word for security release. And then the open web application security project or the OWASP, it's quite an in-depth document, but I do recommend reading it and understanding how these things work. And the more that you can read and understand these concepts, the more you'll start thinking about them when you're writing code. And then going back to what I said earlier, always be checking. Trust no one. Trust nothing. Don't trust any user data. Don't trust your own data. Any data that you're working with, make sure that you don't trust it. Okay. That's my bit for today. Does anybody have any questions around anything? I'm sorry that I was a little bit flustered today. As I say, I'm a bit tired. So I do apologize if anything wasn't clear, but if anybody has any questions, they're welcome to ask now. Okay. So that's my bit for today. Thank you all for joining me. As I've mentioned in previous sessions, if you do come across this and you're watching this on WordPress TV later and you do have questions, you can find me at the moment still on Twitter. As Twitter is slowly going through some changes. The recording will be posted to WordPress TV tomorrow. Hopefully during the course of the day tomorrow, if not tomorrow, at least by Monday. If you have questions, you can find me on Twitter. You're welcome to also send me messages in the Make WordPress Slack. It's a Slack channel specifically for first contributions to WordPress. And you're always welcome to ask me questions there. And then the other thing that I mentioned is, is last year is this year I'm planning on doing some sort of ask me anything sessions. And I've already got one great question that Michelle Moore, I think her name is sent me. And I'm going to be looking at that soon. So if there's anything that you're struggling with, if it's nonsense that you're struggling with, and you want me to go through that again in a session, you're welcome to give me a shot. But please do let me know. And if you have any ideas for any workshops that you want to see, any topics in the WordPress development space that don't quite make sense to you or you struggle with, please do send them my way. You have my email address. It is just my name, Jonathan Bostinger at gmail.com. You're welcome to send me any suggestions or ideas. I don't mind giving folks my email address. I can't guarantee I'll answer it straight away, but I'm always open to new ideas for workshops and tutorials. I'm probably going to do a tutorial video on this topic. That's a bit more sort of structured and down to the point. So that you can review that as well at a later stage. Once again, happy new year to everybody. It's lovely to see you all again. And enjoy the rest of your Thursday and the rest of your week. Thank you. Cool. How does this work?