Nov. 6, 2011, 2:11 p.m.
IT

Drupal 7 and Blog API

I finally managed to get Drupal 7 work with MarsEdit and Blog API. Since I had a request to explain what I did, I have decided to include a summarised version of what was needed. Take note that I cannot give you a module that just works as the changes I had to make was quite specific to my installation. With some time and energy one can most probably make it generic, but I leave that to the reader.

First and foremost, the people at DITAinfo ported the core BlogAPI module that was removed from Drupal core, to Drupal 7. According to them it is working mostly, but for me it failed to work. In specific, the following functions did not work:

The fixes assume you have downloaded and installed their blogapi.tar file.

Posting new entry - Body content

This failed because my Text Filter was set to None in MarsEdit. This sent through a 0 in the mtconvertbreaks field, which is assigned in blogapi.module to the format field. That format field corresponds to the Text formats set up in Drupal. If you go to configure the text format, you will see next to the name field, the machine name. This will be a number. For me, I have no 0 defined, therefore no body content was being passed through. I therefore changed MarsEdit's Text Filter to "Full HTML", which corresponded to Full HTML on my Drupal installation. Once done, the post showed up just fine, but still with no category assigned.

Another error was that the method _blogapi_mt_extra in blogapi.modules were not working properly with the XML MarsEdit was sending through. MarsEdit sent through blank values for mt_excerpt and mt_text_more if no break was specified, but the blogapi module kept on appending and to the body text every time the post was edited. I fixed this by changing the following lines:

if (isset($struct['mt_excerpt']) && !empty($struct['mt_excerpt'])) {   
  $node->body['und'][0]['value'] = $struct['mt_excerpt'] . 
     '<!--break-->' . $node->body['und'][0]['value'];  
}  

if (isset($struct['mt_text_more']) && !empty($struct['mt_text_more'])) {
  $node->body['und'][0]['value'] = $node->body['und'][0]['value'] . 
    '<!--extended-->' . $struct['mt_text_more'];
}

The extra check was to ensure that those fields need to not only be set, but also not blank.

Posting new entry - missing categories

The missing categories were much harder to get to work. The taxonomy vocabulary was changed for my specific installation. My Content Type Blog, which I use for all my blog entries, has a field called Blog Categories of type Term reference, with a Name of taxonomy_vocabulary_1. The key here for me was to change blogapi_mt_set_post_categories in blogapi.modules as follows - this is not the best code but I was lazy:

function blogapi_mt_set_post_categories($postid, $username, $password, $categories) {
  bloglog("blogapi_mt_set_post_categories ".$username." ".$password. 
     " categories=".count($categories));

  $user = blogapi_validate_user($username, $password);
  if (!$user->uid) {
    return blogapi_error($user);
  }

  $node = node_load($postid);

  $node->taxonomy = array();
  $node->taxonomy_vocabulary_1[LANGUAGE_NONE] = array();
  foreach ($categories as $category) {
    $node->taxonomy[] = $category['categoryId'];
  }
  $validated = blogapi_mt_validate_terms($node);
  if ($validated !== TRUE) {
    return $validated;
  }

  foreach ($categories as $category) {
    $node->taxonomy_vocabulary_1[LANGUAGE_NONE][]['tid'] = $category['categoryId'];
  }

  $node = node_submit($node);

  node_save($node);

  return TRUE;
}

The key change here was that $node->taxonomy did not exist for me. I still use it because blogapi_mt_validate_terms use it and I was lazy, but to make it save properly I had to ensure the vocabulary tid's selected were assigned to $node->taxonomy_vocabulary_1[LANGUAGE_NONE][]['tid']. I also had to modify blogapi_mt_validate_terms as follows (once again this is horrible coding from my part):

