1

I've developed some functionality for a company website around job postings. There is a custom post type, with specialized fields, and a widget that displays a summary of current job postings. It's 5 functions and a widget class that was hobbled together from various websites etc and dumped into functions.php. It works.

Now I would like to extract it to a plugin but my understanding is weak... My first attempt was to just dump it into a php file with a header, but that threw a bunch of errors when I tried to activate it. Then I put all of the function hooks into the activation hook for the plugin - which doesn't throw any errors, but doesn't seem to do anything either.

<?php 
/**
 * Plugin Name: My Careers Plugin
 * Description: This plugin adds a Careers Post Type and careers summary widget .
 * Version: 1.0.0
 * Author: Marc Pelletier
 */
// Exit if accessed directly
if ( !defined( 'ABSPATH' ) ) exit;

function activate_my_careers() {
    add_action( 'init', 'create_career_posttype');
    add_action( 'admin_bar_menu', 'add_careers_to_admin_bar', 1000000);
    add_action( 'add_meta_boxes', 'add_careers_meta_box' );
    add_action( 'save_post', 'save_careers_meta_box' );
    register_widget('CurrentJobs_Widget');
}
register_activation_hook( __FILE__, 'activate_my_careers' );

function create_career_posttype() {
  $args = array(
    'labels' => array(
      'name' => __('Careers'),
      'singular_name' => __('Careers'),
      'all_items' => __('All Job Postings'),
      'add_new_item' => __('Add New Job Posting'),
      'edit_item' => __('Edit Job Posting'),
      'view_item' => __('View Job Posting')
    ),
    'public' => true,
    'has_archive' => true,
    'rewrite' => array('slug' => 'careers'),
    'show_ui' => true,
    'show_in_menu' => true,
    'show_in_nav_menus' => true,
    'show_in_admin_bar' => true,
    'capability_type' => 'post',
    'supports' => array('title', 'editor', 'thumbnail'),
    'exclude_from_search' => true,
    'menu_position' => 6,
    'has_archive' => true,
    'menu_icon' => 'dashicons-universal-access'
    );
  register_post_type('careers', $args);
}

function add_careers_to_admin_bar($wp_admin_bar){

    $wp_admin_bar->add_node(
        array(
            'title'  => '<span class="dashicons-before dashicons-universal-access" >Careers</span>'
        )
    );
    $wp_admin_bar->add_node(
        array(

            'parent' => 'careers',
            'title'  => 'All Job Postings',
            'href' => get_admin_url().'edit.php?post_type=careers'
        )
    );
    $wp_admin_bar->add_node(
        array(

            'parent' => 'careers',
            'title'  => 'New Job Posting',
            'href' => get_admin_url().'post-new.php?post_type=careers'
        )
    );
}

function add_careers_meta_box(){
    add_meta_box( 'job-details', 'Job Details', 'careers_meta_box_cb', 'Careers', 'normal', 'default');
}

function careers_meta_box_cb($post){
    $Post_ID = get_post_custom( $post->ID );
    $salary = isset( $Post_ID['salary'] ) ? esc_attr( $Post_ID['salary'][0] ) : "";
    $salary_unit = isset( $Post_ID['salary_unit'] ) ? esc_attr( $Post_ID['salary_unit'][0] ) : "hr";
    $qualifications = isset( $Post_ID['qualifications'] ) ? esc_attr( $Post_ID['qualifications'][0] ) : "";
    //echo $qualifications;
    $desirables = isset( $Post_ID['desirables'] ) ? esc_attr( $Post_ID['desirables'][0] ) : "";
    //echo $desirables;
    wp_nonce_field( 'job_details_nonce_action', 'job_details_nonce' );
    $html = '';
    $html .= '<label class="h2">Salary</label>';
    $html .= '<input type="number" step="0.05" name="salary" id="salary" style="margin-top:15px; margin-left:9px; margin-bottom:10px;" value="'. $salary .'" />';
    $html .= '<label> / </label>';
    $html .= '<input type="text" list=salary_units name="salary_unit" id="salary_unit" style="margin-left:9px; margin-top:15px;" value="'. $salary_unit .'" /></br>';
    $html .= '<datalist id=salary_units><option>hr<option>day<option>month<option>annum</datalist>';
    $html .= '<label class="h2"> <strong>Required </strong>Qualifications (one per line) </label></br>';
    $html .= '<textarea name="qualifications" id="qualifications" cols="80" rows="5" style="margin-left:9px; margin-top:15px";>'.$qualifications.'</textarea></br>';
    $html .= '<label class="h2">Additional Qualifications (one per line) </label></br>';
    $html .= '<textarea name="desirables" id="desirables" cols="80" rows="5" style="margin-left:9px; margin-top:15px";>'.$desirables.'</textarea></br>';
    echo $html;
}

