How to Load Content on Demand in WordPress Admin

If you have a WordPress admin page that takes too long to open due to a list of some type or any other content that takes longer to load than you’d like, this solution might be for you.

In my case, I had an admin page for a plugin I’ve been working on a lot lately that has a list of members from another webpage that takes a while to load. What I did was separated that content that fetches the list into another file and placed a link on the page that lets you load the content after the rest of the page has loaded and only if you need to because a lot of the time people aren’t going to need or even want to see that content. Other times they will want to load it and they can now and it won’t take forever to load because the rest of the page already loaded and you only have to wait for that specific content to load, thus making the site much more pleasant to work with…

In case anyone is interested that plugin is named Follow Hook, and it adds CRM features to any WordPress site and we just listed it on wordpress.org! Just search for “Follow Hook” and it will come right up.

How To Load Content On-Demand

  1. First, as I mentioned above, you separate the code for just the feature you want to load separately. In my case it was a list of MailChimp subscribers, but it could be any content really. In my case I named my file MC_list_members.php, but you can name yours whatever seems appropriate, just make sure you use the same name below in step 3 where you see the filename in green.
  2. Then you put a link on the admin page you had originally that looks like this: <p><a href=”<?php echo $puri; ?>” target=”hifrm”>View MailChimp Subscribers</a></p>
  3. As you see I have a variable named $puri in the code above and that is going to need to be defined somewhere above where you placed the link. It is the URL of the page that you put your separate code in for the feature you wish to load on demand. In my case I am working on a plugin named Follow Hook so I would use something like this:  <?php $puri = plugins_url(‘MC_list_members.php’, __FILE__); ?>
  4. Then finally, you add your iFrame HTML like this: <iframe id=”hifrm” name=”hifrm” src=”” width=”100%” height=”100%”>Please view this page in a modern browser like Firefox to be able to view this content!</iframe>

Please take note of the name attribute in the iFrame tag because that is what you have to set the target attribute in your link tag too! That is what sends the file with your content in it to the iFrame element on demand and it won’t load until you click the link! Perfect!

JavaScript Auto Complete for Text Field

Yesterday, I had a project that required me to create an auto complete feature for a text field. I searched for an existing solution online and couldn’t find one that fit my exact needs, so I created my own.  I created this particular solution using both PHP and JavaScript, but it could easily be modified to use only JavaScript if you prefer. However, if you have a long list of words or phrases you need to use for the auto complete feature, then you too may want to use PHP to call them into the JavaScript code as I have done.

A Working Example

Let’s start off by showing you a live working example so you can see what we are creating. Here is the link to the example I made as a proof of concept for the project I was working on:

http://jafty.com/test/cities.php

Type of Auto Complete

This particular type of auto complete feature is used on a regular text input field within a form. This auto complete feature is for when you have a list of words and/or phrases that you want to be made available as input into a text field. It is up to you if you want to allow values outside of that list or not. In my case I do not restrict my text field to values from the list, but I did consider it.

Why I Created a New Auto Complete Feature

I made this solution because I was working on a site for the country of Switzerland and had a need to allow Swiss users to enter a Swiss city into a form. I soon discovered that there are well over two thousand villages, towns and cities throughout Switzerland. I found a list that is supposed to contain all of the municipalities in Switzerland online. I wanted to make a script that would use that list for the auto complete feature for the city text field in that form. My solution allows for larger sized word/phrase lists to be employed with the auto complete feature for any text field you assign.

The Solution

Finally, let’s see some code! The code is mostly JavaScript with a bit of PHP. PHP is optional in this case, so if you desire a pure JavaScript solution, simply exclude the PHP file and hard-code the word/phrase list into the JavaScript code.

The Completed Code

Here is all the code from the cities.php file which is a full working example solution when paired with the towns.txt file which has the list of cities in it. Read the instructions provided near the end of this article to learn how to recreate a working example on your own server.

<style>
#town{
border: 1px solid #ccc;
padding-left:20px;
padding-right:15px;
width:165px;
margin-left:100px;
cursor: pointer;
}
</style>
<script>
//isGood JS function checks if char is a valid letter or not:
function isGood(c) {
return c.toLowerCase() != c.toUpperCase();
}

var first = ”;//used in suggest function to hold first letter entered into text field
var second = ”;//used in suggest function to hold 2nd letter entered into text field
var third = ”;//3rd letter…
var fourth = ”;//…
function suggest(c){
//build an array of cities:
var cities = [
<?php
$file = fopen(“towns.txt”, “r”) or exit(“Unable to open towns.txt file!”);
//Output a line of the file until the end is reached:
$line_no=0;
while(!feof($file)) {
$line_no++;
$line = fgets($file);
$curcity = trim($line);
echo “\”$curcity\”,”;
}//end while reading in lines from keywords.txt file
fclose($file);
?>
];
if(isGood(c)){
document.getElementById(‘town’).style.display=”;//shows the suggestions dropdown(town div)
//if only one letter so far:
if(c.length == 1){
//get first letter of c(what was entered into text field) and save it in “first” global variable:
first = c[0].toLowerCase();
}
if(c.length > 1){
second = c[1].toLowerCase();
}else{
second = ”;
}

if(c.length > 2){
third = c[2].toLowerCase();
}else{
third = ”;
}

if(c.length > 3){
fourth = c[3].toLowerCase();
}else{
fourth = ”;
}

con = ”;//empties contents of suggestions saved previously.
cities.forEach(foreachfunction);
document.getElementById(‘town’).innerHTML = ”;//empties the suggested cities first…
document.getElementById(‘town’).innerHTML = con;//fills the town div with suggested cities
}//end if letter is good
}//end suggest JS function by Ian L. of Jafty.com

var con = ”;//init a variable to hold content of the dropdown of suggested cities
//function ran for ea. city in the cities array:
function foreachfunction(city, index){
//get first letter of city:
var f = city[0].toLowerCase();
//see if first matches 1st letter of city(f):
if(f == first){//adds city to suggestions only if first letter is entered in text input:
//if more than one letter has been entered into text field, get 2nd letter of city too:
if(second != ”){//second is blank if only 1 letter, so if it’s not blank get 2nd letter:
if(city.length > 1)var s = city[1].toLowerCase();
if(city.length > 2)var t = city[2].toLowerCase();
if(city.length > 3)var f = city[3].toLowerCase();
if(fourth != ”){//fourth is blank only if 1,2,3 letters, so if it’s not blank, get 4th letter:
if(f == fourth && t == third && s == second){
con += “<div onclick=’addItem(this.innerHTML)’>”+city+”</div>”;
}
}else{
if(third != ”){//third is blank if only 1 or 2 letters, so if it’s not blank get 3rd letter:

if(t == third && s == second){
con += “<div onclick=’addItem(this.innerHTML)’>”+city+”</div>”;
}
}else{

if(s == second){
con += “<div onclick=’addItem(this.innerHTML)’>”+city+”</div>”;
}
}
}

}else{
con += “<div onclick=’addItem(this.innerHTML)’>”+city+”</div>”;
}
}
}//end foreachfunction JS function used within above suggest JS function by Ian L.

//function ran when one of the suggested cities is clicked upon:
function addItem(x){
document.getElementById(‘city’).value=x;
document.getElementById(‘town’).style.display=’none’;
}//end addItem JS function by Ian L. of Jafty.com
</script>
<form>
<table><tr><td>
Village / Town:<input style=”width:200px” type=”text” id=”city” name=”city” onkeyup=”suggest(this.value)” /><br />
<div style=”display:none;” id=”town” name=”town”>
</div>
</td><td>
<input type=”submit” id=”s” name=”s” value=”Suche” />
</td></tr></table>
</form>

The Word and/or Phrase List

In this example we have a long list of Swiss cities as our word list. Your word list can be a list of words and/or phrases which you would like to suggest as values to be entered into the text field for which you are applying the auto complete feature to. Each word or phrase can be as long as you like and should be formatted in a plain .txt file with one word and/or phrase per line. Generally you should probably try to keep your phrases short, but there is no actual limit that I am aware of regarding how long they can be. Just remember this is a “text” input not a “textarea” input, so it is meant for short values typically of one to six words approximately. My list is of city names so they are all relatively short and I’ve formatted the .txt file to list one city per line and saved it as “towns.txt” I have uploaded towns.txt to my personal server, so if you would like to copy it to use for testing your own script, you can do so by going to the following url and simply copying and pasting the word list into your own .txt file and naming it “towns.txt”. Here is the link to towns.txt:

http://jafty.com/test/towns.txt

Instructions to recreate your own auto complete feature

Follow these simple instructions if you want to create the live example on your own server. Then you may modify the code to fit your individual needs.

  1. First, copy the entire code example above in green text and save it to a .php file. I named my file cities.php, you can name yours whatever you like as long as it ends in “.php”.
  2. follow the above link to the towns.txt file and copy and paste it into a new .txt file and name it “towns.txt”. If you choose to name it something else, be sure to also change the name in the code wherever it is used by doing a search and replace for “towns.txt” and replacing it with your new name if you so choose.
  3. Upload both the PHP file and the TXT file to your server inside of the same directory. Then finally, you can visit the cities.php page in your browser and see it work! That’s all there is to it.

Creating a Pure JavaScript Solution Instead

If you prefer to have a pure JavaScript solution and your word list isn’t too large to handle within the JavaScript, follow these simple instructions:

  1. Follow the above instructions, but while doing step no. 1 above, after copying the green text above to a blank file, find the PHP section of code and simply delete it. Delete everything from “<?php” to after the closing PHP tag that looks like this: “?>”.
  2. Then where the php used to be, add the following code but replace the “one, two, three, four” text with your own personal word and/or phrase list.
    “one”,
    “two”,
    “three”,
    “four” , so your cities variable will look like t his now:
    var cities = [
    “one”,
    “two”,
    “three”,
    “four”
    ];
  3. Then you can save your file as an HTML file now since there is no more PHP code in it, so save it as cities.html and open it in your browser and it should work even from your desktop since HTML doesn’t require a server to function like PHP does.

Summary