function blogapi_mt_validate_terms($node) {
  // We do a lot of heavy lifting here since taxonomy module doesn't have a
  // stand-alone validation function.
  if (module_exists('taxonomy')) {

    $found_terms = array();
    if (!empty($node->taxonomy)) {
      $term_list = array_unique($node->taxonomy);

      $query = new EntityFieldQuery();

      $query
        ->entityCondition('entity_type', 'taxonomy_vocabulary', '=')
        ->propertyCondition('machine_name', $node->type, '=');

      $result = $query->execute();
      $aids = array();
      foreach($result['taxonomy_vocabulary'] as $record) {
        $aids[] = $record->vid;
      }

      if (empty($aids))
        return blogapi_error(t('Node type ['.$node->type.
               '] does not match any Vocabulary machine name.'));

      $vid = $aids[0];
      $terms = taxonomy_term_load_multiple($term_list);
      $found_terms = array();      $found_count = 0;
      foreach ($terms as $term) {
        if ($term->vid == $vid) {
          $found_terms[$term->vid][$term->tid] = $term->tid;
          $found_count++;
        }
      }
      // If the counts don't match, some terms are invalid or not accessible to this user.
      if (count($term_list) != $found_count) {
        return blogapi_error(t('Invalid categories submitted.'));
      }
    }

    // Look up all the vocabularies for this node type.
    $vocabularies = taxonomy_vocabulary_load_multiple($aids);
    // Check each vocabulary associated with this node type.
    foreach ($vocabularies as $vocabulary) {
      // Required vocabularies must have at least one term.
      if (empty($found_terms[$vocabulary->vid])) {
        return blogapi_error(t('A category from the @vocabulary_name vocabulary is required.',
            array('@vocabulary_name' => $vocabulary->name)));
      }
      // Vocabularies that don't allow multiple terms may have at most one.
     // if (!($vocabulary->multiple) &&
   (isset($found_terms[$vocabulary->vid]) &&
    count($found_terms[$vocabulary->vid]) > 1)) {
     //   return blogapi_error(t('You may only choose one category from the '
             '@vocabulary_name vocabulary.'), array('@vocabulary_name' => $vocabulary->name));
     // }
    }
  }
  elseif (!empty($node->taxonomy)) {
    return blogapi_error(t('Error saving categories. This feature is not available.'));
  }
  return TRUE;
}

I had to remove the check for multiple vocabularies as well since those fields do not seem to be supported anymore. Once this was done categories were saved properly.

Reading existing entry - no body

When MarsEdit posts an entry, it immediately reads it back. In my case this failed. In blogapi_blogger_get_recent_posts in blogapi.module, the code was incorrect for reading the body of the post as it pointed to a comment field, not the body field. In Drupal 7 the body field moved to the field_data_body table. So I changed the SQL to read:

if ($bodies) {
  $result = db_query_range("SELECT n.nid, n.title, f.body_value, n.created, u.name FROM
   {node} n, {node_revision} r, {field_data_body} f, {users} u WHERE n.uid = u.uid AND
    n.vid = r.vid AND n.nid = f.entity_id AND n.type = :type AND n.uid = :uid
    ORDER BY n.created DESC", 0, $number_of_posts,
    array(
    ':type' => $blogid,
    ':uid' => $user->uid
    )
  );
}
...

and also _blogapi_get_post to:

if ($bodies) {
  if (!empty($node->body_value))
    $body = $node->body_value;
  else
    $body = $node->body['und'][0]['value'];
  ...

This allowed the content to be returned properly.

Reading existing entries - no categories retrieved

So when the entry is being read back, the categories were always blank. The problem here was in method blogapi_mt_get_post_categories of blogapi.modules. The code used a deprecated method call to the taxonomy module's node_get_terms method. I had to change that and look up my own vocabulary for the node's type. Then I loaded all taxonomy terms and picked only those with the same vocabulary. Once confirmed I sent back the list that matched the node's terms. Horrible code but it works for me. I don't have a category hierarchy - mine is flat, so the code could fail there.

function blogapi_mt_get_post_categories($postid, $username, $password) {
  bloglog("blogapi_mt_get_post_categories ".$username." ".$password);
  $user = blogapi_validate_user($username, $password);
  if (!$user->uid) {
    return blogapi_error($user);
  }

  $node = node_load($postid);
  //$terms = module_invoke('taxonomy', 'node_get_terms', $node, 'tid');
  $query = new EntityFieldQuery();

  $query
    ->entityCondition('entity_type', 'taxonomy_vocabulary', '=')
    ->propertyCondition('machine_name', $node->type, '=');

  $result = $query->execute();
  $aids = array();
  foreach($result['taxonomy_vocabulary'] as $record) {
    $aids[] = $record->vid;
  }

  if (empty($aids))
    return blogapi_error(t('Node type ['.$node->type.'] does not match any
            Vocabulary machine name.'));

  $taxonomy = array();
  foreach ($node->taxonomy_vocabulary_1[LANGUAGE_NONE] as $term) {
    $taxonomy[] = $term['tid'];
  }

  $term_list = array_unique($taxonomy);
  $vid = $aids[0];
  $terms = taxonomy_term_load_multiple($term_list);
  $found_terms = array();
  $found_count = 0;
  $categories = array();
  foreach ($terms as $term) {
    if ($term->vid == $vid) {
      $term_name = $term->name;
      foreach (module_invoke('taxonomy', 'get_parents', $term->tid, 'tid') as $parent) {
        $term_name = $parent->name . '/' . $term_name;
      }

      $categories[] = array('categoryName' => $term_name,
                            'categoryId' => $term->tid,
                            'isPrimary' => TRUE);
    }
  }

  return $categories;
}

This should at least help point you in the correct direction.