Meta Box on Custom Post Type not saving

Hi @George Wiscombe:

So close, yet so far! (I know the pain, I’m there often. 🙂 You almost had it but are using the wrong hook to do the post meta update. Use the 'wp_insert_post_data' hook instead of the 'save_post' hook:

<?php
if (!class_exists('YourSite_Portfolio')) {
  class YourSite_Portfolio {
    static function on_load() {
      add_action('init',array(__CLASS__,'init'));
      add_action("admin_init",array(__CLASS__,'admin_init'));
      add_action('wp_insert_post_data',array(__CLASS__,'wp_insert_post_data'),10,2);
    }
    static function init() {
      register_post_type( 'portfolio',array(
        'label' => __('Portfolio'),
        'singular_label' => __('Portfolio'),
        'public' => true,
        'show_ui' => true,
        'menu_position' => 5,
        'capability_type' => 'page',
        'hierarchical' => false,
        'rewrite' => true,
        'show_in_nav_menus' => true,
        'supports' => array('title', 'editor', 'thumbnail', 'excerpt'),
        'has_archive' => true
      ));
      register_taxonomy( 'type', 'portfolio', array(
        'hierarchical' => false,
        'label' => __('Project Type'),
        'query_var' => 'type',
        'rewrite' => array('slug' => 'portfolio/type' )
      ));
    }
    static function admin_init(){
      add_meta_box('portfolio_text','Main Text',array(__CLASS__,'portfolio_options'),'portfolio','normal','high');
    }
    static function portfolio_options($post,$metabox) {
      $portfolio_text =  get_post_meta($post->ID,'portfolio_text',true);
    $html =<<<HTML
<textarea name="portfolio_text" cols="40" rows="1" style="width:98%; height:100px"/>{$portfolio_text}</textarea>
HTML;
      echo $html;
    }
    static function wp_insert_post_data($data,$postarr) {
      if ($postarr['post_type'] == 'portfolio') {
        update_post_meta($postarr['ID'], 'portfolio_text', $postarr['portfolio_text']);
      }
      return $data;
    }
  }
  YourSite_Portfolio::on_load();
}

The 'wp_insert_post_data' hook is where WordPress captures the values from the $_POST array; by the time you reach 'save_post' WordPress has reloaded the post and that’s why 'portfolio_text' empty when you are trying to save it.