That’s all there is to it! Feel free to modify this code to fit your needs as you wish. I hope it helps someone else as it did me. I found it very useful myself. I used this on a WordPress site as well, so it is WP friendly FYI.

 

 

 

How to Install NodeJS on Linux Server

In this article, I will cover how to very easily install Node.js on just about any Debian, Ubuntu or other Linux web server. I created this article to fill a great need for accurate information on this subject because I spent the better part of a full day getting Node.JS to work on the first server I had to install it on. Now that I’ve installed it on 3 or 4 different servers, I finally found a quick and easy way to make it work. I also had to make it so node could be called from PHP using the exec command, so it goes one step beyond just installing Node.js.

Here are the Four Quick and Easy Steps for Installing Node.JS on Linux Servers:

(use sudo in front of the command line commands if you are  not root user)

  1. Confirm that Curl is installed using “which curl” from command line and if it doesn’t return the curl location, then install curl before continuing.
  2. Confirm that Node isn’t already installed with the command line command “node –version”. if it doesn’t print a version no. then it needs installed.
  3. Using instructions from https://github.com/nodesource/distributions/blob/master/README.md#debinstall and If on a Debian server, go to command line and run(use sudo if not root user):
    curl -sL https://deb.nodesource.com/setup_8.x | bash –
    …and hit enter….then go to step 4.
    NOTE: if you’re not on a Debian server, follow the relative instructions for your particular server by scrolling to it in the above github URL instead of steps 3 and 4 here.
  4. then run this command from the command prompt(use sudo if not root user):
    apt-get install -y nodejs

Then you can verify that NodeJS works from the command line with:

node –version

and it shold return something like:

8.4.0

 

WordPress Editor is Missing Tabs

I found my site was missing the “Visual” and “Text” tabs along with several other options including the “Kitchen Sink” option that adds the entire second row of menu options to the WYSIWYG editor! So, I was lost without these options and the fix was very difficult to figure out, so I’m going to share it in case someone else has this happen to them.

First off, let me just say that this may not be the solution for you. While searching for the solution, I saw there were several other issues that can cause this, so I’ll go over some of the easy and more common fixes first for you just in case. If the first two solutions don’t work, scroll down to my “The Tricky Fix” section below and give that one a try because that’s the one that got it working for me.

The Easy Fix:

The easy fix is to simply navigate to wp-admin and go to “Users”/”Your Profile” and at the top of your profile admin page you’ll see the “Visual Editor” option with a checkbox that reads: “Disable the visual editor when writing”. All you have to do is make sure this box is NOT CHECKED and then scroll to the bottom of the page and click on the update button to save your settings. Even if it was not checked to begin with, try checking, saving and then uncheck it and save it again and test the post/page editor again and sometimes this alone will make it work, but if it didn’t, keep reading!

The Difficult Fix:

The more difficult but probably second most common fix involves reinstalling or updating WordPress. If that doesn’t work, try disabling plugins one at a time to see which one may be causing the issue. Hopefully one of these first two fixes worked for you because those are the most common ones, however keep reading if you have yet to find a solution that enables your visual editor and missing editor tabs in WordPress. There’s one much trickier solution that is very hard to fine, but rather easy to implement….

The Tricky Fix:

Now for the tricky fix. This fix saved my day! If I hadn’t found it I would have spent all day trying different tricks to fix my Visual Editor and missing buttons issue for my WordPress blog, Jafty.com/blog. It has to do with missing headers and is related to a server issue that I admittedly do not fully understand. All I know is that this fix works if your server has this particular issue as mine did. All you have to do is add a few lines of PHP code to any active plugin or to your theme’s functions.php file. If you are not a plugin developer who happens to be making a plugin at this time, I would add it to my functions.php file. Here is the code:

function richedit_wp_cloudfront () {
add_filter(‘user_can_richedit’,’__return_true’);
}

add_action( ‘init’, ‘richedit_wp_cloudfront’, 9 );

Simply add that to the top of your functions.php file, save it and upload it to your server and refresh the page with the editor on it and it should now work. Mine did. If this doesn’t work, then unfortunately you must have a different sort of issue than I did. I have read online that Amazon servers can have this problem, although it happened to me on a Linode server. Good Luck!

How to Stop WordPress Functions from Running More than Once

I’m going to share what I learned yesterday because it took me about five hours to figure this out and I hope to save other people and myself from having to spend so much time figuring this out in the future. I was working on a plugin for a client that had to fire a function using the init WordPress hook, something done quite often in plugin development. The problem was that this function was running more than one time each time a page loaded. In a perfect world, init should only fire one time per page load, but in real life, it often fires more than once. I’ll get into some reasons for this later, but for now just understand that the problem was related to a PHP function running more than once per page load and I only wanted it to run one time per page load.

The WordPress Init Hook

The WordPress init hook fires after WordPress has finished loading but before any headers are sent. It is very often NOT the best WordPress hook to tie a function too, in fact it should be used quite sparingly! However, should you find a need to use it, this post should help you some I hope. You should only use the init hook if you want something to happen every time any page is loaded. By any page, I mean any admin page or front end page including error pages and everything, so be careful how you use it!

The Main Issue With Init Hook

In my opinion the main issue with using the init hook to call a function in WordPress is that it can be rather unpredictable depending on the state of your WordPress site. For example, the most common issue that can cause the init hook to fire more than once per page load is missing images and missing scripts that are part of the page being loaded but that WordPress can’t find. Every time a page’s image is missing or a script that is linked to the page is missing, a 404 error occurs which in turn fires init yet again. That means if you hook a function into init that opens a pop up window that says “Hello World!”, and the page you open in a browser is missing the header image, then it causes that pop up window to open two times. Now if you happen to be also missing a linked to css file, it could fire even a third time etc…..needless to say, it could be very inconvenient for you as a coder and the end user who has to close all of those nasty pop ups you inadvertently subjected them too…

The Solution

Now, lets say you’ve read to hear and you still want to use the init function or you have no other choice in the matter, then you can do what I did. First I tried several other methods recommended on the internet to stop my function from running more than once. I will list some of those methods here just so you don’t waste your time on them like I did. Please note that some of the methods may work for you if the situation is a little different, but if you are trying to stop a function from running more than once at a time and that function is triggered by the init WordPress hook, then none of the following are likely go work for you:

What Did NOT Work

  1. First I tried simply writing the time to the database ea. time the function ran and also made it so the function read in the time and if it was less than a minute ago, it wouldn’t run the function again in theory. In practice, this doesn’t work! Trust me. Not for this situation at least.
  2. Then I tried using something new to me, semaphore. Semaphores are PHP protected variables used to control access to some resource. They are developed by Dijkstra. I think the reason semaphores didn’t work to stop a function from running more than once was because often times the init hook will fire twice within a hundredth of a second making it almost simultaneious and that doesn’t give the semephore time to work. Don’t expect semaphores to work if you are using the init function and you want to stop a function from firing more than once, but you can learn more about Semaphores here: http://www.re-cycledair.com/php-dark-arts-semaphores. They are interesting, just not for this scenario.
  3. I also tried various methods of saving to the options table using a built-in WP function such as update_option and similar, but everything was too slow to catch the nearly simultaneous firing of the init hook that seem to occur often in WordPress.
  4. The main reason for the init hook firing multiple times per page load to begin with is due to missing images and files as I explained above, so the next thing I tried was to simply clean up the site. If you have a small site, fixing all broken links, images and missing files will probably work for you, but if you have a site like the one I’m working on, forget it! I tried, but quickly found out that it would be a multiple day project to go this route, so I moved on to the next solution. This one however may work for some of you. The way I would go about it is to use Firefox and inspect the page using the web console with the network requests visible. You have to refresh the page after you set them to visible to see all of the network requests. Then simply look for 404 errors and it should tell you all of them for that particular page. You’ll have to do this for every page in the site is why I don’t recommend this solution for larger sites.

What Finally Worked for Me

Now, what worked for me was a combination of things I tried above in a way. I’ll try to keep it short and get to the solution instead of how I came to it because that is a never ending story! Here is some example code that I simplified because this was part of a much more complex and copyright protected solution for a client. This snippet will show you how to stop functions fired by init from running more than one time in the same minute(or whatever interval you set):

<?php
/*
This is example code for how to make a function triggered by the WordPress Init hook only run once per page load and only once per minute or other time interval as set in the code.
*/

//hook a function handler function to init instead of the main function that does the work:
add_action(‘init’, ‘function_handler’, 10, 3);
function function_handler(){
//start out with a random pause from 1 to 5 seconds:
$rn = rand(1000000, 5000000);
log_debug(“Random time was $rn\n”,”a.log”);
usleep($rn);//1000000 is 1sec and 250000 is 1/4 sec etc. 100000 is about 1/10 of a sec.

//get last time the import function was ran from time.txt
$cdir = plugin_dir_path(__FILE__);//get dir of current file with trailing slash
$tFile = $cdir.’time.txt’;
$ftime = “”;//set default value for ftime:
if($file = fopen($tFile, “r”)){
//Output line at a time until end is reached:
while(!feof($file)) {
$ftime = fgets($file);
}
fclose($file);
//log time fetched from time.txt
log_debug(“Time retrieved from time.txt: $ftime\n”,”a.log”);
//log each time this function is called:
}else{
log_debug(“failed to read time.txt time\n”,”a.log”);
}

$lasttime = new DateTime($ftime);//turn time found into a valid DateTime obj.
$dt = new DateTime(“now”);//put the current time into valid DateTime obj.

$dtf = $dt->format(‘Y-m-d H:i:s’);//format $dt or current time to display it
log_debug(“Current time: $dtf\n”,”a.log”);

$lasttimef = $lasttime->format(‘Y-m-d H:i:s’);//format time found to display it
log_debug(“last time ran(according to time.txt): $lasttimef\n”,”a.log”);

//get difference between $dt and lasttime:
$dteDiff = $lasttime->diff($dt);
//format the time difference in a human readable format:
$time_diff = $dteDiff->format(“%H:%I:%S”);
log_debug(“Time since import function last ran: $time_diff\n”,”a.log”);
//see if $time_diff is more than a minute and if not, stop function now!
$comparetime = strtotime(“00:01:00”);//set to max time allowed between function calls!
$timediff = strtotime($time_diff);
if($ftime == ” || $timediff > $comparetime){
//if no time stored yet or time diff is more than a minute, then continue…
log_debug(“ftime was blank or it has been more than a minute since last run so continuing to run import function next!\n”,”a.log”);
}else{//otherwise, kill operation of function because it hasn’t been enough time:
log_debug(“——NOT enough time since last run. import function stopped!\n”,”a.log”);
return;//stops execution of this function here!
}
//code past here only gets executed if it has been more than x minutes since last import
//store time in time.txt:
$fh = fopen($tFile, ‘w’) or die(“can’t open time.txt file to append, Please make sure your follow-hook folder and all files are owned by the www user(user www-data usually). To change ownership you have to access your server command line and issue the command: ‘chown -R www-data:www-data cutivate-crm’ while you are in the wp-content/plugins directory(cd /var/www/yoursite.com/public_html/wp-content/plugins). If problem persists, email developer, Ian L. at linian11@yahoo.com”);
fwrite($fh, $dtf);
fclose($fh);
//go ahead and run your import function now:
import_function();//this is the function that will only get run once!
}//End function_handler PHP function by Ian L. of Jafty.com

