The post Unban GoDaddy / MediaTemple WordPress Plugins appeared first on Justin Silver.
]]>GoDaddy managed WordPress hosting, and by ownership MediaTemple as well, prevents some plugins from being activated on website. Some of these may make some sense if the conflict with caching, etc, but I feel like including a list is good enough and if you want to install that plugin on the website you’re paying for, so be it. There are several plugins that are banned in the list that I regularly use in my website development, for example Gravity Forms.
If you try to enable this plugin on a site hosted on GoDaddy or MediaTemple rather than activating, you get an error that says:
Not Available: This plugin is not allowed on our system due to performance, security, or compatibility concerns. Please contact our support with any questions.
There is a Must-Use Plugin called gd-system-plugin
that includes a class called GD_System_Plugin_Blacklist
. This class hooks into the plugin installer code to hide links, show errors, and otherwise prevent some plugins from being installed and activated. The way that it does this is by calling the GD_System_Plugin_Blacklist->get_blacklist()
method. This method tries to get a value for get_site_transient( 'gd_system_blacklist' )
and if it’s empty, then fetches the list from a URL and caches it.
Luckily there is a filter for pre_site_transient_*
so you can short circuit the whole thing.
We can’t just return an empty array here, because there is a check for empty()
, but we can return our own “list” of banned plugins instead of fetching it from a URL (or even the transient cache if it was already set). Plugin code is below, but the filter could just as easily be added to another plugin, functions.php in your theme, or another mu-plugin.
/* Plugin Name: Unban Goddady/MediaTemple Plugins Description: Allow plugins banned by GoDaddy to be used on your site */ add_filter( 'pre_site_transient_gd_system_blacklist', function(){ return array( array( 'name'=>'godaddy', 'minVersion'=>0, 'maxVersion'=>1 ) ); } );
This is the list of plugins banned by GoDaddy as of the writing of this post.
6scan-backup 6scan-protection adminer adsense-click-fraud-monitoring all-in-one-seo-pack all-in-one-wp-migration aspose-cloud-ebook-generator aspose-doc-exporter backupwordpress backwpup broken-link-checker contextual-related-posts custom-contact-forms easy-coming-soon ezpz-one-click-backup fancybox-for-wordpress favicon-by-realfavicongenerator fuzzy-seo-booster google-analytics-for-wordpress google-sitemap-generator google-xml-sitemaps-with-multisite-support gravityforms inboundio-marketing iwp-client jr-referrer leads liveforms miwoftp mp3-jplayer newsletter nextgen-gallery pagelines photo-gallery php-event-calendar platform pluscaptcha pods portable-phpmyadmin ptengine-real-time-web-analytics-and-heatmap quick-cache referrer-wp schram-kljsdfjkl seo-alrp sgcachepress similar-posts statpress synthesis tdwordcount the-codetree-backup toolspack ultimate-member updraft w3-total-cache wordpress-beta-tester wordpress-gzip-compression wordpress-popular-posts wordpress-seo work-the-flow-file-upload wp-all-import wp-business-intelligence-lite wp-cache wp-cachecom wp-copysafe-pdf wp-copysafe-web wp-engine-snapshot wp-fast-cache wp-fastest-cache wp-file-cache wp-phpmyadmin wp-postviews wp-power-stats wp-slimstat wp-super-cache wp-ultimate-csv-importer wpengine-common wponlinebackup wptouch wysija-newsletters yet-another-featured-posts-plugin yet-another-related-posts-plugin
The post Unban GoDaddy / MediaTemple WordPress Plugins appeared first on Justin Silver.
]]>The post ACF: Link to Specific Tab appeared first on Justin Silver.
]]>Advanced Custom Fields, or ACF, provides a great user interface for defining and laying out custom fields for Posts, Pages, Custom Post Types, Options, Users and more in WordPress. The Tab Field provides an easy way to clean up your user interface by seperating different field inputs into different tabs, making it easier for your users to find what they are looking for.
One feature that is missing from the Tab field however is the ability to link directly to a specific tab from another page. We can work around this with some pretty straightforward JavaScript. In short when the acf “ready” event fires, we want to look at all the tab buttons, and if any match our URL hash (in lowercase with spaces replaced with hyphens) then we “click” that link, loading the appropriate tab. Subsequently we can update the hash in the address bar any time a new tab is clicked making it easy for uses to bookmark the page or send the link to another user.
If your tab name is “Options Tab 1” the link to it would be something like edit.php?post_type=my-post-type#options-tab-1
.
(function($){ // run when ACF is ready acf.add_action('ready', function(){ // check if there is a hash if (location.hash.length>1){ // get everything after the # var hash = location.hash.substring(1); // loop through the tab buttons and try to find a match $('.acf-tab-wrap .acf-tab-button').each(function(i, button){ if (hash==$(button).text().toLowerCase().replace(' ', '-')){ // if we found a match, click it then exit the each() loop $(button).trigger('click'); return false; } }); } // when a tab is clicked, update the hash in the URL $('.acf-tab-wrap .acf-tab-button').on('click', function($el){ location.hash='#'+$(this).text().toLowerCase().replace(' ', '-'); }); }); })(jQuery);
The post ACF: Link to Specific Tab appeared first on Justin Silver.
]]>The post Admin Columns Pro SSL Upgrade Fix appeared first on Justin Silver.
]]>I use the Admin Columns Pro plugin on several of my WordPress site to easily customize the layout in my admin tables. As it is a premium plugin updates to it are not hosted on the WordPress repository but rather come from their own private repository. This was working fine until a recently when I started getting errors during the upgrade process. It seems as though the SSL request was to www.admincolumns.com
but for some reason the server was responding with a wildcard cert for *.wpengine.com
– their hosting provider.
I opened a ticket on their support site and while helpful, unfortunately they were not able to come to a resolution. The error during the plugin update reads like the following.
Enabling Maintenance mode…
Updating Plugin Admin Columns Pro (1/1)
Downloading update from https://www.admincolumns.com?wc-api=software-licence-api&request=plugindownload&licence_key=XXXXXXXXXXX&plugin_name=admin-columns-pro…
An error occurred while updating Admin Columns Pro: Download failed. SSL: certificate subject name '*.wpengine.com' does not match target host name 'www.admincolumns.com'
Disabling Maintenance mode…
I was never able to determine the root issue other than it likely likes with WP Engine. The behavior is not consistent between environments, for example from my Mac running OSX Yosemite I can can use curl to load the Admin Columns Pro site via curl
:
> curl -v https://www.admincolumns.com * Adding handle: conn: 0x7fa2cc004000 * Adding handle: send: 0 * Adding handle: recv: 0 * Curl_addHandleToPipeline: length: 1 * - Conn 0 (0x7fa2cc004000) send_pipe: 1, recv_pipe: 0 * About to connect() to www.admincolumns.com port 443 (#0) * Trying 178.79.179.38... * Connected to www.admincolumns.com (178.79.179.38) port 443 (#0) * TLS 1.0 connection using TLS_RSA_WITH_AES_128_CBC_SHA * Server certificate: www.admincolumns.com * Server certificate: RapidSSL CA * Server certificate: GeoTrust Global CA > GET / HTTP/1.1 > User-Agent: curl/7.30.0 > Host: www.admincolumns.com > Accept: */* > < HTTP/1.1 200 OK
But the same request did not work from my CentOS 5 servers:
[root@dev1 ~]# curl -v https://www.admincolumns.com * About to connect() to www.admincolumns.com port 443 (#0) * Trying 178.79.179.38... connected * Connected to www.admincolumns.com (178.79.179.38) port 443 (#0) * successfully set certificate verify locations: * CAfile: /etc/pki/tls/certs/ca-bundle.crt CApath: none * SSLv3, TLS handshake, Client hello (1): * SSLv3, TLS handshake, Server hello (2): * SSLv3, TLS handshake, CERT (11): * SSLv3, TLS handshake, Server finished (14): * SSLv3, TLS handshake, Client key exchange (16): * SSLv3, TLS change cipher, Client hello (1): * SSLv3, TLS handshake, Finished (20): * SSLv3, TLS change cipher, Client hello (1): * SSLv3, TLS handshake, Finished (20): * SSL connection using AES256-SHA * Server certificate: * subject: serialNumber=dzc7avuEuqhZCEL82HF5aqoCQMgtwixa; OU=GT41552380; OU=See www.rapidssl.com/resources/cps (c)14; OU=Domain Control Validated - RapidSSL(R); CN=*.wpengine.com * start date: 2014-04-17 12:42:18 GMT * expire date: 2018-05-19 17:27:48 GMT * subjectAltName does not match www.admincolumns.com * Closing connection #0 * SSLv3, TLS alert, Client hello (1): * SSL peer certificate or SSH remote key was not OK curl: (51) SSL peer certificate or SSH remote key was not OK
I noticed that the CentOS machines were using SSLv3
, whereas my Mac was using TLS
. I was then able to recreate the issue on my Mac by forcing curl
to use SSLv3
.
> curl -v -sslv3 https://www.admincolumns.com * Adding handle: conn: 0x7fae0b004000 * Adding handle: send: 0 * Adding handle: recv: 0 * Curl_addHandleToPipeline: length: 1 * - Conn 0 (0x7fae0b004000) send_pipe: 1, recv_pipe: 0 * About to connect() to www.admincolumns.com port 443 (#0) * Trying 178.79.179.38... * Connected to www.admincolumns.com (178.79.179.38) port 443 (#0) * SSL certificate problem: Invalid certificate chain * Closing connection 0 curl: (60) SSL certificate problem: Invalid certificate chain More details here: http://curl.haxx.se/docs/sslcerts.html
With the SSL certificate for the request being invalid and the server not being in my control, the only option is to disable the SSL certificate verification in WP_Http
. This is accomplished by setting a key in its configuration array called sslverify
to false
. We can do this by hooking into the http_request_args
filter, checking the URL that it is loading, and disabling the verification for Admin Columns Pro.
add_filter( 'http_request_args', 'fix_acp_plugin_update', 10, 2 ); function fix_acp_plugin_update( $r, $url ){ $starts_with = 'https://www.admincolumns.com?wc-api=software-licence-api&request=plugindownload'; // if the url starts with ^ then don't verify SSL if ( 0 === strpos( $url, $starts_with ) ){ $r['sslverify'] = false; } return $r; }
Et voila! The plugin is now able to update successfully.
Enabling Maintenance mode…
Updating Plugin Admin Columns Pro (1/1)
Admin Columns Pro updated successfully. Show Details.
Disabling Maintenance mode…
All updates have been completed.
The post Admin Columns Pro SSL Upgrade Fix appeared first on Justin Silver.
]]>The post Fix Backslashes in Advanced Custom Fields appeared first on Justin Silver.
]]>There is a somewhat serious bug in Advanced Custom Fields 5.0 and its handling of backslashes, at least on my WordPress installs. The problem is that the slashes are stripped, but after the data has been serialized resulting in a mismatch between the declared string length in the serialized data, and it’s actual value length.
Imagine for example a Validated Field that uses a regular expression to check that the value is a digit. The validation pattern would look something like \d
for our example. When this is serialized, it’s length is 2
and the data looks like s:2:"\d"
. The problem is that when this is represented as a string to insert into the database, the backslash just escapes the d
and what gets saved to the database is s:2:"d"
.
Now we have a much bigger problem than a missing backslash – because the declared length is 2
but the length of the string "d"
is 1
, the serialized data can no longer be loaded from the database and all the field configuration values are lost.
This occurs during the upgrade process from ACF 4 to 5, as well as when saving a field with a backslash as part of its configuration. The field will default back to the “Text” field type in the UI, and the settings will be unloadable in the database. It is possible to manually fix this data by adding the missing \
characters back in, but this is going to be tedious work depending on how many fields are affected and the complexity. Even when fixed manually the field will break again the next time it is saved.
A bug has been listed on the ACF support site.
The following code attempts to correct this behavior, the following code attempts to use the filters content_save_pre
and acf/get_valid_field
to double slash any single slashes found in the configuration so that they are saved correctly to the database.
class ACF5_Slash_Fix { function __construct(){ // bug fix for acf with backslashes in the content. add_filter( 'content_save_pre', array( $this, 'fix_post_content' ) ); add_filter( 'acf/get_valid_field', array( $this, 'fix_upgrade' ) ); } function fix_upgrade( $field ){ // the $_POST will tell us if this is an upgrade $is_5_upgrade = isset( $_POST['action'] ) && $_POST['action'] == 'acf/admin/data_upgrade' && isset( $_POST['version'] ) && $_POST['version'] == '5.0.0'; // if it is an upgrade recursively fix the field values if ( $is_5_upgrade ){ $field = $this->do_recursive_slash_fix( $field ); } return $field; } function fix_post_content( $content ){ global $post; // are we saving a field group? $is_field_group = get_post_type() == 'acf-field-group'; // are we upgrading to ACF 5? $is_5_upgrade = isset( $_POST['action'] ) && $_POST['action'] == 'acf/admin/data_upgrade' && isset( $_POST['version'] ) && $_POST['version'] == '5.0.0'; // if we are, we need to check the values for single, but not double, backslashes and make them double if ( $is_field_group || $is_5_upgrade ){ $content = $this->do_slash_fix( $content ); } return $content; } function do_slash_fix( $string ){ return preg_match( '~(?<!\\\\)\\\\(?!\\\\)~', $string )? str_replace('\\', '\\\\', $string ) : $string; } function do_recursive_slash_fix( $array ){ // loop through all levels of the array foreach( $array as $key => &$value ){ if ( is_array( $value ) ){ // div deeper $value = $this->do_recursive_slash_fix( $value ); } elseif ( is_string( $value ) ){ // fix single backslashes to double $value = $this->do_slash_fix( $value ); } } return $array; } } new ACF5_Slash_Fix();
This fix is also included in the Validated Field plugin available in the WordPress repository. It offers additional enhancements as well to support field validation, read-only values, unique key, and more.
The post Fix Backslashes in Advanced Custom Fields appeared first on Justin Silver.
]]>The post W3 Total Cache Fragment Caching in WordPress appeared first on Justin Silver.
]]>W3 Total Cache, also known as W3TC, is a very powerful caching plugin for WordPress which is notoriously slow without tuning. For guest users much of your content will typically be static which makes serving up cached content to these users a pretty good idea. But what if you want part of the page to be be dynamic while most of the page is cached? Fragment caching to the rescue.
First you will need to make sure W3TC is properly configured for fragment caching. To enable it, from your WordPress Admin, visit Performance > General Settings
and look under the Page Cache
section. Make sure that the Page cache method
is set to Disk: Basic
and save your settings.
Next visit Performance > Page Cache
and scroll down to the Advanced
section. Here you will need to check the box next to Late initialization
to enable it. Notice the help text under it:
Enables support for WordPress functionality in fragment caching for the page caching engine. Use of this feature may increase response times.
For security reasons W3TC requires that you create a constant named W3TC_DYNAMIC_SECURITY
and pass it to the sections of code you would like it to ignore (and execute!). An obvious choice for this is wp-config.php
, but you might also have success with /wp-content/mu-plugins
, a custom plugin, your theme’s functions.php
, or even inline before your fragment – though the latter isn’t really recommended. You can set this to a hardcoded value, or use something random for each request.
define( 'W3TC_DYNAMIC_SECURITY', 'SOME_SECURE_STRING_YOU_CREATE' );
The final step is to update your template files to indicate where you want W3TC to *not* cache the page. This is done by using an HTML comment called `mfunc
`. You pass it your W3TC_DYNAMIC_SECURITY
and include any PHP you want to execute (without <?php>
tags!).
<!--mfunc <?php echo W3TC_DYNAMIC_SECURITY; ?> --> echo 'The time is '.date( 'H:i:s', time() ); <!--/mfunc <?php echo W3TC_DYNAMIC_SECURITY; ?> -->
The post W3 Total Cache Fragment Caching in WordPress appeared first on Justin Silver.
]]>The post ACF validate_value Filter With post_ID appeared first on Justin Silver.
]]>Advanced Custom Forms Pro 5.0 is out, and it contains a major overhaul to the way that custom fields are handled. It also had a major impact on the way that third-party add-ons were written to extend ACF functionality which meant that it was considerable work to refactor my Validated Fields plugin to support the new architecture. The new architecture is definitely superior in my opinion and has some great new filters that we can leverage such as acf/validate_value
and its siblings acf/validate_value/type={$field_type}
, acf/validate_value/name={$field_name}
, acf/validate_value/key={$field_key}
.
Unfortunately this filter does not have the post_ID
available to it, which greatly limits the range of things we can do with it. To work around this I found that by inserting a field named acf[post_ID]
into the editor form – and the “acf” part is critical – it would be picked up and submitted with the rest of the form values. This meant that it would be available in the $_POST
, really opening up the possibilities.
// use a unique value to prevent conflicts with other ACF fields define( 'MY_ACF_FORM_VALUES', 'MY_ACF_FORM_VALUES' ); // add the post_ID to the acf[] form function my_edit_form_after_editor( $post ){ print( "<input type='hidden' name='acf[%1$s][post_ID]' value='%2$d'/>", MY_ACF_FORM_VALUES, $post->ID ); } add_action( 'edit_form_after_editor', 'my_edit_form_after_editor' ); // use the post_ID in your validation function function my_validate_value( $valid, $value, $field, $input ) { $post_id = $_POST['acf'][MY_ACF_FORM_VALUES]['post_ID']; // more code! return $valid; } add_filter( "acf/validate_value", 'my_validate_value', 10, 4 );
The rest of the my_validate_value()
will be up to you.
Why reinvent the wheel? The above code is already included in Validated Field for ACF available in the WordPress repository.
The post ACF validate_value Filter With post_ID appeared first on Justin Silver.
]]>The post Sort by Featured Image in WordPress appeared first on Justin Silver.
]]>Featured Images on a WordPress page or post can make it easy to use a single image to represent the content of your page. Given that they look better, why not feature them at the top of your content, while still preserving the rest of your ORDER BY
? The answer is that it’s not terribly straightforward but you do have a couple of options. We can make use of the “_thumbnail_id” meta_key that is stores the attachment ID of the featured image, but as it is missing for pages and posts that don’t have a featured image they are omitted from The Loop entirely, which is not what we want.
The most robust way to sort your posts by featured image while not interfering with your existing ORDER BY
is to plug into the filters for posts_fields_request
, posts_join_request
, and posts_orderby_request
. The SQL that is going to be run passes through these filters in pieces, allowing us to modify it – and make sure that pages and posts without featured images are last! Since we don’t want to modify every WP_Query
, presumably just some of them, we can add another argument to handle this for us – we’ll call it featured_image_sort
.
class Featured_Image_Sort { public static function init(){ add_filter( 'pre_get_posts', array( __CLASS__, 'pre_get_posts' ) ); } public static function pre_get_posts( $query ){ if ( $query->query_vars['featured_image_sort'] ){ add_filter( 'posts_fields_request', array( __CLASS__, 'posts_fields_request' ) ); add_filter( 'posts_join_request', array( __CLASS__, 'posts_join_request' ) ); add_filter( 'posts_orderby_request', array( __CLASS__, 'posts_orderby_request' ) ); } return $query; } public static function posts_fields_request( $select ){ global $wpdb; return "{$select}, IF ( _has_featured_image.meta_value IS NULL, 0, 1 ) AS has_featured_image "; } public static function posts_join_request( $join ){ global $wpdb; return "{$join}\tLEFT JOIN {$wpdb->postmeta} AS _has_featured_image ON _has_featured_image.post_id = {$wpdb->posts}.ID and _has_featured_image.meta_key = '_thumbnail_id'"; } public static function posts_orderby_request( $orderby ){ return "has_featured_image DESC, {$orderby}"; } } Featured_Image_Sort::init();
I like to keep things flexible, so if you want to add this to every query I would use a hook to pre_get_posts
higher than a priority 10 to set the featured_image_sort
flag. This is also an easy way to quickly test your code.
add_filter( 'pre_get_posts', 'sort_featured', 1 ); function sort_featured( $query ){ $query->query_vars['featured_image_sort'] = true; return $query; }
If you want to apply the sort to specific queries you would pass this flag as an argument to WP_Query
as you would any other argument.
$args = array( 'orderby' => 'meta_value', 'meta_key' => 'some_meta_key', 'featured_image_sort' => true ); $query = new WP_Query( $args );
The post Sort by Featured Image in WordPress appeared first on Justin Silver.
]]>The post Using WP Better Emails with WooCommerce Email Templates appeared first on Justin Silver.
]]>On one site that I run there are several different functions, primarily provided by disparate plugins with custom actions/filters, that send emails both to site administrators and customers. For most emails that are sent from WordPress itself and several of the plugins, the content type is set to plain/text
allowing me to use WP Better Emails to wrap content in an easily edited header and footer. WooCommerce on the other hand proved to be a little more tricky – there is the option to send emails via plain/text
, but they definitely lack valuable formatting when displaying order information to a customer. This is further compounded if you insert any custom HTML into the content.
Conceptually all that needs to be done is to first remove the WooCommerce email header and footer, and second have WP Better Emails process it even though it has a content type of text/html
. After checking out the code for both of the plugins in question, I devised a way to make it work.
If you view the source of any of the WooCommerce email templates (at least the default ones) you will see a line for <?php do_action( 'woocommerce_email_header', $email_heading ); ?>
and <?php do_action( 'woocommerce_email_footer' ); ?>
which are the hooks that WooCommerce itself uses to insert the header and footer contents. By setting our own functions to these hooks earlier and later than the WooCommerce priorities, we can capture all of the content and hide it with ob_start()
and ob_get_clean() to capture and clean the output buffer contents. We are also setting a filter to return true any time the woocommerce_email_header
action is run.
Common sense says that we could just unhook all functions from these actions, however in my testing I found that some other, non-UI, logic was being performed in some of the attached functions. This method allows the code to run, but prevents the output from being inserted into the generated email.
Next we need to apply the email template that WP Better Emails did not, because the content type was wrong – text/html and not text/plain. This is actually a good thing because we also want to avoid some of the formatting that WP Better Emails applies to the typical plain text email content. We can do this using the global $wp_better_emails
and hooking into phpmailer_init
after most of the work has been done – priority 20 works fine.
This example uses anonymous function which require PHP 5.3+, however you could also use create_function()
.
// Determine if it's an email using the WooCommerce email header add_action( 'woocommerce_email_header', function(){ add_filter( "better_wc_email", "__return_true" ); } ); // Hide the WooCommerce Email header and footer add_action( 'woocommerce_email_header', function(){ ob_start(); }, 1 ); add_action( 'woocommerce_email_header', function(){ ob_get_clean(); }, 100 ); add_action( 'woocommerce_email_footer', function(){ ob_start(); }, 1 ); add_action( 'woocommerce_email_footer', function(){ ob_get_clean(); }, 100 ); // Selectively apply WPBE template if it's a WooCommerce email add_action( 'phpmailer_init', 'better_phpmailer_init', 20 ); function better_phpmailer_init( $phpmailer ){ // this filter will return true if the woocommerce_email_header action has run if ( apply_filters( 'better_wc_email', false ) ){ global $wp_better_emails; // Add template to message $phpmailer->Body = $wp_better_emails->set_email_template( $phpmailer->Body ); // Replace variables in email $phpmailer->Body = apply_filters( 'wpbe_html_body', $wp_better_emails->template_vars_replacement( $phpmailer->Body ) ); } }
Your WP Better Emails templates should now be applied to your WooCommerce emails.
The post Using WP Better Emails with WooCommerce Email Templates appeared first on Justin Silver.
]]>The post Prepare IN and NOT IN Statements in WordPress appeared first on Justin Silver.
]]>Using some type of prepared statement protects you against SQL injection attacks when you need to interact with the database using parameters passed in from the client side. Depending on the library you are using, how you do this will vary slightly, however when working directly with the database in WordPress it’s best to use the $wpdb
global object to abstract the actual interface. The deprecated mysql_*
extensions are still officially being used, however as of release of WordPress 3.9 when used in conjunction with PHP 5.5, you bumped over to the newer and improved mysqli
. Either way, you can use $wpdb->prepare( $statement, $arg1, $arg2... )
and the WordPress API will handle the details of the implementation.
This is an example of code that is susceptible to a SQL injection.
global $wpdb; // is this really a numeric ID? $id = $_POST['id']; $sql = "SELECT post_name FROM {$wpdb->posts} WHERE ID = $id;"; $name = $wpdb->get_var( $sql );
Imagine what would happen if the value of $_POST['id']
was not a number like we expect, and is instead the string 0; DELETE FROM wp_posts;
. The resulting $sql
value that is executed would look like the following.
SELECT post_name FROM wp_posts WHERE ID = 0; DELETE FROM wp_posts;
The correct way to write the above code so that it is not susceptible to SQL injection attacks would be to use $wpdb->prepare()
, and it would look like this:
global $wpdb; // is this really a numeric ID? $id = $_POST['id']; // Use %d for digits, or %s for strings when calling prepare() $sql = $wpdb->prepare( "SELECT post_name FROM {$wpdb->posts} WHERE ID = %d", $id ); $name = $wpdb->get_var( $sql );
Things get a bit more complicated when you need to pass an array of values into an IN
or NOT IN
clause however. The best way to deal with this situation is to use call_user_func_array()
to pass an array as a list of arguments to $wpdb->prepare()
. Since there may be other values we want to escape as well, I usually define a new function to handle this special case and end up calling prepare()
twice. This particular function assumes you want to pass in a list of post ID’s, potentially having the list twice (an OR
statement for example) – so you may need to adjust for your needs.
function my_function(){ global $wpdb; $id = $_POST['id']; $id_array = $_POST['id_array']; $sql = "SELECT post_name FROM {$wpdb->posts} WHERE ID = %d or ID IN ([IN])"; $sql = $wpdb->prepare( $sql, $id ); $sql = prepare_in( $sql, $id_array ); // SELECT post_name FROM wp_posts WHERE ID = 9 or ID IN ( 10, 11, 12 ) } function prepare_in( $sql, $vals ){ global $wpdb; $not_in_count = substr_count( $sql, '[IN]' ); if ( $not_in_count > 0 ){ $args = array( str_replace( '[IN]', implode( ', ', array_fill( 0, count( $vals ), '%d' ) ), str_replace( '%', '%%', $sql ) ) ); // This will populate ALL the [IN]'s with the $vals, assuming you have more than one [IN] in the sql for ( $i=0; $i < substr_count( $sql, '[IN]' ); $i++ ) { $args = array_merge( $args, $vals ); } $sql = call_user_func_array( array( $wpdb, 'prepare' ), array_merge( $args ) ); } return $sql; }
The post Prepare IN and NOT IN Statements in WordPress appeared first on Justin Silver.
]]>The post Disable WP-Cron in WordPress appeared first on Justin Silver.
]]>WP-Cron is the way that WordPress executes scheduled jobs that are added via wp_schedule_event()
or one of its associated functions. First it’s a bit of a hack given the single threaded-ness of PHP. It’s simply not possible to spin up a Thread
like you would in Java to do the work asynchronously, and we don’t want the users to have to wait while we run a large job. To get around this WordPress checks to see if there are any scheduled jobs with an execution time in the past (more on this in a bit), and will then make a request to itself calling wp-cron.php
with a querystring of doing_wp_cron
. The job can now run in this new request (which is a different PHP process) and the page HTML is returned to the users without ever trying to fetch the results.
WP-Cron lets you do things like check for plugin updates or other arbitrary tasks at set intervals – once or twice a day, longer, or more frequently if you desire. In many ways this mimics the functionality of cron
on your Linux box.
There are lots of useful things you can do with a scheduled task – send emails, update derived database values, really anything that you want to happen regularly, but not on every request. If you’re ok with some user’s seeing slightly stale data in exchange for better performance, you can use a WP-Cron job to indicate that the cache needs to be primed.
While WP-Cron can be a very useful tool, there are some downsides to it as well. As mentioned earlier it can only check for jobs that should have already run, meaning that the scheduled $execution_time < time()
, but this can be an issue on sites with low traffic, especially if you actually care what time the task will be run. Even if your site does decent traffic during the day, maybe it drops way off at night and you want to run something as close to 1:00AM as possible… which might or might not happen.
There is also the performance hit you take making an HTTP request, even though it’s to yourself. Why check the database and WP-Cron locks to see if the HTTP request is even necessary? Speaking of locks – while much better about locking WP-Cron, sites with heavy traffic can occasionally end up with multiple WP-Cron requests (if user requests come in at the exact same time).
The way that I handle WP-Cron on my sites is to disable it entirely by setting the DISABLE_WP_CRON
constant to true
in wp-config.php
. This will prevent WordPress from checking for WP-Cron tasks at all when a user makes a request.
The next step is to set cron on the system to make the request to wp-cron.php based on whatever schedule works best for you. You can have it run as frequently as once per minute, or up to… forever. Probably whatever the smallest resolution of your recurring tasks are is best, unless you are using wp_schedule_single_event()
to run something asynchronously – then a minute might be best.
define( 'DISABLE_WP_CRON', true );
# Run WP-Cron every 5 minutes */5 * * * * /usr/bin/curl --silent http://example.com/wp-cron.php?doing_wp_cron >/dev/null
The post Disable WP-Cron in WordPress appeared first on Justin Silver.
]]>