function save_careers_meta_box($post_id){
    // Bail if we're doing an auto save
    if( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) return;

    // if our nonce isn't there, or we can't verify it, bail
    if( !isset( $_POST['job_details_nonce'] ) || !wp_verify_nonce( $_POST['job_details_nonce'], 'job_details_nonce_action' ) ) return;

    // if our current user can't edit this post, bail
    if( !current_user_can( 'edit_post' ) ) return;

    if(isset( $_POST['salary'] ) )
        update_post_meta( $post_id, 'salary', $_POST['salary']);

    if(isset( $_POST['salary_unit'] ) )
        update_post_meta( $post_id, 'salary_unit', $_POST['salary_unit']);

    if(isset( $_POST['qualifications'] ) )
        update_post_meta( $post_id, 'qualifications', $_POST['qualifications']);

    if(isset( $_POST['desirables'] ) )
        update_post_meta( $post_id, 'desirables', $_POST['desirables']);
}

class CurrentJobs_Widget extends WP_Widget{
function __construct() {
    parent::__construct(
        'currentjobs_widget', // Base ID
        'Current Jobs Widget', // Name
        array('description' => __( 'Displays your latest listings. Outputs the title and expiry per listing'))
       );
}
function update($new_instance, $old_instance) {
    $instance = $old_instance;
    $instance['title'] = strip_tags($new_instance['title']);
    $instance['numberOfListings'] = strip_tags($new_instance['numberOfListings']);
    return $instance;
}

function form($instance) {
    if( $instance) {
        $title = esc_attr($instance['title']);
        $numberOfListings = esc_attr($instance['numberOfListings']);
    } else {
        $title = '';
        $numberOfListings = '';
    }
    ?>
        <p>
        <label for="<?php echo $this->get_field_id('title'); ?>"><?php _e('Title', 'currentjobs_widget'); ?></label>
        <input class="widefat" id="<?php echo $this->get_field_id('title'); ?>" name="<?php echo $this->get_field_name('title'); ?>" type="text" value="<?php echo $title; ?>" />
        </p>
        <p>
        <label for="<?php echo $this->get_field_id('numberOfListings'); ?>"><?php _e('Number of Listings:', 'currentjobs_widget'); ?></label>
        <select id="<?php echo $this->get_field_id('numberOfListings'); ?>"  name="<?php echo $this->get_field_name('numberOfListings'); ?>">
            <?php for($x=1;$x<=10;$x++): ?>
            <option <?php echo $x == $numberOfListings ? 'selected="selected"' : '';?> value="<?php echo $x;?>"><?php echo $x; ?></option>
            <?php endfor;?>
        </select>
        </p>
    <?php
}

function widget($args, $instance) {
    extract( $args );
    $title = apply_filters('widget_title', $instance['title']);
    $numberOfListings = $instance['numberOfListings'];
    echo $before_widget;
    if ( $title ) {
        echo $before_title . $title . $after_title;
    }
    $this->getJobListings($numberOfListings);
    echo $after_widget;
}
function getJobListings($numberOfListings) { //html
    global $post;
    $listings = new WP_Query();
    $listings->query('post_type=careers&posts_per_page=' . $numberOfListings );
    if($listings->found_posts > 0) {
        echo '<ul class="currentjobs_widget">';
            while ($listings->have_posts()) {
                $listings->the_post();
                $listItem = '<li>' . $image;
                $listItem .= '<a href="' . get_permalink() . '">';
                $listItem .= get_the_title() . '</a></br>';
                $listItem .= '<span>Added ' . get_the_date() . '</span></li>';
                echo $listItem;
            }
        echo '</ul>';
        wp_reset_postdata();
    }else{
        echo '<p style="padding:25px;">No postings found</p>';
    }
}
} //end class CurrentJobs_Widget