function import_function(){
log_debug(“***import_function has been triggered!***\n”,”a.log”);
}//end import_function that can only be ran once a min max.

//Custom function to write data to log file:
function log_debug($log,$filename=’a.log’){
$pdir = plugin_dir_path( __FILE__ );//gets the directory of current file with trailing slash whether inside plugin or not!
$myFile = $pdir.$filename;
$fh2 = fopen($myFile, ‘a’) or die(“Error – can’t open $filename to append…”);
fwrite($fh2, $log);
fclose($fh2);
}//End log_debug PHP function

?>

That’s my solution! Let me explain a little more. The above code is meant to go in a plugin file since this is a technique that should really only be used by plugin developers. It could also go into a theme’s functions.php file, but is less likely to be used there since it depends on other code and isn’t meant to be by itself really. In my case I had a function that imported users into a WordPress site as posts and it had to be done every x minutes, so I had the init hook call the function_handler function. That function_handler function is just to keep the import_function from being called more than one time per minute.

Before I had this set up I had the import_function being called from the WordPress init hook and inside the import_function I logged ea. time it ran to a log file and sometimes when a page was loaded, I would see lines like this in the log file:

Apr,23,2019 07:53:43 PM – check_import_queue function began….

Apr,23,2019 07:53:43 PM – check_import_queue function began….

…and somtimes I might get something like this:

Apr,23,2019 08:03:03 PM – check_import_queue function began….

Apr,23,2019 08:03:04 PM – check_import_queue function began….

…and other times there might be three of these lines or more and sometimes there was only one as it should be, but not often. So, as you can see, some were posting the exact same time and some were only a second apart, so I developed the above code to make them at least be a minute apart to keep my server from getting backed up with import calls. The import function itself was coded to prevent doubles(two of the same person) from being inserted into the post type, but WordPress is not able to catch doubles if the import function was fired within a second or less from the last time it was fired. Therefore without the above code, I would get hundreds of double posts even with code in place to stop it. It simply would not work without the code I wrote above.

The Random Pause

You probably noticed at the start of the code there is a random pause from 1 to 5 seconds. This keeps the function_handler function from writing more than one line to the time.txt file at a time and also will help give WordPress time to do what it has to do in the background as well. I’ve tried it without the pause and it doesn’t work.

Writing to a .txt File

The part of the code that writes the times the import_function was run to a .txt file named time.txt is also part of the trick that makes this work. I could find no other way to store the data fast enough for it to be ready by the next time the init hook fired. I tried storing it in a custom database table and in the WordPress options table, both methods failed. I also tried using a PHP semaphore as I mentioned above and that too failed in my case.

 

WordPress Shortcode Not Showing Up Where It Should?

If you are creating a fantastic plugin or an extraordinary theme or even a lame one of the two, Shortcodes are always fun to work with in WordPress until you run into a situation where the content being generated by the shortcode simply won’t show up where you want it to.

For example after creating a shortcode in a plugin, I tried enter this into a blog post or page today using the WYSIWYG editor :

some content before the shortcode<br />

[my-simple-shortcode]

some content after the shortcode<br />

…and when I saved and viewed the page or post, I got:

my shortcode’s content here…..

some content before the shortcode

some content after the shortcode

….so that is NOT what I wanted clearly and I found a solution!

In simple terms, the solution is not to echo the shortcode content in the shortcode’s function, but to return it instead.

The correct way to create a WordPress Shortcode:

For example, here is how you create a simple shortcode the correct way:

<?php

function create_simple_shortcode(){
    $returnvar = “<p>The content that your shortcode displays…</p>”;
    return $returnvar;
}//end create_simple_shortcode function

//name the shortcode simple_shortcode:
add_shortcode(‘simple_shortcode’,’create_simple_shortcode’);

?>

The wrong way to create a WordPress Shortcode:

In contrast, this is the incorrect way to create a WordPress shortcode using echo instead of return:

<?php

function create_simple_shortcode(){
    $returnvar = “<p>The content that your shortcode displays…</p>”;
    echo $returnvar;
}//end create_simple_shortcode function

//name the shortcode simple_shortcode:
add_shortcode(‘simple_shortcode’,’create_simple_shortcode’);

?>

…this is also NOT correct:

<?php
function create_simple_shortcode(){
    echo “<p>The content that your shortcode displays…</p>”;
}//end create_simple_shortcode function

//name the shortcode simple_shortcode:
add_shortcode(‘simple_shortcode’,’create_simple_shortcode’);
?>

The above could also be done correctly like this however:

<?php
function create_simple_shortcode(){
    return “<p>The content that your shortcode displays…</p>”;
}//end create_simple_shortcode function

//name the shortcode simple_shortcode:
add_shortcode(‘simple_shortcode’,’create_simple_shortcode’);
?>

Get it? return NOT echo! Good luck! hopefully this will save you some time!

Using Ajax on Admin Page in WordPress Plugins

Today, I’m writing a rather simple WordPress Ajax example that demonstrates how to user Ajax in a wp-admin page from a WordPress Plugin.

Using Ajax in wp-admin is a little easier than using it on the front-end of a WordPress site because of the ajaxurl JavaScript global variable which refers to the admin-ajax.php url.

In the following code example the goal is to provide an admin page with a delete post link that is powered by Ajax. Therefore the code enables the deletion of a WordPress post without the obtrusive page refresh!

This is a pretty bare-bones example meant as a learning tool. It could be fleshed out much more to be more practical, but I wanted to keep it simple to get the main point across, which is how to do a simple Ajax request from wp-admin.

The Essential Code

First, for those of you that only need to see the basics, I’ll demonstrate the essential code for the Ajax to work. Further down this page, I’ll put it all together in a simple plugin for those of you that may need to see a working example to grasp the concept better. Here is the essential code to enable an Ajax delete post button:

First, a simple HTML line to call our JavaScript function onclick:

<span onclick=’ajax_delete_posts(<?php echo $ID; ?>)’ style=’color:red;cursor:pointer’ title=’Delete Person!’>X</span> <?php echo $title; ?><br />

In the above, you’ll notice the ajax_delete_posts JavaScript function is called. ajax_delete_posts is the function we must create! Of course you’ll need to define the post id in $ID and the post tile in $title using PHP, but this is just to show you how I intend to call the Ajax/JavaScript function

Second, we use PHP to generate our ajax_delete_posts function in JavaScript code:

You can rename the ajax_delete_posts as you wish, just make sure to rename it in your HTML above too if you do. Here’s the code to generate the JavaScript from within your PHP file, the plugin’s main file normally:

<?php

//add Ajax for deleting posts or custom post type(replace ‘post’ with cpt name):
add_action(‘admin_footer’, ‘del_posts_js’);
function del_posts_js() {
?>
    <script type=”text/javascript” >
    function ajax_delete_posts(t) {

        var data = {
            ‘action’: ‘del_posts_with_ajax’,
            ‘t’: t
        };

        // since 2.8 ajaxurl is always defined in the admin header and points to admin-ajax.php
        jQuery.post(ajaxurl, data, function(response) {
            alert(‘Ajax server response: ‘ + response);
        });
    }//end Ian’s JS AJAX function
    </script>
<?php
}

?>

Notice in the above code how we pass the post id to the JavaScript function using the “t” variable. Also it’s important to note your data variable’s action value as you will need to use the same value for the function name in the below code. Feel free to use whatever text you want for the action as long as it can be an appropriate PHP function name in the code below as well.

Finally, the third step is to add your PHP function named after the action named above and also hook into it with “wp_ajax_” followed by the action named above as well, so in our case the hook we use would be “wp_ajax_del_posts_with_ajax”. However, if you use a different action, such as “do_aj” for example, then your action hook would read “wp_ajax_do_aj”. Got it? I hope so, but the code example should help too, so here it is:

<?php
add_action( ‘wp_ajax_del_posts_with_ajax’, ‘del_posts_with_ajax’ );

function del_posts_with_ajax() {

    $tpidof = $_POST[‘t’];
    
    if(wp_trash_post($tpidof)){
        echo $tpidof.” Deleted!”;
    }else{
        echo “$tpidof Failed to delete!”;
    }

    wp_die(); //required to terminate immediately and return response!
}
?>

In the above code example, we first set up our action hook by using the hook “wp_ajax_” followed by the action we used in the “var data” line further above.  Then we write a PHP function named exactly like the action we used above and be sure to also name the function as the parameter in the action hook as well and you will be good to go!

Putting it all together in a simple Ajax WordPress Plugin

Next I’ll show you the example plugin I created to demonstrate exactly how this all works together in case some of you failed to grasp it from my previous explanations. You can cut and paste the following PHP code into a file named “ajax_post_delete.php” and place it on your desktop in a folder named “ajax_post_delete” and I promise you the code will function on any standard WordPress installation as of the day I wrote this. Here is the complete “Ajax Post Delete” WordPress plugin code in a single file example plugin:

