Dashboard Disappears after Upgrade

That is a seriously wonky plugin, my friend.


Prologue

Before we actually start, trash this conditional – it does absolutely nothing:

if($_REQUEST['s'] != '') {

}

But really, more than likely, the original plugin developer had intended this conditional to actually do something by bailing out of the function if $_REQUEST['s'] is not set (seeings as this function will do very little in such a case):

function getApplications( $_REQUEST ) {
    if( empty( $_REQUEST['s'] ) ) {
        return false;
    }

    global $wpdb;
    $sql = "SELECT * FROM employment_applications";

    ...

    return $applications;
}

The Fatal Error

First up: the fatal error. $_REQUEST in PHP is an automatically-populated super-global that PHP fills with HTTP request data. As such, it can be regarded as a reserved word (more specifically, a predefined variable). Specifying $_REQUEST as a parameter to the getApplications() function tells PHP to reference the first argument as $_REQUEST, but super-globals cannot be locally scoped in PHP5 which means that PHP interprets the code as attempting to re-assign the super-global, thus the error (not sure of the specific version in which this changed, but it was possible to use super-globals as parameters in PHP4).

If the getApplications() function is called with arguments, change the name of the parameter to maintain proper flow and avoid reassigning the $_REQUEST super-global:

function getApplications( $request ) {
    if( empty( $request['s'] ) )
        return false;

    global $wpdb;
    $sql = "SELECT * FROM employment_applications";
    $sql .= " WHERE applicant_name LIKE '%" . $request['s']  . "%'";

    $applications = $wpdb->get_results( $sql );
    $wpdb->show_errors();
    $error = $wpdb->last_error;

    if($error != '') {
        echo '<div class="alert alert-error">' . $error . '</div>';
    }

    return $applications;
}

If the getApplications() function called without arguments (or is always called with $_REQUEST as the argument, i.e. getApplications( $_REQUEST )), remove the parameter entirely and make direct use of the $_REQUEST super-global:

function getApplications() {
    if( empty( $_REQUEST['s'] ) )
        return false;

    global $wpdb;
    $sql = "SELECT * FROM employment_applications";
    $sql .= " WHERE applicant_name LIKE '%" . $_REQUEST['s']  . "%'";

    $applications = $wpdb->get_results( $sql );
    $wpdb->show_errors();
    $error = $wpdb->last_error;

    if($error != '') {
        echo '<div class="alert alert-error">' . $error . '</div>';
    }

    return $applications;
}

More than likely, your solution will be the latter of the two.


Disabling Plugins Outside of the Dashboard

There are many ways to go about this, but here are a few of the most common:

  • Alter the active_plugins option (permanent)

    • Database:

      Using PHPMyAdmin or raw SQL, within the wp_options table locate the row whose option_name field is active_plugins and modify it’s
      option_value field. Note that this is a serialized
      value
      ,
      so if you’re not sure how to make the modifications to serialized data
      by hand you’ll need to unserialize the value, make your modifications,
      and then reserialize it before placing it back into the field.
      Alternately, deleting the contents of option_value will disable all
      plugins (though their deactivation hooks may not fire, depending on
      the plugin’s implementation).

      Or you can make use of a serialization-aware search and replace tool.

    • PHP:

      Make use of the deactivate_plugins()
      function
      or the Options API in a must-use plugin. Must-use plugins
      are always regarded as “activated” and are loaded before all other
      plugins; this provides a convenient entry point to execute code even
      when the large portions of the site are inaccessible.

  • Invalidate active_plugins (“undoable”)

    This is essentially the same trick as renaming the (root) plugins folder. In
    active_plugins, WordPress notes the location of each plugin’s
    primary file relative to the plugin root (the constant
    WP_PLUGIN_DIR
    ). By changing WP_PLUGIN_DIR you can force WordPress
    to loose track of the plugins and thus fail to load them, effectively
    processing the request as though no plugins are present and leaving
    the active_plugins option untouched in the database. In wp-config.php: define( 'WP_PLUGIN_DIR', 'thisPathDoesNotExist' );. Removing this line will revert the plugin path to it’s default setting.


“A Better Way to Troubleshoot”

Please refer to the Codex entry Debugging in WordPress for a primer into the debugging facilities provided by WordPress, as well as a short list of some great plugins that aid debugging. The very first step is always to enable the WP_DEBUG constant in wp-config.php (though refrain from doing this in a production environment, if at all possible):

define( 'WP_DEBUG', true );

If WP_DEBUG is false (as is default), WordPress suppresses the vast majority of warnings and errors.

I also highly recommend using Xdebug on your development machine for quick access to ridiculously useful stack traces, formatted var_dump()s, remote debugging, and pretty colors (among other things).


A Note Regarding Security

Let’s take a moment to inspect this line here:

$sql .= " WHERE applicant_name LIKE '%" . $_REQUEST['s']  . "%'";

As $_REQUEST is filled with HTTP request data, what this line here is doing is taking the data-key s straight from the request and dropping it into an SQL query. This is a major security hole and likely exposes your site to SQL injections. Anyone who has access to any part of the site that results in the getApplications() function executing can simply append ?s=whatever to the URL to perform queries of their own design. The lack of proper escaping means that an attacker could even potentially use this query to access data outside of the employment_applications table.

If not abstracted and/or validated, all data supplied by users should be escaped, at the very least:

$sql = "SELECT * FROM employment_applications";
$sql .= " WHERE applicant_name LIKE '%" . esc_sql( like_escape( $_REQUEST['s'] ) ) . "%'";
$applications = $wpdb->get_results( $sql );

or

$querystring = "SELECT * FROM employment_applications WHERE applicant_name LIKE '%%%s%%'";
$applications = $wpdb->get_results( $wpdb->prepare( $querystring, $_REQUEST['s'] ) );

You should give the Codex entries on Data Validation and Protecting Queries Against SQL Injection Attacks a look-over for a brief intro to data security in WordPress.


Epilogue

All of the above considered, I feel your modified code should look similar to the following:

function getApplications() {
    if( empty( $_REQUEST['s'] ) )
        return false;

    global $wpdb;
    $querystring = "SELECT * FROM employment_applications WHERE applicant_name LIKE '%%%s%%'";
    $applications = $wpdb->get_results( $wpdb->prepare( $querystring, $_REQUEST['s'] ) );
    $wpdb->show_errors();
    $error = $wpdb->last_error;

    if( ! empty( $error ) ) {
        echo '<div class="alert alert-error">' . $error . '</div>';
    }

    return $applications;
}