Suggestions for further reading are great.

3

If all you want is for your plugin to work, simply

replace

register_activation_hook( __FILE__, 'activate_my_careers' );

with

add_action( 'plugins_loaded', 'activate_my_careers' );

If you want to understand why your new plugin isn't working, then you need to understand WordPress hooks. I would encourage you to carefully read the WordPress Plugin API.

The first hook available to (non- must-use) plugins is plugins_loaded. When I write plugins, the only thing I put into the main plugin file are adding actions to the activation, deactivation, and plugins_loaded hooks.

The function/method called by the plugins_loaded hook should load your plugin by adding actions and filters to other hooks. By using hooks you allow yourself and other plugins the option of removing those hooks before they fire. This makes your plugin extensible.

The key is to know which hooks fire when and to add functions/methods to specific hooks that only get fired when you want the function/method to be available.

So for your example, the only action we want to add when the main plugin file is included is a hook for plugins_loaded. The callback for that hook will then add the other actions needed for your plugin to work.

The reason your plugin isn't working is because the activation hook is only fired immediately after a plugin is activated. Once the plugin is activated, the only other hook called is shutdown before you're redirected to another page. From then on, the activation hook doesn't fire and since it doesn't none of your other actions will fire either.

1
  • Nathan, this works except for one thing... I had to create a new function with the register_widget call in it (called register_career_widget) and replace register_widget in activate_my_careers with add_action('widgets_init', 'register_career_widget'); .
    – marcp
    Feb 28 '17 at 19:11
1

don't wrap actions and register_widget inside the function activate_my_careers but if you do so then also hook this function with one of the hooks. like init, admin_init

add_action( 'init', 'activate_my_careers' );

or use plugin activation hook. like

register_activation_hook( __FILE__, 'activate_my_careers' );

for example

function activate_my_careers() {
    add_action( 'init', 'create_career_posttype');
    add_action( 'admin_bar_menu', 'add_careers_to_admin_bar', 1000000);
    add_action( 'add_meta_boxes', 'add_careers_meta_box' );
    add_action( 'save_post', 'save_careers_meta_box' );
    register_widget('CurrentJobs_Widget');
}
2
  • Actually I do have the register_activation_hook defined right after activate_my_careers. I just neglected to include it. I've updated my question to include it.
    – marcp
    Feb 3 '17 at 19:47
  • @marcp the problem is that you have it hooked to only register_activation_hook. Those actions will only fire once on the single request where your plugin is activated. Those actions need to be added on every request. Actions don't persist beyond the single http request they are added on.
    – Milo
    Feb 3 '17 at 22:58
0

You can use:

function activate_my_careers() {
     add_action( 'init', 'create_career_posttype');
     add_action( 'admin_bar_menu', 'add_careers_to_admin_bar', 1000000);
     add_action( 'add_meta_boxes', 'add_careers_meta_box' );
     add_action( 'save_post', 'save_careers_meta_box' );
     register_widget('CurrentJobs_Widget'); }
}

register_activation_hook( __FILE__, 'activate_my_careers' );

Where your other functions are defined?

1
  • That's exactly what I did do. I just neglected that in my question. I've updated it.
    – marcp
    Feb 3 '17 at 19:49
0

You also need to refer or include all your callbacks (create_career_posttype, add_careers_to_admin_bar, etc)...

function create_career_posttype() {
  //code
 }
function add_careers_to_admin_bar() {
  //code
 }
function add_careers_meta_box() {
  //code
 }
function save_careers_meta_box() {
  //code
 }
register_activation_hook( __FILE__, 'activate_my_careers' ); //
1
  • Yes, all of the functions are defined in the same file as well. I didn't include them for simplicity.
    – marcp
    Feb 3 '17 at 19:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.