<?php
/*
Plugin Name: Ajax Post Delete
Plugin URI: http://jafty.com/ajax_post_delete

Description: A plugin used in the article at http://jafty.com/blog/?p=10739 in order to demo an admin ajax example

Author: Ian L. of Jafty.com
Author URI: http://jafty.com

Version: 1.0.0

License: GNU General Public License v2.0
License URI: http://www.opensource.org/licenses/gpl-license.php
*/

//function to add wp-admin menu:
add_action(‘admin_menu’, ‘add_ajax_post_delte_admin_menu’);
function add_ajax_post_delte_admin_menu() {

    $page_title=’Delete posts’;
    $menu_title=’Delete posts’;
    $capability=’manage_options’;
    $menu_slug=’del_posts_pg’;
    $function=’mk_dele_pg’;
    $icon_url = ”;
    $position=’4′;
    
    add_menu_page($page_title, $menu_title, $capability, $menu_slug, $function, $icon_url, $position);
    

}//————end add_ajax_post_delte_admin_menu function——————

function mk_dele_pg(){
?>
<h1>Delete posts</h1>
<?php
//get all posts:
$args = array(
‘post_type’ => ‘post’,
‘posts_per_page’=>-1
);
global $wp_query;
$wp_query = new WP_Query($args);
//start looping posts
while($wp_query->have_posts()) : $wp_query->the_post();
$person = get_the_title();
$personID = get_the_ID();
//echo $person.”<br />”;
echo “<span onclick=’ajax_delete_posts($personID)’ style=’color:red;cursor:pointer’ title=’Delete Person!’>X</span> $person<br />”;
endwhile;

}//end mk_dele_pg function

//add ajax for deleting touchpoint history for posts CPT:
add_action(‘admin_footer’, ‘del_posts_js’); // Write our JS below here

function del_posts_js() {
?>
    <script type=”text/javascript” >
    function ajax_delete_posts(t) {

        var data = {
            ‘action’: ‘del_posts_with_ajax’,
            ‘t’: t
        };

        // since 2.8 ajaxurl is always defined in the admin header and points to admin-ajax.php
        jQuery.post(ajaxurl, data, function(response) {
            alert(‘Ajax server response: ‘ + response);
        });
    }//end Ian’s JS AJAX function
    </script>
<?php
}

add_action( ‘wp_ajax_del_posts_with_ajax’, ‘del_posts_with_ajax’ );

function del_posts_with_ajax() {

    $tpidof = $_POST[‘t’];
    
    if(wp_trash_post($tpidof)){
        echo $tpidof.” Deleted!”;
    }else{
        echo “$tpidof Failed to delete!”;
    }

    wp_die(); //required to terminate immediately and return response!
}

?>

That’s it! copy and paste the above code into your .php file and you’ve got a simple WordPress plugin that will demonstrate the use of Ajax effectively in a custom admin page of your WordPress website. Feel free to modify the code as needed, but please change the plugin name if you intend to distribute it. Thank you and Good Luck!

Adding Custom Field to WordPress Quick Edit UI for Custom Post Type

Adding a custom field to the WordPress Quick Edit screen for a custom post type was a rather tricky task, so I wanted to be sure to share my solution with my readers in case any of you have a difficult time with it as well.

Let’s get our hands dirty shall we?

There are six basic high level steps to adding a field to quick edit which are:

  1. Add a new column to the posts screen for the post type – I thought this was not necessary at first, but I assure you that it is 100% required for the new field to work within the quick edit UI. Therefore if this turns you off, you can surely find a way to hide the field from view if you don’t want it to show up on the posts admin screen.
  2. Fill the column you added in the previous item with data – While this step isn’t actually required for the field to work in the quick edit screen, it is needed if you wish for the column to have relevant data in it. However, if you wish to hide the column as I suggested could be done, then you may surely skip this step. I don’t recommend skipping it however.
  3. Add HTML code to the Quick Edit User Interface – this will be accomplished, as you’ll soon see below, with WordPress’s quick_edit_custom_box action hook and your custom HTML for your custom field.
  4. Save the new custom field’s value that you’ve added to the Quick Edit UI – This can be easily achieved by making use of the WordPress save_post action hook as you will discover below.
  5. Add some JavaScript code in the admin page’s footer to update your custom fields value within Quick Edit UI – this is where it gets a little tricky for some. I’ll explain as best I can below, but suffice it to say for now that you have to update the input field you created in the quick edit menu with JavaScript that’s generated with PHP code using WordPress’s admin_footer hook. I have seen some alterations of this in other posts online where people use JQuery, but I tend to find that using JQuery is often more likely to lead to plugin conflicts if you are not very careful, so I prefer to use plain JavaScript whenever possible.
  6. Finally you’ll need to link your JavaScript function from the previous step to the Quick Edit link for each post using a JavaScript OnClick event – this is done with the elusive WordPress filter named post_row_actions that allows you to change the context of the Quick Edit link itself with some PHP magic.

Now that the high level steps have been explained in very little detail to give you an idea of what we’ll be doing in this tutorial, I will demonstrate each step with a working code example that you can use to paste into your own Plugin or Theme files as needed. If you don’t know where to paste the code, then frankly, this tutorial may not be for you, so I won’t go into that part in detail since this tutorial is really meant for plugin developers and all of them surely know where to place the code I’ll be showing below.

Step 1 – Add a new column to the posts screen for the post type

Step 1 is by far the easiest step with very little code. All we need to accomplish here is to add a column to the Custom Post Type admin screen. Here is the simple PHP code:

<?php

//Add groomer Column to dogs CPT:
add_filter(‘manage_dogs_posts_columns’, ‘dogs_groomer_post_column’);
 
function dogs_groomer_post_column($columns) {
    $columns[‘groomer’] = ‘groomer’;
    return $columns;
}//end dogs_groomer_post_column PHP function

?>

Not a lot you need to do to the above code but change the custom post type name and the field name to your own CPT and Custom Field Name, so search and replace “dogs” with the name of your Custom Post Type. Then search and replace “groomer” with your custom field name. Your now done with step 1! Don’t worry, they will get harder!

Step 2 – Fill the column you added in the previous item with data

Now all we have to do in step 2 is fill our column from step 1 with relevant data. In our case, the name of the groomer for each dog in the dogs custom post type. Here’s the PHP code:

<?php

//Add content to new groomer Column in wp-admin for dogs cpt:
add_action(‘manage_posts_custom_column’, ‘render_groomer_column_for_dogs’, 10, 2);
 
function render_groomer_column_for_dogs($column_name, $id) {
    switch($column_name) {
    case ‘groomer’:
        // show groomer
        $groomer_val = get_post_meta( $id, ‘_groomer’, TRUE);
        if($groomer_val == ”)$groomer_val = ‘None’;
        echo $groomer_val;              
        break;
    }
}//end render_groomer_column_for_dogs PHP Function

?>

Again, all you have to do to make this code your own is replace “dogs” with the name of your custom post type and replace “groomer” with the name of your custom field and be sure to get the ones with the underscore in front of them too! I like to put underscores in front of field names when saving them to the database. That’s all for step 2! Another easy one! Okay on to step 3.

Step 3 – Add HTML code to the Quick Edit User Interface

In step 3 we have to add a PHP function using the quick_edit_custom_box WordPress hook that adds HTML content to the Quick Edit User Interface. Here’s the PHP code:

<?php

//add groomer to quick edit screen:
add_action(‘quick_edit_custom_box’,  ‘CCRM_add_groomer_to_quick_edit’, 10, 2);
 
function CCRM_add_groomer_to_quick_edit($column_name, $post_type) {
    if($column_name != ‘groomer’) return;
    ?>
    <fieldset class=”inline-edit-col-left”>
    <div class=”inline-edit-col”>
        <input type=”hidden” name=”groomer_noncename” id=”groomer_noncename” value=”” />
<label for=”groomer_status”><?php _e(‘groomer: ‘, ‘dogs’); ?><select name=”groomer_status” id=”groomer_status”>
    <option>-Pick one-</option>
    <option>Ted Dogly</option>
    <option>Teresa Lassie</option>
    <option>Megan Dame</option>
    <option>Star Wolfe</option>
    <option>Joan Smith</option>
    <option>Dober Mann</option>
    </select></label>
    </div>
    </fieldset>
    <?php
}//end CCRM_add_groomer_to_quick_edit PHP function

?>

Again to make the above code your own, you’ll want to replace all occurrences of dogs and groomer with your own names as in the previous two steps. Another option you may wish to change in the above code will be the input type. Here we have decided to use a select input to create a drop-down of possible groomer names to assign to each dog. You may prefer to use a text field, checkbox or even a textarea here, you can use any form element you want to collect your custom field’s data. Simply change the above code to fit your individual needs and your done with step 3!

Step 4 – Save the new custom field’s value that you’ve added to the Quick Edit UI

In step 4, we save the new custom field value to the WordPress database when it is edited from the Quick Edit dialog. As with all the previous steps, change the names of dog and groomer according to your own post type and field name respectively. In the below code we use the common “save_post” WordPress hook to detect when a new field value is submitted and save it with the following PHP code:

<?php

//save groomer for quick edit screen:
add_action(‘save_post’, ‘save_groomer_quick_edit_data’);
 
function save_groomer_quick_edit_data($post_id) {
    $post = get_post($post_id);
    //verify if this is an auto save routine. If it is our form has not been submitted, so we dont want to do anything:
    if(defined(‘DOING_AUTOSAVE’) && DOING_AUTOSAVE)
        return $post_id;    
    //Check permissions
    if(‘dogs’ == $post->post_type){
        if(!current_user_can(‘edit_page’, $post_id ))
            return $post_id;
    }   
    //OK, we’re authenticated: we need to find and save the data
    
    if(isset($_POST[‘groomer_status’]) && ($post->post_type != ‘revision’)) {
        $groomer = esc_attr($_POST[‘groomer_status’]);
        if ($groomer)
            update_post_meta($post_id, ‘_groomer’, $groomer);     
        else
            delete_post_meta($post_id, ‘_groomer’);     
    }       
    if(!isset($groomer))$groomer=”;
    return $groomer;  
}//End save_groomer_quick_edit_data PHP function

?>

