Index: views/itunes.views.inc
===================================================================
RCS file: views/itunes.views.inc
diff -N views/itunes.views.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ views/itunes.views.inc	28 Nov 2010 23:05:05 -0000
@@ -0,0 +1,114 @@
+<?php
+// $Id: itunes.views.inc,v 1.1 2010/05/20 22:55:48 drewish Exp $
+
+/**
+ * @file
+ * Views includes.
+ */
+
+/**
+ * Implementation of hook_views_plugins().
+ */
+function itunes_views_plugins() {
+  return array(
+    'module' => 'itunes',
+    'style' => array(
+      'itunes_rss' => array(
+        'title' => t('iTunes RSS Feed'),
+        'help' => t('Generates an RSS feed from a view with extra iTunes specific attributes.'),
+        'handler' => 'itunes_plugin_style_rss',
+        'path' => drupal_get_path('module', 'itunes') . '/views',
+        'theme' => 'views_view_rss',
+        'parent' => 'rss',
+        'uses row plugin' => TRUE,
+        'uses options' => TRUE,
+        'type' => 'feed',
+      ),
+    ),
+    'row' => array(
+      'itunes_rss' => array(
+        'title' => t('iTunes RSS Item'),
+        'help' => t("Customize the sources of data for the episodes. Use this if the Node style doesn't meet your needs."),
+        'handler' => 'itunes_plugin_row_rss',
+        'path' => drupal_get_path('module', 'itunes') . '/views',
+        'uses fields' => TRUE,
+        'type' => 'feed',
+        'parent' => 'node_rss',
+        'uses options' => TRUE,
+      ),
+    ),
+  );
+}
+
+
+/**
+ * Implementation of hook_views_data()
+ */
+function itunes_views_data() {
+  // Basic table information.
+  $data['itunes_item']['table']['group']  = t('iTunes');
+
+  // Join to 'node' as a base table.
+  $data['itunes_item']['table']['join'] = array(
+    'node' => array(
+      'left_field' => 'vid',
+      'field' => 'vid',
+    ),
+  );
+
+  // ----------------------------------------------------------------
+  // Fields
+  $data['itunes_item']['summary'] = array(
+    'title' => t('Summary'),
+    'field' => array(
+      'handler' => 'views_handler_field',
+      'click sortable' => TRUE,
+     ),
+    'sort' => array(
+      'handler' => 'views_handler_sort',
+    ),
+    'filter' => array(
+      'handler' => 'views_handler_filter_string',
+    ),
+    'argument' => array(
+      'handler' => 'views_handler_argument_string',
+    ),
+  );
+  $data['itunes_item']['subtitle'] = array(
+    'title' => t('Subtitle'),
+    'field' => array(
+      'handler' => 'views_handler_field',
+      'click sortable' => TRUE,
+     ),
+    'sort' => array(
+      'handler' => 'views_handler_sort',
+    ),
+    'filter' => array(
+      'handler' => 'views_handler_filter_string',
+    ),
+    'argument' => array(
+      'handler' => 'views_handler_argument_string',
+    ),
+  );
+  $data['itunes_item']['explicit'] = array(
+    'title' => t('Explicit'),
+
+  );
+  $data['itunes_item']['block'] = array(
+    'title' => t('Blocked'),
+    'field' => array(
+      'handler' => 'views_handler_field_boolean',
+      'click sortable' => TRUE,
+    ),
+    'filter' => array(
+      'handler' => 'views_handler_filter_boolean_operator',
+      'label' => t('Blocked'),
+      'type' => 'yes-no',
+    ),
+    'sort' => array(
+      'handler' => 'views_handler_sort',
+    ),
+  );
+
+  return $data;
+}
\ No newline at end of file
Index: views/itunes.views_default.inc
===================================================================
RCS file: views/itunes.views_default.inc
diff -N views/itunes.views_default.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ views/itunes.views_default.inc	28 Nov 2010 23:05:05 -0000
@@ -0,0 +1,124 @@
+<?php
+// $Id: itunes.views_default.inc,v 1.1 2010/05/20 22:55:48 drewish Exp $
+
+/**
+ * @file
+ * Default iTunes view.
+ */
+/**
+ * Implementation of hook_views_default_views().
+ */
+function itunes_views_default_views() {
+  $itunes_types = array();
+  foreach (variable_get('itunes_types', array('audio')) as $type) {
+    $itunes_types[$type] = $type;
+  }
+
+  /*
+   * View 'itunes'
+   */
+  $view = new view;
+  $view->name = 'itunes';
+  $view->description = 'A demo feed for the iTunes module. You can either enable this and modify it to suit your needs or, if you don\'t like the name, clone it and make your changes that way.';
+  $view->tag = 'iTunes';
+  $view->view_php = '';
+  $view->base_table = 'node';
+  $view->is_cacheable = FALSE;
+  $view->api_version = 2;
+  $view->disabled = TRUE; /* Edit this to true to make a default view disabled initially */
+  $handler = $view->new_display('default', 'Defaults', 'default');
+  $handler->override_option('filters', array(
+    'type' => array(
+      'operator' => 'in',
+      'value' => $itunes_types,
+      'group' => '0',
+      'exposed' => FALSE,
+      'expose' => array(
+        'operator' => FALSE,
+        'label' => '',
+      ),
+      'id' => 'type',
+      'table' => 'node',
+      'field' => 'type',
+      'relationship' => 'none',
+    ),
+  ));
+  $handler->override_option('access', array(
+    'type' => 'none',
+  ));
+  $handler->override_option('cache', array(
+    'type' => 'none',
+  ));
+  $handler->override_option('row_plugin', 'node');
+  $handler->override_option('row_options', array(
+    'teaser' => 1,
+    'links' => 1,
+    'comments' => 0,
+  ));
+  $handler = $view->new_display('feed', 'Feed', 'feed_1');
+  $handler->override_option('style_plugin', 'itunes_rss');
+  $handler->override_option('style_options', array(
+    'mission_description' => FALSE,
+    'description' => '',
+    'subtitle' => '',
+    'summary' => '',
+    'author' => '',
+    'copyright' => '',
+    'image_url' => '',
+    'explicit' => '0',
+    'block' => 0,
+    'owner_name' => '',
+    'owner_email' => '',
+    'categories' => array(
+      '0' => '',
+      '1' => '',
+      '2' => '',
+    ),
+  ));
+  $handler->override_option('row_plugin', 'node_rss');
+  $handler->override_option('row_options', array(
+    'item_length' => 'default',
+  ));
+  $handler->override_option('path', 'itunes/feed');
+  $handler->override_option('menu', array(
+    'type' => 'none',
+    'title' => '',
+    'description' => '',
+    'weight' => 0,
+    'name' => 'navigation',
+  ));
+  $handler->override_option('tab_options', array(
+    'type' => 'none',
+    'title' => '',
+    'description' => '',
+    'weight' => 0,
+    'name' => 'navigation',
+  ));
+  $handler->override_option('displays', array(
+    'default' => 'default',
+    'page_1' => 'page_1',
+  ));
+  $handler->override_option('sitename_title', FALSE);
+  $handler = $view->new_display('page', 'Page', 'page_1');
+  $handler->override_option('title', 'iTunes');
+  $handler->override_option('path', 'itunes/feed');
+  $handler->override_option('menu', array(
+    'type' => 'none',
+    'title' => '',
+    'description' => '',
+    'weight' => 0,
+    'name' => 'navigation',
+  ));
+  $handler->override_option('tab_options', array(
+    'type' => 'none',
+    'title' => '',
+    'description' => '',
+    'weight' => 0,
+    'name' => 'navigation',
+  ));
+  $views[$view->name] = $view;
+
+  return $views;
+}
+
+
Index: views/itunes_plugin_row_rss.inc
===================================================================
RCS file: views/itunes_plugin_row_rss.inc
diff -N views/itunes_plugin_row_rss.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ views/itunes_plugin_row_rss.inc	28 Nov 2010 23:05:05 -0000
@@ -0,0 +1,366 @@
+<?php
+// $Id: itunes_plugin_style_rss.inc,v 1.1 2010/05/20 22:55:48 drewish Exp $
+
+/**
+ * @file
+ * Contains the RSS row plugin.
+ */
+
+/**
+ * iTunes specific row plugin to render the feed element.
+ *
+ * @ingroup views_style_plugins
+ */
+class itunes_plugin_row_rss extends views_plugin_row_node_rss {
+  var $elements;
+
+  function itunes_plugin_row_rss() {
+    $this->elements = array(
+      'author' => array('title' => t('Author')),
+      'subtitle' => array('title' => t('Subtitle')),
+      'summary' => array('title' => t('Summary')),
+      'enclosure_url' => array('title' => t('Enclosure URL')),
+      'enclosure_mime' => array('title' => t('Enclosure MIME Type')),
+      'enclosure_size' => array('title' => t('Enclosure Size')),
+      'duration' => array('title' => t('Duration')),
+      'block' => array('title' => t('Blocked')),
+      'explicit' => array('title' => t('Explicit')),
+      'keywords' => array('title' => t('Keywords')),
+    );
+  }
+
+  function option_definition() {
+    $options = parent::option_definition();
+
+    $options['sources'] = array('translatable' => FALSE, 'default' => array());
+    foreach ($this->elements as $key => $info) {
+      $options['sources']['default'][$key] = NULL;
+    }
+
+    return $options;
+  }
+
+  function options_form(&$form, &$form_state) {
+    parent::options_form($form, $form_state);
+    $handlers = $this->display->handler->get_handlers('field');
+    if (empty($handlers)) {
+      $form['error_markup'] = array(
+        '#value' => t('You need at least one field before you can configure your feed item settings'),
+        '#prefix' => '<div class="error form-item description">',
+        '#suffix' => '</div>',
+      );
+      return;
+    }
+
+
+    // Create an array of allowed columns from the data we know:
+    $field_names = array(NULL => t('- Not selected -')) + $this->display->handler->get_field_labels();
+
+    $form['sources'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Sources'),
+    );
+    foreach ($this->elements as $key => $info) {
+      $form['sources'][$key] = array(
+        '#type' => 'select',
+        '#title' => $info['title'],
+        '#description' => isset($info['description']) ? $info['description'] : NULL,
+        '#options' => $field_names,
+        '#default_value' => $this->options['sources'][$key],
+      );
+    }
+  }
+
+  function render($row) {
+    $values = array();
+
+    // The values are stored in $row by the field alias so we need to look up
+    // the alias before we can fetch the value.
+    foreach ($this->elements as $key => $info) {
+      if (!empty($this->options['sources'][$key])) {
+        $field_name = $this->options['sources'][$key];
+        $field_alias = $this->view->field[$field_name]->field_alias;
+        $values[$key] = $row->{$field_alias};
+      }
+    }
+
+    return '';
+
+      // For the most part, this code is taken from node_feed() in node.module
+    global $base_url;
+
+    $nid = $row->{$this->field_alias};
+    if (!is_numeric($nid)) {
+      return;
+    }
+
+    $item_length = $this->options['item_length'];
+    if ($item_length == 'default') {
+      $item_length = variable_get('feed_item_length', 'teaser');
+    }
+
+    // Load the specified node:
+    $node = node_load($nid);
+    if (empty($node)) {
+      return;
+    }
+
+    $node->build_mode = NODE_BUILD_RSS;
+
+    if ($item_length != 'title') {
+      $teaser = ($item_length == 'teaser') ? TRUE : FALSE;
+
+      // Filter and prepare node teaser
+      if (node_hook($node, 'view')) {
+        $node = node_invoke($node, 'view', $teaser, FALSE);
+      }
+      else {
+        $node = node_prepare($node, $teaser);
+      }
+
+      // Allow modules to change $node->teaser before viewing.
+      node_invoke_nodeapi($node, 'view', $teaser, FALSE);
+    }
+
+    // Set the proper node part, then unset unused $node part so that a bad
+    // theme can not open a security hole.
+    $content = drupal_render($node->content);
+    if ($teaser) {
+      $node->teaser = $content;
+      unset($node->body);
+    }
+    else {
+      $node->body = $content;
+      unset($node->teaser);
+    }
+
+    // Allow modules to modify the fully-built node.
+    node_invoke_nodeapi($node, 'alter', $teaser, FALSE);
+
+    $item = new stdClass();
+    $item->title = $node->title;
+    $item->link = url("node/$row->nid", array('absolute' => TRUE));
+    $item->nid = $node->nid;
+    $item->readmore = $node->readmore;
+
+    // Allow modules to add additional item fields and/or modify $item
+    $extra = node_invoke_nodeapi($node, 'rss item');
+    $item->elements = array_merge($extra,
+      array(
+        array('key' => 'pubDate', 'value' => gmdate('r', $node->created)),
+        array(
+          'key' => 'dc:creator',
+          'value' => $node->name,
+          'namespace' => array('xmlns:dc' => 'http://purl.org/dc/elements/1.1/'),
+        ),
+        array(
+          'key' => 'guid',
+          'value' => $node->nid . ' at ' . $base_url,
+          'attributes' => array('isPermaLink' => 'false')
+        ),
+      )
+    );
+
+    // Custom elements
+    $item->elements[] = array(
+      'key' => 'enclosure',
+      'value' => '',
+      'attributes' => array(
+        'url' => $values['enclosure_url'],
+        'length' => !empty($values['enclosure_size']) ? (int) $values['enclosure_size'] : 0,
+        'type' => !empty($values['enclosure_mime']) ? check_plain($values['enclosure_mime']) : 'audio/mpeg',
+      ),
+    );
+    $item->elements[] = array(
+      'namespace' => array('xmlns:itunes' => 'http://www.itunes.com/dtds/podcast-1.0.dtd'),
+      'key' => 'itunes:duration',
+      'value' => (int) $values['duration'],
+    );
+
+    $item->elements[] = array(
+      'namespace' => array('xmlns:itunes' => 'http://www.itunes.com/dtds/podcast-1.0.dtd'),
+      'key' => 'itunes:keywords',
+      'value' => implode(',', array_slice($keywords, 0, 12)),
+    );
+
+    $item->elements[] = array(
+      'namespace' => array('xmlns:itunes' => 'http://www.itunes.com/dtds/podcast-1.0.dtd'),
+      'key' => 'itunes:author',
+      'value' => $values['author'],
+    );
+    $item->elements[] = array(
+      'namespace' => array('xmlns:itunes' => 'http://www.itunes.com/dtds/podcast-1.0.dtd'),
+      'key' => 'itunes:summary',
+      'value' => $values['summary'],
+    );
+    $item->elements[] = array(
+      'namespace' => array('xmlns:itunes' => 'http://www.itunes.com/dtds/podcast-1.0.dtd'),
+      'key' => 'itunes:subtitle',
+      'value' => $values['subtitle'],
+    );
+    $item->elements[] = array(
+      'namespace' => array('xmlns:itunes' => 'http://www.itunes.com/dtds/podcast-1.0.dtd'),
+      'key' => 'itunes:block',
+      'value' => 'yes',
+    );
+    $item->elements[] = array(
+      'namespace' => array('xmlns:itunes' => 'http://www.itunes.com/dtds/podcast-1.0.dtd'),
+      'key' => 'itunes:explicit',
+      'value' => itunes_explicit($values['explicit']),
+    );
+
+
+    foreach ($item->elements as $element) {
+      if (isset($element['namespace'])) {
+        $this->view->style_plugin->namespaces = array_merge($this->view->style_plugin->namespaces, $element['namespace']);
+      }
+    }
+
+    // Prepare the item description
+    switch ($item_length) {
+      case 'fulltext':
+        $item->description = $node->body;
+        break;
+      case 'teaser':
+        $item->description = $node->teaser;
+        if (!empty($item->readmore)) {
+          $item->description .= '<p>' . l(t('read more'), 'node/' . $item->nid, array('absolute' => TRUE, 'attributes' => array('target' => '_blank'))) . '</p>';
+        }
+        break;
+      case 'title':
+        $item->description = '';
+        break;
+    }
+
+    return theme($this->theme_functions(), $this->view, $this->options, $item);
+  }
+
+
+
+  function _render($row) {
+    // For the most part, this code is taken from node_feed() in node.module
+    global $base_url;
+
+    $item_length = $this->options['item_length'];
+    if ($item_length == 'default') {
+      $item_length = variable_get('feed_item_length', 'teaser');
+    }
+
+    if (empty($this->view->style_plugin->namespaces)) {
+      $this->view->style_plugin->namespaces['xmlns:itunes'] = 'http://www.itunes.com/dtds/podcast-1.0.dtd';
+    }
+
+    // Load the specified node:
+    $item = node_load($row->nid);
+    $item->build_mode = NODE_BUILD_RSS;
+    $item->link = url("node/$row->nid", array('absolute' => TRUE));
+
+    if ($item_length != 'title') {
+      $teaser = ($item_length == 'teaser') ? TRUE : FALSE;
+
+      // Filter and prepare node teaser
+      if (node_hook($item, 'view')) {
+        $item = node_invoke($item, 'view', $teaser, FALSE);
+      }
+      else {
+        $item = node_prepare($item, $teaser);
+      }
+
+      // Allow modules to change $node->teaser before viewing.
+      node_invoke_nodeapi($item, 'view', $teaser, FALSE);
+    }
+
+    // Allow modules to add additional item fields and/or modify $item
+    $extra = node_invoke_nodeapi($item, 'rss item');
+    $extra = array_merge($extra,
+      array(
+        array('key' => 'pubDate', 'value' => gmdate('r', $item->created)),
+        // The author should be an email address. Need to add this in.
+        //array('key' => 'author', 'value' => $item->name),
+      )
+    );
+    foreach ($extra as $element) {
+      if (isset($element['namespace'])) {
+        $this->view->style_plugin->namespaces = array_merge($this->view->style_plugin->namespaces, $element['namespace']);
+      }
+    }
+
+    // Prepare the item description
+    switch ($item_length) {
+      case 'fulltext':
+        $item_text = $item->body;
+        break;
+      case 'teaser':
+        $item_text = $item->teaser;
+        if (!empty($item->readmore)) {
+          $item_text .= '<p>' . l(t('read more'), 'node/' . $item->nid, array('absolute' => TRUE, 'attributes' => array('target' => '_blank'))) . '</p>';
+        }
+        break;
+      case 'title':
+        $item_text = '';
+        break;
+    }
+
+    $stripped_item_text = strip_tags($item_text);
+
+    if (strlen($stripped_item_text) > 255) {
+      $item_subtitle = substr($stripped_item_text, 0, 252) .'...';
+    }
+    else {
+      $item_subtitle = $stripped_item_text;
+    }
+
+    if (!getid3_load(TRUE)) {
+      return NULL;
+    }
+    $getid3 = new getID3;
+    foreach ( $this->view->field as $id => $field ) {
+      if ($field->content_field['widget']['type'] == 'filefield_widget') {
+        foreach ( $item->{$field->content_field['field_name']} as $file ) {
+          $info = $getid3->analyze($file['filepath']);
+          $file_extra = array();
+          $file_extra[] = array(
+            'key' => 'enclosure',
+            'attributes'  =>  array(
+              'url' => file_create_url($file['filepath']),
+              'length' => $file['filesize'],
+              'type' => $file['filemime'],
+            ),
+          );
+          $file_extra[] = array(
+            'key' => 'itunes:duration',
+            'value' => $info['playtime_string'],
+          );
+          $file_extra[] = array(
+            'key' => 'itunes:author',
+            'value' => $info['tags']['id3v2']['artist'][0],
+          );
+
+          $file_extra[] = array(
+            'key' => 'itunes:subtitle',
+            'value' => str_replace('&amp;', '&', $item_subtitle),
+          );
+          $file_extra[] = array(
+            'key' => 'itunes:summary',
+            'value' => str_replace('&amp;', '&', $stripped_item_text),
+          );
+          $file_extra[] = array(
+            'key' => 'guid',
+            'value' => file_create_url($file['filepath']),
+            'attributes' => array('isPermaLink' => 'false'),
+          );
+          $file_extra = array_merge($extra, $file_extra);
+          /*
+           * The following function takes title, link, description and then
+           * all additional XML elements.  For the title we'll use the node
+           * title.  Link serves no real purpose in a podcast.  Description
+           * is overridden by the extra "subtitle" tag but we'll keep it for
+           * completeness with RSS and use the node teaser.
+          */
+          $output .= format_rss_item($item->title, $item->link, $item_text, $file_extra );
+        }
+      }
+    }
+    return $output;
+  }
+}
Index: views/itunes_plugin_style_rss.inc
===================================================================
RCS file: views/itunes_plugin_style_rss.inc
diff -N views/itunes_plugin_style_rss.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ views/itunes_plugin_style_rss.inc	28 Nov 2010 23:05:05 -0000
@@ -0,0 +1,270 @@
+<?php
+// $Id: itunes_plugin_style_rss.inc,v 1.1 2010/05/20 22:55:48 drewish Exp $
+
+/**
+ * @file
+ * Contains the RSS style plugin.
+ */
+
+/**
+ * iTunes specific style plugin to render an RSS feed.
+ *
+ * @ingroup views_style_plugins
+ */
+class itunes_plugin_style_rss extends views_plugin_style_rss {
+  function option_definition() {
+    $options = parent::option_definition();
+
+    $options['subtitle'] = array('default' => '', 'translatable' => TRUE);
+    $options['summary'] = array('default' => '', 'translatable' => TRUE);
+    $options['author'] = array('default' => '', 'translatable' => TRUE);
+    $options['copyright'] = array('default' => '', 'translatable' => TRUE);
+    $options['image_url'] = array('default' => '', 'translatable' => FALSE);
+    $options['explicit'] = array('default' => 0, 'translatable' => FALSE);
+    $options['block'] = array('default' => 0, 'translatable' => FALSE);
+    $options['owner_name'] = array('default' => '', 'translatable' => FALSE);
+    $options['owner_email'] = array('default' => '', 'translatable' => FALSE);
+    $options['categories'] = array('default' => array('', '', ''), 'translatable' => FALSE);
+    $options['keywords'] = array('default' => '', 'translatable' => TRUE);
+    $options['new_feed_url'] = array('default' => '', 'translatable' => FALSE);
+
+    return $options;
+  }
+
+  function options_form(&$form, &$form_state) {
+    $form['subtitle'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Subtitle'),
+      '#default_value' => $this->options['subtitle'],
+      '#maxlength' => 255,
+      '#description' => t("The contents of this tag are shown in the Description column in iTunes. The subtitle displays best if it is only a few words long."),
+    );
+    $form['summary'] = array(
+      '#type' => 'textarea',
+      '#title' => t('Summary'),
+      '#default_value' => $this->options['summary'],
+      '#maxlength' => 4000,
+      '#rows' => 5,
+      '#description' => t('The contents of this tag are shown in a separate window that appears when the "circled i" in the Description column is clicked. It also appears on the iTunes page for your podcast.'),
+    );
+    $form['author'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Author'),
+      '#default_value' => $this->options['author'],
+      '#maxlength' => 255,
+      '#description' => t("This is shown in the Artist column in iTunes."),
+    );
+    $form['copyright'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Copyright'),
+      '#default_value' => $this->options['copyright'],
+      '#maxlength' => 255,
+      '#description' => t('i.e. "&#xA9; 2005 John Doe"'),
+    );
+    $form['image_url'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Image URL'),
+      '#default_value' => $this->options['image_url'],
+      '#maxlength' => 255,
+      '#description' => t('This specifies the artwork for your podcast. iTunes prefers square .jpg images that are at least 300 x 300 pixels, which is different than what is specified for the standard RSS image tag. iTunes supports images in JPEG and PNG formats. The URL must end in ".jpg" or ".png". If you change your podcast’s image, also change the file’s name. iTunes may not change the image if it checks your feed and the image URL is the same.'),
+    );
+    $form['new_feed_url'] = array(
+      '#type' => 'textfield',
+      '#title' => t('New Feed URL'),
+      '#default_value' => $this->options['new_feed_url'],
+      '#maxlength' => 255,
+      '#description' => t('Podcasters occasionally need to move their feed from one location to another. To do so without losing subscribers, you must convey the change directly to all users who are subscribed to your feed. Specify the <a href="http://www.apple.com/itunes/podcasts/specs.html#changing">new feed url tag</a> here if desired.'),
+    );
+    $form['explicit'] = array(
+      '#type' => 'select', '#title' => t('Explicit'),
+      '#options' => array(0 => 'Unspecified', ITUNES_EXPLICIT_YES => 'Yes', ITUNES_EXPLICIT_CLEAN => 'Clean'),
+      '#default_value' => $this->options['explicit'],
+      '#description' => t('If select "yes", an "explicit" parental advisory graphic will appear next to your podcast artwork on the iTunes Music Store, and in the Name column in iTunes. If you select "clean", the parental advisory type is considered Clean, meaning that no explicit language or adult content is included anywhere in the episode, and a "clean" graphic will appear.'),
+    );
+    $form['block'] = array(
+      '#type' => 'checkbox', '#title' => t('Hide'),
+      '#default_value' => $this->options['block'],
+      '#description' => t('Check this to prevent the entire podcast from appearing in the iTunes Podcast directory.'),
+    );
+    $form['owner_name'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Name'),
+      '#default_value' => $this->options['owner_name'],
+      '#maxlength' => 255,
+      '#description' => t("Apple uses this information to contact the owner of the podcast for communication specifically about their podcast. It will not be publicly displayed but it may be picked up by spammers."),
+    );
+    $form['owner_email'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Email'),
+      '#default_value' => $this->options['owner_email'],
+      '#maxlength' => 255,
+    );
+    $category_options = array('' => t('- None selected -'));
+    foreach (itunes_categories() as $top_level => $children) {
+      $category_options[] = (object) array(
+        'option' => array($top_level => $top_level)
+      );
+      foreach ($children as $second_level) {
+        $category_options[] = (object) array(
+          'option' => array($top_level . '|' . $second_level => ' - ' . $second_level)
+        );
+      }
+    }
+    $form['categories'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Categories'),
+      '#collapsible' => TRUE,
+      '#description' => '<p>'. t('There are two ways to browse podcast subject categories on iTunes: click Browse in the Quick Links box or click a selection in the Category box. The former method leads to a text-based table, while the latter leads to pages that include the podcast art.') .'</p>'
+        .'<p>'. t('For placement within the older, text-based browse system, podcast feeds may list up to 3 category/subcategory pairs. (For example, "Music" counts as 1, as does "Business > Careers.") For placement within the newer browse system based on Category links, however, and for placement within the Top Podcasts lists that appear in the right column of most podcast pages, only the first category listed in the feed is used.') .'</p>',
+    );
+    $form['categories'][0] = array(
+      '#type' => 'select',
+      '#title' => t('First Category'),
+      '#default_value' => $this->options['categories'][0],
+      '#options' => $category_options,
+    );
+    $form['categories'][1] = array(
+      '#type' => 'select',
+      '#title' => t('Second Category'),
+      '#default_value' => $this->options['categories'][1],
+      '#options' => $category_options,
+    );
+    $form['categories'][2] = array(
+      '#type' => 'select',
+      '#title' => t('Third Category'),
+      '#default_value' => $this->options['categories'][2],
+      '#options' => $category_options,
+    );
+    $form['keywords'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Keywords'),
+      '#default_value' => $this->options['keywords'],
+      '#maxlength' => 4000,
+      '#description' => t('This tag allows users to search on a maximum of 12 text keywords. Use commas to separate keywords.'),
+    );
+  }
+
+  function options_validate(&$form, &$form_state) {
+    if (!empty($form_state['values']['style_options']['image_url'])) {
+      if (!valid_url($form_state['values']['style_options']['image_url'])) {
+        form_set_error('image_url', t('The image URL must be a valid URL.'));
+      }
+      else {
+        $url = parse_url($form_state['values']['style_options']['image_url']);
+        $ext = drupal_strtolower(pathinfo($url['path'], PATHINFO_EXTENSION));
+        if (!isset($url['path']) || ($ext != 'jpg' && $ext != 'png')) {
+          form_set_error('image_url', t('The URL must specify a file ending with <code>.jpg</code> or <code>.png</code>.'));
+        }
+      }
+    }
+    if (!empty($form_state['values']['style_options']['owner_email'])) {
+      if (!valid_email_address($form_state['values']['style_options']['owner_email'])) {
+        form_set_error('owner_email', t('The owner email must be a valid email address.'));
+      }
+    }
+    if (!empty($form_state['values']['style_options']['keywords'])) {
+      if (substr_count($form_state['values']['style_options']['keywords'], ',') > 11) {
+        form_set_error('keywords', t('Only 12 keywords are allowed.'));
+      }
+    }
+  }
+
+  function get_channel_elements() {
+    $extra = parent::get_channel_elements();
+
+    // Specify the namespace in this element because it is always included so
+    // we can omit it from subsequent elements.
+    $extra[] = array(
+      'namespace' => array('xmlns:itunes' => 'http://www.itunes.com/dtds/podcast-1.0.dtd'),
+      'key' => 'itunes:explicit',
+      'value' => itunes_explicit($this->options['explicit']),
+    );
+    if ($this->options['copyright']) {
+      $extra[] = array(
+        'key' => 'copyright',
+        'value' => $this->options['copyright'],
+      );
+    }
+    if ($this->options['author']) {
+      $extra[] = array(
+        'key' => 'itunes:author',
+        'value' => $this->options['author'],
+      );
+    }
+    if ($this->options['image_url']) {
+      $extra[] = array(
+        'key' => 'itunes:image',
+        'attributes' => array('href' => $this->options['image_url']),
+        'value' => NULL,
+      );
+    }
+    if ($this->options['new_feed_url']) {
+      $extra[] = array(
+        'key' => 'itunes:new-feed-url',
+        'value' => $this->options['new_feed_url'],
+      );
+    }
+    if ($this->options['subtitle']) {
+      $extra[] = array(
+        'key' => 'itunes:subtitle',
+        'value' => $this->options['subtitle'],
+      );
+    }
+    if ($this->options['summary']) {
+      $extra[] = array(
+        'key' => 'itunes:summary',
+        'value' => $this->options['summary'],
+      );
+    }
+    if ($this->options['block']) {
+      $extra[] = array(
+        'key' => 'itunes:block',
+        'value' => 'yes',
+      );
+    }
+    if ($this->options['owner_name'] && $this->options['owner_email']) {
+      $extra[] = array(
+        'key' => 'itunes:owner',
+        'value' => array(
+          array(
+            'key' => 'itunes:name',
+            'value' => $this->options['owner_name'],
+          ),
+          array(
+            'key' => 'itunes:email',
+            'value' => $this->options['owner_email'],
+          ),
+        ),
+      );
+    }
+    // We've got up to three categories which can consist of a parent and
+    // child stored as "Parent" or "Parent|Child". We need to split them up and
+    // create nested elements.
+    foreach ($this->options['categories'] as $category) {
+      if ($category) {
+        $parts = explode('|', $category);
+        $element = array(
+          'key' => 'itunes:category',
+          'attributes' => array('text' => $parts[0]),
+          'value' => NULL,
+        );
+        if (isset($parts[1])) {
+          $element['value'][] = array(
+            'key' => 'itunes:category',
+            'attributes' => array('text' => $parts[1]),
+            'value' => NULL,
+          );
+        }
+        $extra[] = $element;
+      }
+    }
+    if ($this->options['keywords']) {
+      $extra[] = array(
+        'key' => 'itunes:keywords',
+        'value' => $this->options['keywords'],
+      );
+    }
+
+    return $extra;
+  }
+}
\ No newline at end of file