The above code is fairly straight forward, so simply change dogs and groomer text as with previous steps and move on to step 5 below.

step 5 – Add some JavaScript code in the admin page’s footer to update your custom fields value within Quick Edit UI

Here is where it gets a little tricky! There’s no simple PHP code to update the Quick Edit UI with the latest updated field value, so we have to generate some JavaScript with the following PHP code. We’ll use the admin_footer WordPress hook to inject JS code into the footer of the admin page like so:

<?php

//add JS to footer that updates quick edit dropdown:
add_action(‘admin_footer’, ‘groomer_quick_edit_JS’);
 
function groomer_quick_edit_JS() {
    global $current_screen;
    if(($current_screen->id != ‘edit-dogs’) || ($current_screen->post_type != ‘dogs’)) return;
     
    ?>
    <script type=”text/javascript”>
    <!–
    function replace_quick_edit_link(widgetSet, nonce) {
        //alert(‘set_inline….’);
        //revert Quick Edit menu so that it refreshes properly
        inlineEditPost.revert();
        var widgetInput = document.getElementById(‘groomer_status’);
        var nonceInput = document.getElementById(‘groomer_noncename’);
        nonceInput.value = nonce;
        //check option selected
        for(i = 0; i < widgetInput.options.length; i++) {
            if(widgetInput.options[i].value == widgetSet) {
                widgetInput.options[i].setAttribute(“selected”, “selected”);
            }else{widgetInput.options[i].removeAttribute(“selected”);}
        }
    }//End replace_quick_edit_link JavaScript Function
    //–>
    </script>
    <?php
}//End groomer_quick_edit_JS PHP function

?>

Now the above code maybe a little complicated for some, especially those of you who want to use a different type of form field rather than use the drop-down select like in our code. Where you see the section of code that has the comment “//check option selected” before it is where you’ll need to make significant code changes if you use anything other than a select field in your own code. Also be sure to also change all occurrences of dog and groomer as in each of the previous steps.

Step 6 – Link your JavaScript function from step 5 to the Quick Edit link for each post using a JavaScript OnClick event

Next due to the fact that it’s impossible to update the option updated in the quick edit UI, we use the following PHP and JS magic to make it happen with the help of the WordPress post_row_actions filter hook:

<?php

//code to modify quick edit link with on-click event:
add_filter(‘post_row_actions’, ‘groomer_expand_quick_edit_link’, 10, 2);
 
function groomer_expand_quick_edit_link($actions, $post) {
    global $current_screen;
    if (($current_screen->id != ‘edit-dogs’) || ($current_screen->post_type != ‘dogs’)) return $actions;
 
    $nonce = wp_create_nonce(‘groomer’.$post->ID);
    $groomer_val = get_post_meta($post->ID, ‘_groomer’, TRUE);
    $actions[‘inline hide-if-no-js’] = ‘<a href=”#” class=”editinline” title=”‘;
    $actions[‘inline hide-if-no-js’] .= esc_attr(__( ‘Edit this item inline’ ) ) . ‘” ‘;
    $actions[‘inline hide-if-no-js’] .= ” onclick=\”replace_quick_edit_link(‘{$groomer_val}’, ‘{$nonce}’)\”>”;
    $actions[‘inline hide-if-no-js’] .= __( ‘Quick&nbsp;Edit’ );
    $actions[‘inline hide-if-no-js’] .= ‘</a>’;
    return $actions;    
}//end groomer_expand_quick_edit_link PHP function

?>

Changing each occurrence of dogs and groomer in the above code should be sufficient in most cases to make it your own. Good luck!

Going a Step Further with Bulk Edit Ability Too!

Okay, this technically makes it seven steps instead of six, but I added this as an afterthought because it is so easy to add the bulk edit feature to the above code. All you have to do in order to make the same custom field from the above code show up in the bulk edit screen or UI, is add one line of code! Therefore I thought it wise to add it here.  Here is how:

If you look above at step 3, the first line of PHP code is the quick edit hook which looks like this:

add_action(‘quick_edit_custom_box’,  ‘CCRM_add_groomer_to_quick_edit’, 10, 2);

All you have to do to also make the same field show up in the bulk edit screen too is add the following code that contains the bulk_edit_custom_box hook in place of the quick_edit_custom_box hook in the above line of code like this:

add_action(‘bulk_edit_custom_box’,  ‘CCRM_add_groomer_to_quick_edit’, 10, 2);

That’s it! you can add the above l line directly before or after the quick_edit_custom_box hook as in step 3 above. Now if you select all posts from the custom post type screen and then in the “bulk actions” dropdown, select “edit” and you can edit the new custom field value for all of your posts at one time! Yes, we saved the simple step for last! Enjoy your new bulk and quick editor plugin!

Summary

In conclusion, you’ll want to incorporate the above code examples into your own plugin for best results. I have created such a plugin for anyone who needs it. Please feel free to contact me by email at linnian11@yahoo.com if you would like the plugin.

Basic WP Cron Jobs

This is going to be a simple WP Cron tutorial that explains the basic use of WordPress cron, the wp_schedule_event function and it’s 3 built in intervals. I’ll write a more complex one soon that explains how to add your own intervals, but this is a good starting point:

Practical Code Example

<?php
//Add function to register daily function on WordPress init
add_action( ‘init’, ‘register_daily_cron’);

// Function which will register the daily event:
function register_daily_cron() {
    // Make sure event hasn’t been scheduled already:
    if(!wp_next_scheduled(‘daily_cron’)) {
        // Schedule the event
        wp_schedule_event(time(), ‘daily’, ‘daily_cron’);
    }
}

?>

Don’t Hard Code WP-Content Directory!

Plugin development tip of the day!
DO NOT HARD CODE wp-content Directory!

I made this mistake not realizing how many people change that directory as a security precaution.

Alternatives

The best alternative is probably the WP_CONTENT_DIR in my opinion. You can also use WP_CONTENT_URL, depending if you want a relative path or a full url.

Examples:
Here is an example of each followed by the output code:
<?php
echo WP_CONTENT_DIR;
echo “<br />“;
echo WP_CONTENT_URL;
echo “<br />“;
?>

/var/www/html/jafty.com/public_html/wp-content
http://jafty.com/wp-content

So now you know!

Want more? Here are some other ways to get similar paths or directories  from within a plugin file:

<?php
echo “<br>”;
echo plugins_url( ‘myscript.js’, __FILE__ );
echo “<br>”;
echo plugins_url();
echo “<br>”;
echo plugin_dir_url(__FILE__) ;
echo “<br>”;
echo plugin_dir_path(__FILE__) ;
echo “<br>”;
echo plugin_basename(__FILE__) ;
?>

The above would output the following lines if it were ran from a plugin on jafty.com:

http://jafty.com/wp-content/plugins/jafty_plugin_tester/test_code/myscript.js

http://jafty.com/wp-content/plugins

http://jafty.com/wp-content/plugins/jafty_plugin_tester/test_code

//var/www/html/jafty.com/public_html/wp-content/plugins/jafty_plugin_tester/test_code/

jafty_plugin_tester/test_code/wp_content_url.php

EDD Instant Updater WordPress Plugin

The EDD Instant Updater WordPress plugin is a plugin I created while working with a custom WordPress plugin that needed to have instant update notifications while using Easy Digital Downloads(EDD). EDD comes with it’s own updater class, but it uses the default WP standards which means you have to wait up to 12 hours after you push an update to get the notice in the WordPress dashboard! With the EDD Instant Updater plugin, you no longer have to wait, the plugin update notices come to you instantly! No more delays!

Interested? Contact Ian L of Jafty.com by email at linian11@yahoo.com for more information.

How to Fix Max File Upload Size on Godaddy and Other Shared hosting Accounts

I ran into this problem using WordPress today where I reached the max upload size on my client’s Godaddy server on a shared hosting account. If you get this error in WordPress, while uploading a file, by the way, it’s probably a max file size issue:

Error: 1

That’s all it said! So it took me a few guesses to figure out what that “Error: 1” message meant.

How to increase the Max File Size Setting in Cpanel

1 – go to your site’s Cpanel by going to yoursite.com/cpanel and log in with the user and password your hosting provider provides for you. You should see this when logged in:

cpanel1

2) scroll down until you see the “Select PHP Version” link in Cpanel that is circled in the below image and click on it:

cpanel1b

3) That will take you to the PHP version selection page. Then click the link in the upper right corner of the page that you see circled in the below image:

cpanel2

4) Clicking the “Switch to PHP Options” link in the above image will take you to a PHP settings page. Look for the upload_max_filesize setting that you see circled in the image below and set it to something higher than 2M according to your individual needs:

cpanel3b

5) After you change it from “2M” to something higher as needed, click the “Apply” button directly to the right of the dropdown and then be sure to also click the “Save” button near the bottom-left of the page as well and then you are done!

PHP WordPress Large background Importer Code

My wonderful client wanted me to add a .CSV importer that imports posts into a Custom Post Type(CPT) in large quantities of around 10k to 50k at a time.

What a standard WordPress site can handle

From my experience building plugins and importers for WordPress over the past 20 years, I have learned to expect trouble if you try to import anything more than 4k to 5k posts into WP at a time. The main requirement for the importer that I am building now is that it is able to import 20k posts in under 15 minutes. Not too difficult, but a challenge none-the-less because the average WP site will timeout after the first 4k posts on average. I also wanted to accomplish this goal without using any bloated PHP Que libraries or Worker Libraries, so I’ll be developing it completely from scratch, just like a grown up!

Step One, Break Large File into Smaller Chunks

The first thing I did while developing my large WP file import feature, was to handle the issue of having a huge .csv file with 20 thousand posts saved in it. What do you do? What I did was wrote a PHP script that reads in that huge file and breaks it up into manageable chunks and saves each chunk to its own separate .csv file. Kind of simple really, this is the easy part!

I created a file named file_split.php to handle this portion of the importer’s code. Here are the contents of that file:

<?php
$chunk_size = 2000;//size of ea. chunk file given in the no. of lines written to the file(excluding the first line with headings that will be at the top of ea. chunk file created).
$max_files = 0;//it will stop after writing this many files, set to 0 for no limmit
$u = $uploadpath;//”double_accounts-test.csv”;
//read a file line by line:
echo “<p>Opening $u to spit it into chunks……</p>”;
$file = fopen($u, “r”) or exit(“Unable to open $u!”);
//extract the first line from the file:
$first_line = fgets($file);
//Output a line of the file until the end is reached:
$i=0;
$ii=0;
$iii=0;
$chunk=$first_line;
while(!feof($file) &&($iii < $max_files||$max_files==0)) {
$i++;
$ii++;
$line = fgets($file);
//echo “$i) $line<br>”;
//add line to lines that get written to files:
$chunk .= $line;
//once we get to chunk_size lines, write a new file
if($ii == $chunk_size){
$iii++;
//echo “<h3>Chunk #$iii:</h3>”;
//echo $chunk;
//create dynamic filenames:
$fn = ABSPATH.”wp-content/plugins/YOUR-PLUGIN/temp/chunk$iii.csv”;
//echo “Writing file $fn….<br>”;
$fh = fopen($fn, ‘w’) or die(“can’t open $fn file to overwrite”);
fwrite($fh, $chunk);
fclose($fh);
$m = “<span style=’color:green’>Chunk Written to $fn</span><br>”;
//reset $ii var back to zero:
$ii = 0;
//empty the $chunk var and add first line to top of file content($chunk)
$chunk=$first_line;
//echo “<hr><span style=’text-align:center;color:orange;font-weight:bold;width:100%;margin-right:111px’>End File #$iii</span>$m<hr>”;
echo $m;
//add the file name to the fnames array:
$fnames[] = $fn;
}//end if $ii = $chunk_size
}
fclose($file);
//if $ii is greater than 0 and less then chunk_size, then there was a left over chunk at the end, so write it to a file too:
if($ii > 0 && $ii < $chunk_size){
$noof_files = $iii+1;
$fn = ABSPATH.”wp-content/plugins/YOUR-PLUGIN/temp/chunk$noof_files.csv”;
echo “Left over chunk of $ii lines remains, so writing it to final file named $fn….<br>”;
//echo “Writing Final file $fn….<br>”;
$fh = fopen($fn, ‘w’) or die(“can’t open $fn file to write final chunk!”);
fwrite($fh, $chunk);
fclose($fh);
echo “<h3 style=’color:green’>FINAL Chunk Written to $fn! DONE chunking $u file! Total files created: $noof_files</h3>”;
//add final file name to fnames array:
$fnames[] = $fn;
}else{
echo “Wow! We broke even with no left over chunk of less than $chunk_size lines, so finished writing files! Final file is $fn.<br>”;
}

?>

Put the file_split.php file up for now. We will include it in another file later when we have the rest of our import feature code written. You can test file_split.php easy enough by

Delete Entire Folder with PHP Code

Today, I had to write a block of pure PHP code to delete an entire folder full of files, sub-folders and more files from a server. This particular job was for a WordPress plugin, but the code I used is not specific to WordPress and can run just fine outside of WordPress or inside a WordPress plugin. The only requirement is an up to date PHP installation for this code to function as intended.

<?php

$path = ABSPATH.’wp-content/plugins/cultivate-crm/test’;
$dir_arr = parse_dir($path);
global $darr;

function parse_dir($path){
static $cntf = 0;
global $darr;
$d = dir($path);
while (false !== ($entry = $d->read())) {
$filepath = “{$path}/{$entry}”;
//Check whether the entry is a file or folder.:
$fof = $entry;
    if($fof != “.” && $fof != “..”) {
    $cntf++;
    $fof = $entry;
    $file_type = filetype($filepath);//get file type.
    $file_size = filesize($filepath);//get file size.
    echo “$cntf) $fof<br />Type: $file_type<br />Size: $file_size<hr />”;
    if($file_type==’dir’){$darr[]=$filepath;parse_dir($filepath);}else{echo “Deleting $filepath….<br>”;unlink($filepath);}
    }//end if is file etc.
}//end while going over files in excel_uploads dir.
}//end parse_dir PHP function by Ian L. of jafty.com

$c = count($darr);
echo “$c empty directories remain…deleting them now….<br />”;
$rdarr = array_reverse($darr);
foreach($rdarr as $dr){
    rmdir($dr);
    echo “Folder $dr deleted!<br />”;
}
//and finally remove the test folder:
$test_folder = ABSPATH.’wp-content/plugins/cultivate-crm/test’;
rmdir($test_folder);
echo “Folder $test_folder deleted!<br /><p style=’color:green’>Now all temporary folders have been removed from the server!</p>”;
?>

Make sure you set your path variable at the top of the script correctly and if you are not in WordPress, replace ABSPATH or set a new PHP constant named ABSPATH and it will work as it is. Since the above code deals with deleting files permanently from the server, I must insist that you use it at your own risk! Be careful because one wrong path entered into this function can destroy your entire file system if you don’t know what you are doing!

MailChimp API 3.0 Helper PHP Function

I’ve been doing a lot of work with the MailChimp API V. 3.0 over the past year while developing the Cultivate CRM WordPress Plugin and I created the following helper function that has made live a lot easier for me since I have to make probably a dozen calls to the API within this one plugin.

MailChimp API 3.0 Helper Function

<?php
function mailchimp_request($mcAPIkey,$url,$req_type,$json=''){
//send a HTTP POST, GET or PUT request with curl:
 $ch = curl_init($url);
 curl_setopt($ch, CURLOPT_USERPWD, 'user:' . $mcAPIkey);
 curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
 curl_setopt($ch, CURLOPT_TIMEOUT, 100);
 curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $req_type);//GET, PUT, POST
 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
 if($req_type!="GET")curl_setopt($ch, CURLOPT_POSTFIELDS, $json);//not needed for GET requests
 $result = curl_exec($ch);
 $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
 curl_close($ch);
 // echo "http code: $httpCode<br />";

$body = json_decode($result);
 //echo "successfully got list for Cultivate CRM!<br />result:<hr />$result<hr />";
 $results['code'] = $httpCode ;
 $results['result'] = $result;
 // store the status message based on response code
 if ($httpCode == 200) {//if no error occurred:
 return $results;
 } else {//else an error occurred:
 return false;
 }//end else an error occurred
 }//end mailchimp_request PHP function by Ian L. of Jafty.com

Calling mailchimp_request Function

Here is how you use the above mailchimp_request PHP function:

<?php
//Construct the MailChimp API URL:
$mcAPIkey = 'YOUR-MC-API-KEY';
$mcListID = 'YOUR-MC-LIST-ID';
$email= 'MEMBER_EMAIL@EMAIL.COM';
$memberID = md5(strtolower($email));
$dataCenter = substr($mcAPIkey,strpos($mcAPIkey,'-')+1);
$url = 'https://' . $dataCenter . '.api.mailchimp.com/3.0/lists/' . $mcListID . '/merge-fields/';    
//get the response:
$response = mailchimp_request($mcAPIkey,$url,'GET');
$httpcode = $response['code'];
$result = $response['result'];
$decoded_result = json_decode($result);
echo "http code: $httpcode<br>";
echo "Response data from getting address field data from MC API:
";
$merged_field_obj = $decoded_result->merge_fields;//should be an array of merged fields
print_r($merged_field_obj);
?>

MailChimp API

I’ve been doing a lot of work with the MailChimp API lately and figured it would be good to have a page dedicated to it here for future reference because it’s not very well documented online anywhere else… Hoping this will help some people who also have to use MailChimp’s API.

Understanding MailChimp Lists and the API

If you’ve ever dealt with MailChimp, you know that it is based on lists which can be segmented, grouped and managed all from the API as needed. Next I will demonstrate how to get a list of MailChimp lists and information on each list that exists.

Getting MailChimp List Information from API

Here’s the code I created to get all MailChimp Lists and information on each list from the MailChimp API using PHP code:

<h2>MailChimp List Info:</h2>
<?php
//Get API key :

$mcAPIkey =’Enter_your_own_API_key_HERE’;
//get info regarding MC lists
//use following url ‘https://usX.api.mailchimp.com/3.0/lists’

$dc = substr( $mcAPIkey, strpos( $mcAPIkey, ‘-‘ ) + 1 ); // datacenter, it is the part of your api key – us5, us8 etc
$args = array(
     ‘headers’ => array(
        ‘Authorization’ => ‘Basic ‘ . base64_encode( ‘user:’. $mcAPIkey )
    )
);

$response = wp_remote_get( ‘https://’.$dc.’.api.mailchimp.com/3.0/lists/’, $args );
$body = json_decode(wp_remote_retrieve_body($response));

$rcode = wp_remote_retrieve_response_code($response);
$tot_itms = $body->total_items;
echo “Response code was $rcode …$tot_itms items found!<br /><hr />”;
echo “<pre><code>”;
print_r($body);
echo “</code></pre><hr />”;
?>

The above code would return something like this for a MailChimp account that has one saved list:

MailChimp List Info:

Response code was 200 …1 items found!


stdClass Object
(
    [lists] => Array
        (
            [0] => stdClass Object
                (
                    [id] => xxxxxxxxx
                    [web_id] => xxxxxx
                    [name] => List Name
                    [contact] => stdClass Object
                        (
                            [company] => Company Name
                            [address1] => 1001 Your Road
                            [address2] => 
                            [city] => Adolphus
                            [state] => KY
                            [zip] => 42120
                            [country] => US
                            [phone] => 
                        )

                    [permission_reminder] => You signed up to learn more about how our team can better serve you.
                    [use_archive_bar] => 1
                    [campaign_defaults] => stdClass Object
                        (
                            [from_name] => First Last
                            [from_email] => your_email@domain.com
                            [subject] => 
                            [language] => en
                        )

                    [notify_on_subscribe] => 
                    [notify_on_unsubscribe] => 
                    [date_created] => 2018-02-25T02:01:38+00:00
                    [list_rating] => 0
                    [email_type_option] => 
                    [subscribe_url_short] => http://eepurl.com/dBXF4r
                    [subscribe_url_long] => https://yourcompany.us13.list-manage.com/subscribe?u=bf405c1xxxxxxxxb48df64c3&id=xxxxxxxxxx
                    [beamer_address] => us13-5df785xxxxx-xxx@inbound.mailchimp.com
                    [visibility] => pub
                    [double_optin] => 1
                    [marketing_permissions] => 
                    [modules] => Array
                        (
                        )

                    [stats] => stdClass Object
                        (
                            [member_count] => 3
                            [unsubscribe_count] => 0
                            [cleaned_count] => 0
                            [member_count_since_send] => 5
                            [unsubscribe_count_since_send] => 0
                            [cleaned_count_since_send] => 0
                            [campaign_count] => 2
                            [campaign_last_sent] => 
                            [merge_field_count] => 4
                            [avg_sub_rate] => 1
                            [avg_unsub_rate] => 0
                            [target_sub_rate] => 0
                            [open_rate] => 0
                            [click_rate] => 0
                            [last_sub_date] => 2018-04-28T20:26:38+00:00
                            [last_unsub_date] => 
                        )

                    [_links] => Array
                        (
                            [0] => stdClass Object
                                (
                                    [rel] => self
                                    [href] => https://us13.api.mailchimp.com/3.0/lists/edxxxxxxxxx
                                    [method] => GET
                                    [targetSchema] => https://us13.api.mailchimp.com/schema/3.0/Definitions/Lists/Response.json
                                )

                            [1] => stdClass Object
                                (
                                    [rel] => parent
                                    [href] => https://us13.api.mailchimp.com/3.0/lists
                                    [method] => GET
                                    [targetSchema] => https://us13.api.mailchimp.com/schema/3.0/Definitions/Lists/CollectionResponse.json
                                    [schema] => https://us13.api.mailchimp.com/schema/3.0/CollectionLinks/Lists.json
                                )

                            [2] => stdClass Object
                                (
                                    [rel] => update
                                    [href] => https://us13.api.mailchimp.com/3.0/lists/edxxxxxxxxx
                                    [method] => PATCH
                                    [targetSchema] => https://us13.api.mailchimp.com/schema/3.0/Definitions/Lists/Response.json
                                    [schema] => https://us13.api.mailchimp.com/schema/3.0/Definitions/Lists/PATCH.json
                                )

                            [3] => stdClass Object
                                (
                                    [rel] => batch-sub-unsub-members
                                    [href] => https://us13.api.mailchimp.com/3.0/lists/xxxxxxxxxx
                                    [method] => POST
                                    [targetSchema] => https://us13.api.mailchimp.com/schema/3.0/Definitions/Lists/BatchPOST-Response.json
                                    [schema] => https://us13.api.mailchimp.com/schema/3.0/Definitions/Lists/BatchPOST.json
                                )

                            [4] => stdClass Object
                                (
                                    [rel] => delete
                                    [href] => https://us13.api.mailchimp.com/3.0/lists/xxxxxxxxxxxx
                                    [method] => DELETE
                                )

                            [5] => stdClass Object
                                (
                                    [rel] => abuse-reports
                                    [href] => https://us13.api.mailchimp.com/3.0/lists/xxxxxxxx/abuse-reports
                                    [method] => GET
                                    [targetSchema] => https://us13.api.mailchimp.com/schema/3.0/Definitions/Lists/Abuse/CollectionResponse.json
                                    [schema] => https://us13.api.mailchimp.com/schema/3.0/CollectionLinks/Lists/Abuse.json
                                )

                            [6] => stdClass Object
                                (
                                    [rel] => activity
                                    [href] => https://us13.api.mailchimp.com/3.0/lists/xxxxxxxxx/activity
                                    [method] => GET
                                    [targetSchema] => https://us13.api.mailchimp.com/schema/3.0/Definitions/Lists/Activity/Response.json
                                )

                            [7] => stdClass Object
                                (
                                    [rel] => clients
                                    [href] => https://us13.api.mailchimp.com/3.0/lists/xxxxxxxxxxxxx/clients
                                    [method] => GET
                                    [targetSchema] => https://us13.api.mailchimp.com/schema/3.0/Definitions/Lists/Clients/Response.json
                                )

                            [8] => stdClass Object
                                (
                                    [rel] => growth-history
                                    [href] => https://us13.api.mailchimp.com/3.0/lists/xxxxxxxxxxxxxxxx/growth-history
                                    [method] => GET
                                    [targetSchema] => https://us13.api.mailchimp.com/schema/3.0/Definitions/Lists/Growth/CollectionResponse.json
                                    [schema] => https://us13.api.mailchimp.com/schema/3.0/CollectionLinks/Lists/Growth.json
                                )

                            [9] => stdClass Object
                                (
                                    [rel] => interest-categories
                                    [href] => https://us13.api.mailchimp.com/3.0/lists/xxxxxxxxxx/interest-categories
                                    [method] => GET
                                    [targetSchema] => https://us13.api.mailchimp.com/schema/3.0/Definitions/Lists/InterestCategories/CollectionResponse.json
                                    [schema] => https://us13.api.mailchimp.com/schema/3.0/CollectionLinks/Lists/InterestCategories.json
                                )

                            [10] => stdClass Object
                                (
                                    [rel] => members
                                    [href] => https://us13.api.mailchimp.com/3.0/lists/xxxxxxxxx/members
                                    [method] => GET
                                    [targetSchema] => https://us13.api.mailchimp.com/schema/3.0/Definitions/Lists/Members/CollectionResponse.json
                                    [schema] => https://us13.api.mailchimp.com/schema/3.0/CollectionLinks/Lists/Members.json
                                )

                            [11] => stdClass Object
                                (
                                    [rel] => merge-fields
                                    [href] => https://us13.api.mailchimp.com/3.0/lists/xxxxxxxxxx/merge-fields
                                    [method] => GET
                                    [targetSchema] => https://us13.api.mailchimp.com/schema/3.0/Definitions/Lists/MergeFields/CollectionResponse.json
                                    [schema] => https://us13.api.mailchimp.com/schema/3.0/CollectionLinks/Lists/MergeFields.json
                                )

                            [12] => stdClass Object
                                (
                                    [rel] => segments
                                    [href] => https://us13.api.mailchimp.com/3.0/lists/xxxxxxxxxxxx/segments
                                    [method] => GET
                                    [targetSchema] => https://us13.api.mailchimp.com/schema/3.0/Definitions/Lists/Segments/CollectionResponse.json
                                    [schema] => https://us13.api.mailchimp.com/schema/3.0/CollectionLinks/Lists/Segments.json
                                )

                            [13] => stdClass Object
                                (
                                    [rel] => webhooks
                                    [href] => https://us13.api.mailchimp.com/3.0/lists/xxxxxxxx/webhooks
                                    [method] => GET
                                    [targetSchema] => https://us13.api.mailchimp.com/schema/3.0/Definitions/Lists/Webhooks/CollectionResponse.json
                                    [schema] => https://us13.api.mailchimp.com/schema/3.0/CollectionLinks/Lists/Webhooks.json
                                )

                            [14] => stdClass Object
                                (
                                    [rel] => signup-forms
                                    [href] => https://us13.api.mailchimp.com/3.0/lists/xxxxxxxxxxxx/signup-forms
                                    [method] => GET
                                    [targetSchema] => https://us13.api.mailchimp.com/schema/3.0/Definitions/Lists/SignupForms/CollectionResponse.json
                                    [schema] => https://us13.api.mailchimp.com/schema/3.0/CollectionLinks/Lists/SignupForms.json
                                )

                            [15] => stdClass Object
                                (
                                    [rel] => locations
                                    [href] => https://us13.api.mailchimp.com/3.0/lists/xxxxxxxxxxx/locations
                                    [method] => GET
                                    [targetSchema] => https://us13.api.mailchimp.com/schema/3.0/Definitions/Lists/Locations/CollectionResponse.json
                                    [schema] => https://us13.api.mailchimp.com/schema/3.0/CollectionLinks/Lists/Locations.json
                                )

                        )

                )

        )

    [total_items] => 1
    [_links] => Array
        (
            [0] => stdClass Object
                (
                    [rel] => self
                    [href] => https://us13.api.mailchimp.com/3.0/lists
                    [method] => GET
                    [targetSchema] => https://us13.api.mailchimp.com/schema/3.0/Definitions/Lists/CollectionResponse.json
                    [schema] => https://us13.api.mailchimp.com/schema/3.0/CollectionLinks/Lists.json
                )

            [1] => stdClass Object
                (
                    [rel] => parent
                    [href] => https://us13.api.mailchimp.com/3.0/
                    [method] => GET
                    [targetSchema] => https://us13.api.mailchimp.com/schema/3.0/Definitions/Root/Response.json
                )

            [2] => stdClass Object
                (
                    [rel] => create
                    [href] => https://us13.api.mailchimp.com/3.0/lists
                    [method] => POST
                    [targetSchema] => https://us13.api.mailchimp.com/schema/3.0/Definitions/Lists/Response.json
                    [schema] => https://us13.api.mailchimp.com/schema/3.0/Definitions/Lists/POST.json
                )

        )

)

How to Count Iterations of a PHP Function

I find myself having to do this all the time! I need to know how many times a function is running and for some strange reason, I almost never remember how to do it correctly. That’s why I am posting this simple trick on my blog, so others can find it easily and so I can look at how I did it next time I need to do it again!

Here’s a simple example to illustrate the need here:

<?php
function runMe(){

$i = 0;

$i++;

echo “$i<br>”;

}

//If you were to execute the above function in a loop like this:

for($x=0;$x<10;$x++){

runMe();

}
?>

The output of the above would be:

1
1
1
1
1
1
1
1
1
1

Not what you wanted? or expected? Often we need it to output something like this instead:

1
2
3
4
5
6
7
8
9…

Static Variable to the Rescue

In comes what is know in PHP as Static Variables.  Here’s a quick code example that should make you understand how they work:

<?php
function countRuns(){

static $c = 0;

$c++;

echo “$c<br />”;

}

//Then, Executing the countRuns function in a for loop will increment the number printed ea. time:

for($x=0;$x<10;$x++){

countRuns();

}
?>

If you were to run the above tested code in your browser you would get the following output:

1
2
3
4
5
6
7
8
9
10

That’s what I was looking for! okay, so that’s how it’s done. I hope this helps someone else besides me.

 

 

Learning WooCommerce API Manger

What is WooCommerce API Manger?

WooCommerce API Manger is used to secure your software with API License Key activation, deactivation and automatic updates of WordPress plugins and/or themes. However it can also be used for some applications outside of WordPress as well, but we will be talking mostly about how to use it with a custom WordPress plugin.

Prerequisites

You need to at least make sure you have at least PHP Version 7.00. Here’s how you can check from the linux command line:

php -v

Type the above command and press enter and you should get results that look similar to this:

php-v

 

Alternative Solution to WooCommerce API Manager

You may have noticed this article isn’t complete. That is because I decided to go with a different system for selling WordPress plugins on my site with API keys, WooCommerce didn’t provide the level of support I needed and their documentation was poor. I discovered Easy Digital Downloads which was much simpler to deploy. Easy Digital Downloads(EDD) in combination with the EDD Software Licensing add-on and the EDD Instant Updater for plugins that I developed myself, became the ultimate solution for selling WordPress plugins outside of WordPress.org. If anyone is interested in how I did it, email me at linian11@yahoo.com and I can assist you.

 

 

How to Keep Custom wp-admin Nav Menu Items Open and Sub-menu Items Highlighted When Clicked

So, I spent a good 3+ hours doing what I thought would be a simple task today. I was working on a custom WordPress plugin named “Cultivate” for a client and made some additions to the admin menu in wp-admin. I added a Main menu item with the plugin name on it and there were 8 sub-menu items under it with links to various admin pages for the plugin.

The Problem

The problem is that the cultivate main menu item should stay open when someone clicks on one of the sub-menu items, but doesn’t. So let me demonstrate with a couple of images of the admin area in WordPress. Here’s an image of how the admin nav looks just after I clicked on the main menu item “Cultivate”. As you can see in the below image, the Cultivate menu is open and you can see the 8 submenu items below it. When it is closed all you see if the “Cultivate” menu item, but here it is open:

MainMenu

Okay, that’s all good and fine. You can see the sub-menu items and you can see “Cultivate” main menu item is highlighted because that page is now open. So, everything looks great! However, below is another image of how it looks after clicking on one of the sub-menu links below the “Cultivate” main menu item. In the next image, I have just clicked on the “Pods” sub-menu item under “Cultivate”:

Submenu

Now do you see the problem? Not only is the main “Cultivate” menu item closed, but the “Pods” sub-menu item isn’t even present. Pods should be visible and highlighted which is not possible without the sub-menu being opened! So, after hours of research and coding, I finally figured out how to make this custom wp-admin menu work as it should. Here is how it looks after clicking on “Pods” under “Cultivate” after I added my code which I’ll show you shortly:

OpenSubmenu

See how in the above image, the main “Cultivate” menu remained open and the “Pods” sub-menu item is highlighted after going to the custom admin page?  That’s how it should work! That’s how it works after adding my custom PHP and jQuery code shown below.

The Solution

The solution is a block of PHP code added to your plugin file or functions.php file and a custom jQuery script for each sub-menu item. It might sound like a lot to make a separate .js file for each sub-menu item, but you only have to change one word in each file, so not a huge deal really.

The PHP Code

Here’s the PHP code you can add to your main plugin file or to functions.php if you’re adding it to a theme:

First we will just add the following PHP code, then below that you can see the complete script, but you’ll want to run this partial script first to get your page names for the if statements to function properly:

add_action(‘admin_head’, ‘jafty_set_open_menu’);
//Open the correct menu for taxonomy
function jafty_set_open_menu(){
    $screen = get_current_screen();
    $screenname = $screen->base;
    $screentax = $screen->taxonomy;
    $posttype = $screen->post_type;
    //uncomment next line to write name of page, post type and taxonomy to log:
    log_debug(“\n\nOpened page: $screenname\nOpened Taxonomy: $screentax\nPost Type: $posttype\n\n”);

}//end jafty_set_open_menu function by Ian L. of Jafty.com

//Custom function to write data to log file:
function log_debug($log){
    $pdir = plugin_dir_path( __FILE__ );//gets the directory of current file with trailing slash whether inside plugin or not!
    $myFile = $pdir.”my_log.txt”;
    $fh2 = fopen($myFile, ‘a’) or die(“can’t open file to append”);
    fwrite($fh2, $log);
    fclose($fh2);
}//End log_debug PHP function

Now with the above code installed in your main plugin file or functions.php file, you’ll also need to add a file to your plugin’s main directory named my_log.txt and give it write permissions. This way it will write the page names and any possible taxonomy or post types you need to insert later in the script to the log file when you click on the admin nav links. So you click on a sub link, then see what was written to the log file. Repeat for ea. sub link. and write down the values written to the log file. Then you use what you wrote down to plan your PHP if statements correctly according to the page, post type and taxonomies used.

With that said, here’s the entire script that worked for my Main menu item with 8 sub menu items below it”

//code to keep main menu open if submenu items are clicked:
add_action(‘admin_head’, ‘jafty_set_open_menu’);
//Open the correct menu for taxonomy
function jafty_set_open_menu(){
    $screen = get_current_screen();
    $screenname = $screen->base;
    $screentax = $screen->taxonomy;
    $posttype = $screen->post_type;
    //comment out next line to stop writing the page and taxonomy names to log file:
    log_debug(“\n\nOpened page: $screenname\nOpened Taxonomy: $screentax\nPost Type: $posttype\n”);
    //next will be a separate block of code for ea. submenu item in the main menu to trigger the proper .js file to manipulate the nav menu properly for ea.:
    //code for the “People” submenu item:
    if($screenname === ‘edit’ && $posttype === ‘people’){
        wp_enqueue_script( ‘open-menu-parent_people’, plugins_url(‘js/open_admin_menu_people.js’, __FILE__ ), array(‘jquery’) );
    }    
    //code for the “Pods” submenu item:
    if($screenname === ‘edit-tags’ && $screentax === ‘pods’){
        wp_enqueue_script( ‘open-menu-parent_pod’, plugins_url(‘js/open_admin_menu_pod.js’, __FILE__ ), array(‘jquery’) );
    }
    //code for the “Touchpoints” submenu item:
    if($screenname === ‘edit’ && $posttype === ‘touchpoints’){
        wp_enqueue_script( ‘open-menu-parent_touchpoints’, plugins_url(‘js/open_admin_menu_touchpoints.js’, __FILE__ ), array(‘jquery’) );
    }    
    //code for the “Campaigns” submenu item:
    if($screenname === ‘edit-tags’ && $screentax === ‘campaigns’){
        wp_enqueue_script( ‘open-menu-parent_campaigns’, plugins_url(‘js/open_admin_menu_campaigns.js’, __FILE__ ), array(‘jquery’) );
    }    
    //code for “Forms” submenu item:
    if($screenname === ‘cultivate_page_forms_admin_pg’){
        wp_enqueue_script( ‘open-menu-parent_forms’, plugins_url(‘js/open_admin_menu_forms.js’, __FILE__ ), array(‘jquery’) );
    }    
    //code for “Custom Fields” submenu item:
    if($screenname === ‘cultivate_page_custom_fields_admin_pg’){
        wp_enqueue_script( ‘open-menu-parent_custom_fields’, plugins_url(‘js/open_admin_menu_custom_fields.js’, __FILE__ ), array(‘jquery’) );
    }        
    //code for “MailChimp Admin” submenu item:
    if($screenname === ‘cultivate_page_mailchimp_admin_pg’){
        wp_enqueue_script( ‘open-menu-parent_mailchimp_admin’, plugins_url(‘js/open_admin_menu_mailchimp_admin.js’, __FILE__ ), array(‘jquery’) );
    }        
    
}//end jafty_set_open_menu function by Ian L. of jafty.com

Now the jQuery Code:

You’ll need one .js file with the following contents for each sub-menu item you have in your admin nav. All you have to change in the text that reads “MailChimp Admin” in my example below to the link text of the sub-menu item for ea. separate .js file you use:

(function($) {
    // Close all the other parent menus
    $(‘.wp-has-current-submenu’).removeClass(‘wp-has-current-submenu’);

    // Open your specific parent menu
    $(‘.toplevel_page_cultivate_settings_pg’)//set to menu name for cultivate menu
        .removeClass(‘wp-not-current-submenu’)
        .addClass(‘wp-has-current-submenu wp-menu-open’);
        

            $(‘.toplevel_page_cultivate_settings_pg .wp-submenu-wrap’).find(‘li’).each(function(){
                 var current = $(this);
                    if(current.text()==’MailChimp Admin’){
                        current.addClass(‘current’);
                    }
        });

}(jQuery));

Summary

Nice huh? It took me forever to get this to work, but with the above code you should be able to do it in minutes.

Getting Unterminated String Literal Error When Using Newlines In JavaScript Variable

Nothing like a good Unterminated String Literal Error to fog up your day huh? Well I found a neat little trick to squash this nasty bug with JavaScript today!

The Problem

The problem in my case is that I wanted to place the contents of a PHP variable, filled with data from a database table, into a JavaScript variable which I thought could be easily done like in  the following JavaScript code example:

var sc = “<?php echo $email_content; ?>”;

WRONG! I discovered that when the PHP variable holds text with newline characters in it, like you’ll often encounter when retrieving data from a database or from a textarea, or even a file, JavaScript throws the nasty old “Unterminated string literal” error! I’ve learned to hate this general JavaScript error over the years, but today, I stumbled upon a small, yet useful, trick that makes this error go away in cases like this.

The Solution:

Believe it or not, the solution is as simply as replacing quotes in the JavaScript code with backticks. If you don’t know what a backtick is, read on, otherwise your JavaScript code should look like this after replacing quotes with backticks:

var sc = `<?php echo $email_content; ?>`;

What is a Backtick?

A backtick is the little character that looks like a slanted single quotation mark that normally calls the key above the tab key home on a normal English keyboard. Otherwise known as the Marigold key, grave accent or backquote, the character is a tricky one simply because no one knows what to call it. However, in the land of computer programming, it is commonly referred to as the backtick character, so that’s what we’ll call it. Cool? Okay!