Exemple #1
0
function override_feed_language($feed)
{
    add_filter('pre_option_rss_language', function ($language) use($feed) {
        $podcast = Model\Podcast::get_instance();
        return apply_filters('podlove_feed_language', $podcast->language ? $podcast->language : $language);
    });
}
Exemple #2
0
    /**
     * Insert HTML meta tags into site head.
     *
     * @todo  caching
     * @todo  let user choose what's in og:description: subtitle, excerpt, ...
     * @todo  handle multiple releases per episode
     */
    public function insert_open_graph_metadata()
    {
        $post_id = get_the_ID();
        if (!$post_id) {
            return;
        }
        $episode = \Podlove\Model\Episode::find_one_by_post_id($post_id);
        if (!$episode) {
            return;
        }
        $podcast = Model\Podcast::get_instance();
        // determine image
        $cover_art_url = $episode->get_cover_art();
        if (!$cover_art_url) {
            $cover_art_url = $podcast->cover_image;
        }
        ?>
			<meta property="og:type" content="website" />
			<meta property="og:site_name" content="<?php 
        echo $episode->full_title();
        ?>
" />
			<meta property="og:title" content="<?php 
        the_title();
        ?>
" />
			<?php 
        if ($cover_art_url) {
            ?>
				<meta property="og:image" content="<?php 
            echo $cover_art_url;
            ?>
" />
			<?php 
        }
        ?>
			<meta property="og:url" content="<?php 
        the_permalink();
        ?>
" />
			<?php 
        $media_files = $episode->media_files();
        ?>
			<?php 
        foreach ($media_files as $media_file) {
            ?>
				<meta property="og:audio" content="<?php 
            echo $media_file->get_file_url();
            ?>
" />
				<meta property="og:audio:type" content="<?php 
            echo $media_file->episode_asset()->file_type()->mime_type;
            ?>
" />
			<?php 
        }
        ?>
			<?php 
    }
Exemple #3
0
    function page()
    {
        ?>
		<div class="wrap">
			<div id="icon-options-general" class="icon32"></div>
			<h2><?php 
        echo __('Podcast Settings');
        ?>
</h2>

			<form method="post" action="options.php">
				<?php 
        settings_fields(Podcast::$pagehook);
        ?>

				<?php 
        $podcast = \Podlove\Model\Podcast::get_instance();
        $form_attributes = array('context' => 'podlove_podcast', 'form' => false);
        \Podlove\Form\build_for($podcast, $form_attributes, function ($form) {
            $wrapper = new \Podlove\Form\Input\TableWrapper($form);
            $podcast = $form->object;
            $wrapper->string('title', array('label' => __('Title', 'podlove'), 'description' => __('', 'podlove'), 'html' => array('class' => 'regular-text required')));
            $wrapper->string('subtitle', array('label' => __('Subtitle', 'podlove'), 'description' => __('The subtitle is used by iTunes.', 'podlove'), 'html' => array('class' => 'regular-text')));
            $wrapper->text('summary', array('label' => __('Summary', 'podlove'), 'description' => __('A couple of sentences describing the podcast.', 'podlove'), 'html' => array('rows' => 5, 'cols' => 40)));
            $wrapper->string('slug', array('label' => __('Mnemonic', 'podlove'), 'description' => __('The abbreviation for your podcast. Commonly the initials of the title.', 'podlove'), 'html' => array('class' => 'regular-text required')));
            $wrapper->image('cover_image', array('label' => __('Cover Art URL', 'podlove'), 'description' => __('JPEG or PNG. At least 1400 x 1400 pixels.', 'podlove'), 'html' => array('class' => 'regular-text'), 'image_width' => 300, 'image_height' => 300));
            $wrapper->string('author_name', array('label' => __('Author Name', 'podlove'), 'description' => __('Publicly displayed in Podcast directories.', 'podlove'), 'html' => array('class' => 'regular-text')));
            $wrapper->string('owner_name', array('label' => __('Owner Name', 'podlove'), 'description' => __('Used by iTunes and other Podcast directories to contact you.', 'podlove'), 'html' => array('class' => 'regular-text')));
            $wrapper->string('owner_email', array('label' => __('Owner Email', 'podlove'), 'description' => __('Used by iTunes and other Podcast directories to contact you.', 'podlove'), 'html' => array('class' => 'regular-text')));
            $wrapper->string('keywords', array('label' => __('Keywords', 'podlove'), 'description' => __('List of keywords. Separate with commas.', 'podlove'), 'html' => array('class' => 'regular-text')));
            $wrapper->select('category_1', array('label' => __('iTunes Categories', 'podlove'), 'description' => '', 'type' => 'select', 'options' => \Podlove\Itunes\categories()));
            $wrapper->select('category_2', array('label' => '', 'description' => '', 'type' => 'select', 'options' => \Podlove\Itunes\categories()));
            $wrapper->select('category_3', array('label' => '', 'description' => '<br>' . __('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 and Top Episodes lists that appear in the right column of most podcast pages, only the first category listed in the feed is used.') . ' (<a href="http://www.apple.com/itunes/podcasts/specs.html#category" target="_blank">http://www.apple.com/itunes/podcasts/specs.html#category</a>)', 'options' => \Podlove\Itunes\categories()));
            $wrapper->select('language', array('label' => __('Language', 'podlove'), 'description' => __('', 'podlove'), 'default' => get_bloginfo('language'), 'options' => \Podlove\Locale\locales()));
            $wrapper->select('explicit', array('label' => __('Explicit Content?', 'podlove'), 'description' => __('', 'podlove'), 'type' => 'checkbox', 'options' => array(0 => 'no', 1 => 'yes', 2 => 'clean')));
            $wrapper->string('media_file_base_uri', array('label' => __('Media File Base URL', 'podlove'), 'description' => __('Example: http://cdn.example.com/pod/', 'podlove'), 'html' => array('class' => 'regular-text required')));
            $artwork_options = array('0' => __('None', 'podlove'), 'manual' => __('Manual Entry', 'podlove'));
            $episode_assets = Model\EpisodeAsset::all();
            foreach ($episode_assets as $episode_asset) {
                $file_type = $episode_asset->file_type();
                if ($file_type && $file_type->type === 'image') {
                    $artwork_options[$episode_asset->id] = sprintf(__('Media File: %s', 'podlove'), $episode_asset->title);
                }
            }
            $wrapper->select('supports_cover_art', array('label' => __('Episode Artwork Media File', 'podlove'), 'options' => $artwork_options));
        });
        ?>
				
			</form>
		</div>	
		<?php 
    }
Exemple #4
0
    public function __construct($feed_slug)
    {
        add_action('atom_ns', function () {
            echo 'xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"';
        });
        add_filter('feed_link', function ($output, $feed) use($feed_slug) {
            return get_bloginfo('url') . '/feed/' . $feed_slug . '/';
        }, 10, 2);
        $podcast = Model\Podcast::get_instance();
        $feed = Model\Feed::find_one_by_slug($feed_slug);
        $episode_asset = $feed->episode_asset();
        $file_type = $episode_asset->file_type();
        add_filter('podlove_feed_enclosure', function ($enclosure, $enclosure_url, $enclosure_file_size, $mime_type) {
            return sprintf('<link rel="enclosure" href="%s" length="%s" type="%s"/>', $enclosure_url, $enclosure_file_size, $mime_type);
        }, 10, 4);
        mute_feed_title();
        override_feed_title($feed);
        override_feed_language($feed);
        override_feed_head('atom_head', $podcast, $feed, $file_type);
        override_feed_entry('atom_entry', $podcast, $feed, $file_type);
        add_action('atom_head', function () use($podcast, $feed, $file_type) {
            ?>
			<link rel="self" type="application/atom+xml" title="<?php 
            echo $feed->title_for_discovery();
            ?>
" href="<?php 
            echo $feed->get_subscribe_url();
            ?>
" />
			<?php 
            $feeds = Model\Feed::all();
            foreach ($feeds as $other_feed) {
                if ($other_feed->id !== $feed->id) {
                    ?>
					<link rel="alternate" type="application/atom+xml" title="<?php 
                    echo $other_feed->title_for_discovery();
                    ?>
" href="<?php 
                    echo $other_feed->get_subscribe_url();
                    ?>
" />
					<?php 
                }
            }
        }, 9);
        $this->do_feed($feed);
    }
Exemple #5
0
 public function __construct($feed_slug)
 {
     add_action('rss2_ns', function () {
         echo 'xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"';
     });
     $podcast = Model\Podcast::get_instance();
     $feed = Model\Feed::find_one_by_slug($feed_slug);
     $episode_asset = $feed->episode_asset();
     $file_type = $episode_asset->file_type();
     add_filter('podlove_feed_enclosure', function ($enclosure, $enclosure_url, $enclosure_file_size, $mime_type) {
         return sprintf('<enclosure url="%s" length="%s" type="%s" />', $enclosure_url, $enclosure_file_size, $mime_type);
     }, 10, 4);
     mute_feed_title();
     override_feed_title($feed);
     override_feed_language($feed);
     override_feed_head('rss2_head', $podcast, $feed, $file_type);
     override_feed_entry('rss2_item', $podcast, $feed, $file_type);
     $this->do_feed($feed);
 }
Exemple #6
0
/**
 * Find and run migration for given version number.
 *
 * @todo  move migrations into separate files
 * 
 * @param  int $version
 */
function run_migrations_for_version($version)
{
    global $wpdb;
    switch ($version) {
        case 2:
            $sql = sprintf('ALTER TABLE `%s` ADD COLUMN `chapters` TEXT AFTER `cover_art`', \Podlove\Model\Release::table_name());
            $wpdb->query($sql);
            break;
        case 3:
            $sql = sprintf('ALTER TABLE `%s` ADD COLUMN `format` VARCHAR(255) AFTER `slug`', \Podlove\Model\Feed::table_name());
            $wpdb->query($sql);
            break;
        case 4:
            $sql = sprintf('ALTER TABLE `%s` ADD COLUMN `title` VARCHAR(255) AFTER `id`', \Podlove\Model\EpisodeAsset::table_name());
            $wpdb->query($sql);
            break;
        case 5:
            \Podlove\Modules\Base::activate('podlove_web_player');
            break;
        case 6:
            // title column is "int" for some people. this migration fixes that
            $sql = sprintf('SHOW COLUMNS FROM `wp_podlove_medialocation` WHERE Field = "title"', \Podlove\Model\EpisodeAsset::table_name());
            $row = $wpdb->get_row($sql);
            if (strtolower(substr($row->Type, 0, 3)) === 'int') {
                $wpdb->query(sprintf('UPDATE `%s` SET title = NULL', \Podlove\Model\EpisodeAsset::table_name()));
                $wpdb->query(sprintf('ALTER TABLE `%s` MODIFY COLUMN `title` VARCHAR(255)', \Podlove\Model\EpisodeAsset::table_name()));
            }
            break;
        case 7:
            // move language from feed to show
            $sql = sprintf('ALTER TABLE `%s` ADD COLUMN `language` VARCHAR(255) AFTER `summary`', \Podlove\Model\Show::table_name());
            $wpdb->query($sql);
            $sql = sprintf('ALTER TABLE `%s` DROP COLUMN `language`', \Podlove\Model\Feed::table_name());
            $wpdb->query($sql);
            break;
        case 8:
            $sql = sprintf('ALTER TABLE `%s` ADD COLUMN `supports_cover_art` INT', \Podlove\Model\Show::table_name());
            $wpdb->query($sql);
            break;
        case 9:
            // huge architecture migration
            // assume first show will be blueprint for the podcast
            $show = $wpdb->get_row(sprintf('SELECT * FROM %s LIMIT 1', $wpdb->prefix . 'podlove_show'), ARRAY_A);
            $show_id = $show['id'];
            // On my local machine the migration runs twice.
            // This is a quick fix. caveat: someone who has no show defined
            // will need to uninstall the plugin. That seems acceptable.
            if (!$show_id) {
                return;
            }
            // all releases of this show will be converted to episodes
            $releases = $wpdb->get_results(sprintf('
					SELECT
						E.post_id, R.episode_id, R.active, R.enable, R.slug, R.duration, R.cover_art, R.chapters
					FROM 
						%s R
						INNER JOIN %s E ON R.episode_id = E.id
					WHERE
						R.show_id = "%s"
					', $wpdb->prefix . 'podlove_release', $wpdb->prefix . 'podlove_episode', $show_id), ARRAY_A);
            // write show settings to podcast
            $podcast = \Podlove\Model\Podcast::get_instance();
            foreach ($show as $key => $value) {
                $podcast->{$key} = $value;
            }
            $podcast->save();
            // rebuild show table
            \Podlove\Model\Show::destroy();
            \Podlove\Model\Show::build();
            // rebuild episodes table
            \Podlove\Model\Episode::destroy();
            \Podlove\Model\Episode::build();
            foreach ($releases as $release) {
                $episode = new \Podlove\Model\Episode();
                foreach ($release as $key => $value) {
                    if (!in_array($key, array('episode_id'))) {
                        $episode->{$key} = $value;
                    }
                }
                $episode->save();
            }
            // clean feed table
            $sql = sprintf('DELETE FROM `%s` WHERE `show_id` != "%s"', \Podlove\Model\Feed::table_name(), $show_id);
            $wpdb->query($sql);
            $sql = sprintf('ALTER TABLE `%s` DROP COLUMN `show_id`', \Podlove\Model\Feed::table_name());
            $wpdb->query($sql);
            // fix mediafile table
            $sql = sprintf('ALTER TABLE `%s` CHANGE `release_id` `episode_id` INT', \Podlove\Model\MediaFile::table_name());
            $wpdb->query($sql);
            // remove suffix
            $sql = sprintf('ALTER TABLE `%s` DROP COLUMN `suffix`', \Podlove\Model\EpisodeAsset::table_name());
            $wpdb->query($sql);
            // add more default formats
            $default_formats = array(array('name' => 'PDF Document', 'type' => 'ebook', 'mime_type' => 'application/pdf', 'extension' => 'pdf'), array('name' => 'ePub Document', 'type' => 'ebook', 'mime_type' => 'application/epub+zip', 'extension' => 'epub'), array('name' => 'PNG Image', 'type' => 'image', 'mime_type' => 'image/png', 'extension' => 'png'), array('name' => 'JPEG Image', 'type' => 'image', 'mime_type' => 'image/jpeg', 'extension' => 'jpg'));
            foreach ($default_formats as $format) {
                $f = new Model\FileType();
                foreach ($format as $key => $value) {
                    $f->{$key} = $value;
                }
                $f->save();
            }
            // update assistant
            $assistant = \Podlove\Modules\EpisodeAssistant\Episode_Assistant::instance();
            $template = $assistant->get_module_option('title_template');
            $template = str_replace('%show_slug%', '%podcast_slug%', $template);
            $assistant->update_module_option('title_template', $template);
            // update media locations
            $media_locations = \Podlove\Model\EpisodeAsset::all();
            foreach ($media_locations as $media_location) {
                $media_location->url_template = str_replace('%suffix%', '', $media_location->url_template);
                $media_location->save();
            }
            break;
        case 10:
            $sql = sprintf('ALTER TABLE `%s` ADD COLUMN `summary` TEXT', \Podlove\Model\Episode::table_name());
            $wpdb->query($sql);
            break;
        case 11:
            $sql = sprintf('ALTER TABLE `%s` ADD COLUMN `downloadable` INT', \Podlove\Model\EpisodeAsset::table_name());
            $wpdb->query($sql);
            break;
        case 12:
            $sql = sprintf('UPDATE `%s` SET `downloadable` = 1', \Podlove\Model\EpisodeAsset::table_name());
            $wpdb->query($sql);
            break;
        case 13:
            $opus = array('name' => 'Opus Audio', 'type' => 'audio', 'mime_type' => 'audio/opus', 'extension' => 'opus');
            $f = new \Podlove\Model\FileType();
            foreach ($opus as $key => $value) {
                $f->{$key} = $value;
            }
            $f->save();
            break;
        case 14:
            $sql = sprintf('ALTER TABLE `%s` RENAME TO `%s`', $wpdb->prefix . 'podlove_medialocation', \Podlove\Model\EpisodeAsset::table_name());
            $wpdb->query($sql);
            break;
        case 15:
            $sql = sprintf('ALTER TABLE `%s` CHANGE `media_location_id` `episode_asset_id` INT', \Podlove\Model\MediaFile::table_name());
            $wpdb->query($sql);
            break;
        case 16:
            $sql = sprintf('ALTER TABLE `%s` CHANGE `media_location_id` `episode_asset_id` INT', \Podlove\Model\Feed::table_name());
            $wpdb->query($sql);
            break;
        case 17:
            $sql = sprintf('ALTER TABLE `%s` RENAME TO `%s`', $wpdb->prefix . 'podlove_mediaformat', \Podlove\Model\FileType::table_name());
            $wpdb->query($sql);
            break;
        case 18:
            $sql = sprintf('ALTER TABLE `%s` CHANGE `media_format_id` `file_type_id` INT', \Podlove\Model\EpisodeAsset::table_name());
            $wpdb->query($sql);
            break;
    }
}
Exemple #7
0
 /**
  * Build public url where the feed can be subscribed at.
  *
  * @return string
  */
 public function get_subscribe_url()
 {
     $podcast = \Podlove\Model\Podcast::get_instance();
     $url = sprintf('%s/feed/%s/', get_bloginfo('url'), $this->slug);
     return apply_filters('podlove_subscribe_url', $url);
 }
Exemple #8
0
/**
 * Provides shortcode to display web player.
 *
 * Right now there is only audio support.
 *
 * Usage:
 * 	[podlove-web-player]
 * 	
 * @param  array $options
 * @return string
 */
function webplayer_shortcode($options)
{
    global $post;
    $episode = Model\Episode::find_or_create_by_post_id($post->ID);
    $podcast = Model\Podcast::get_instance();
    $formats_data = get_option('podlove_webplayer_formats');
    if (!count($formats_data)) {
        return;
    }
    $available_formats = array();
    $audio_formats = array('mp3', 'mp4', 'ogg');
    foreach ($audio_formats as $audio_format) {
        $episode_asset = Model\EpisodeAsset::find_by_id($formats_data['audio'][$audio_format]);
        if (!$episode_asset) {
            continue;
        }
        $media_file = Model\MediaFile::find_by_episode_id_and_episode_asset_id($episode->id, $episode_asset->id);
        if ($media_file) {
            $available_formats[] = sprintf('%s="%s"', $audio_format, $media_file->get_file_url());
        }
    }
    $chapters = '';
    if ($episode->chapters) {
        $chapters = 'chapters="_podlove_chapters"';
    }
    return do_shortcode('[podloveaudio ' . implode(' ', $available_formats) . ' ' . $chapters . ']');
}
Exemple #9
0
    private function form_template($episode_asset, $action, $button_text = NULL)
    {
        $raw_formats = \Podlove\Model\FileType::all();
        $formats = array();
        foreach ($raw_formats as $format) {
            $formats[$format->id] = array('title' => $format->title(), 'extension' => $format->extension);
        }
        $format_optionlist = array_map(function ($f) {
            return $f['title'];
        }, $formats);
        $form_args = array('context' => 'podlove_episode_asset', 'hidden' => array('episode_asset' => $episode_asset->id, 'action' => $action), 'attributes' => array('id' => 'podlove_episode_assets'));
        \Podlove\Form\build_for($episode_asset, $form_args, function ($form) use($format_optionlist) {
            $f = new \Podlove\Form\Input\TableWrapper($form);
            $f->select('file_type_id', array('label' => __('File Format', 'podlove'), 'description' => __('', 'podlove'), 'options' => $format_optionlist));
            $f->string('title', array('label' => __('Title', 'podlove'), 'description' => __('Description to identify the media file type to the user in download buttons.', 'podlove'), 'html' => array('class' => 'regular-text required')));
            $f->string('url_template', array('label' => __('URL Template', 'podlove'), 'description' => sprintf(__('Preview: %s'), '<span class="url_template_preview"></span><br/>', 'podlove'), 'html' => array('class' => 'large-text required'), 'default' => '%media_file_base_url%%episode_slug%.%format_extension%'));
            $f->checkbox('downloadable', array('label' => __('Downloadable', 'podlove'), 'description' => sprintf('Allow downloads for users.', 'podlove'), 'default' => true));
        });
        // hidden fields for JavaScript
        ?>
		<input type="hidden" id="podlove_show_media_file_base_uri" value="<?php 
        echo Model\Podcast::get_instance()->media_file_base_uri;
        ?>
">
		<?php 
    }
Exemple #10
0
    public function modal_box_html()
    {
        $podcast = Model\Podcast::get_instance();
        $episode_assets = Model\EpisodeAsset::all();
        $episode_asset = $episode_assets[0];
        $podcast_data = array('slug' => $podcast->slug, 'name' => $podcast->title, 'next_number' => $this->guess_next_episode_id_for_show(), 'base_url' => $podcast->media_file_base_uri, 'episode_asset' => array('template' => $episode_asset->url_template));
        ?>
		<div id="new-episode-modal" class="hidden wrap" title="Create New Episode">
			<div class="hidden" id="new-episode-podcast-data"><?php 
        echo json_encode($podcast_data);
        ?>
</div>
			<p>
				<div id="titlediv">
					<p>
						<strong>Episode Number</strong>
						<input type="text" name="episode_number" value="<?php 
        echo $this->guess_next_episode_id_for_show();
        ?>
" class="really-huge-text episode_number" autocomplete="off">
					</p>
					<p>
						<strong>Episode Title</strong>
						<input type="text" name="episode_title" value="" class="really-huge-text episode_title" autocomplete="off">
					</p>
					<p class="media_file_info result">
						<strong>Media Files</strong>
						<span class="url">Loading ...</span>
					</p>
					<p class="post_info result">
						<strong>Post Title</strong>
						<span class="post_title" data-template="<?php 
        echo $this->get_module_option('title_template', '%podcast_slug%%episode_number% %episode_title%');
        ?>
">Loading ...</span>
					</p>
				</div>
			</p>
		</div>

		<style type="text/css">
		#new-episode-modal .media_file_info, #new-episode-modal .post_info {
			color: #666;
		}

		#new-episode-modal p.result strong {
			display: inline-block;
			width: 115px;
		}

		#episode_file_slug {
			cursor: pointer;
			font-style: italic;
			color: black;
		}

		#episode_file_slug input {
			width: 70px;
			-webkit-border-radius: 3px;
			border-radius: 3px;
			border-width: 1px;
			border-style: solid;
			border-color: #DFDFDF;
		}

		input.really-huge-text {
			padding: 3px 8px;
			font-size: 1.7em;
			line-height: 100%;
			width: 100%;
		}
		</style>
		<?php 
    }
Exemple #11
0
    public static function validate_podcast_files()
    {
        $podcast = Model\Podcast::get_instance();
        $podcast_warnings = self::get_podcast_setting_warnings();
        if (!in_array('curl', get_loaded_extensions())) {
            ?>
			<div class="error">
				<p>
					<strong>ERROR: </strong>You need the <strong>curl PHP extension</strong> for Podlove to run properly.
					<br>
					If you think you can do it yourself, have a look at <a href="http://stackoverflow.com/questions/1347146/how-to-enable-curl-in-php">these instructions on how to enable curl in PHP</a>.
				</p>
			</div>
			<?php 
        }
        ?>
		<div id="validation">

			<?php 
        if (count($podcast_warnings)) {
            ?>
				<style type="text/css">
				#podcast_warnings {
					color: #333333;
					background-color: #FFEBE8;
					border: 1px solid #CC0000;
					border-radius: 3px;
					padding: 0.4em 1.0em;
					margin: 10px 0px;
				}
				#podcast_warnings h4 {
					margin: 10px 0px;
				}
				</style>
				<div id="podcast_warnings">
					<h4><?php 
            echo __('Critical Notes');
            ?>
</h4>
					<?php 
            foreach ($podcast_warnings as $warning) {
                ?>
						<div class="line">
							<?php 
                echo $warning;
                ?>
							<a href="<?php 
                echo admin_url('admin.php?page=podlove_settings_podcast_handle');
                ?>
">
								<?php 
                echo __('go fix it', 'podlove');
                ?>
							</a>
						</div>
					<?php 
            }
            ?>
				</div>
			<?php 
        }
        ?>

			<?php 
        $episodes = Model\Episode::all();
        $assets = Model\EpisodeAsset::all();
        $header = array(__('Episode', 'podlove'));
        foreach ($assets as $asset) {
            $header[] = $asset->title;
        }
        $header[] = __('Status', 'podlove');
        // $header[] = ''; // buttons
        define('ASSET_STATUS_OK', '<span style="color: green">✓</span>');
        define('ASSET_STATUS_INACTIVE', '—');
        define('ASSET_STATUS_ERROR', '<span style="color: red">!!!</span>');
        ?>

			<h4><?php 
        echo $podcast->title;
        ?>
</h4>

			<table>
				<thead>
					<tr>
						<?php 
        foreach ($header as $column_head) {
            ?>
							<th><?php 
            echo $column_head;
            ?>
</th>
						<?php 
        }
        ?>
					</tr>
				</thead>
				<tbody>
					<?php 
        foreach ($episodes as $episode) {
            ?>
						<?php 
            $post_id = $episode->post_id;
            $post = get_post($post_id);
            // skip deleted podcasts
            if (!in_array($post->post_status, array('draft', 'publish'))) {
                continue;
            }
            // skip versions
            if ($post->post_type != 'podcast') {
                continue;
            }
            ?>
						<tr>
							<td>
								<a href="<?php 
            echo get_edit_post_link($episode->post_id);
            ?>
"><?php 
            echo $episode->slug;
            ?>
</a>
							</td>
							<?php 
            $media_files = $episode->media_files();
            ?>
							<?php 
            foreach ($assets as $asset) {
                ?>
								<td style="text-align: center; font-weight: bold; font-size: 20px">
									<?php 
                $files = array_filter($media_files, function ($file) use($asset) {
                    return $file->episode_asset_id == $asset->id;
                });
                $file = array_pop($files);
                if (!$file) {
                    echo ASSET_STATUS_INACTIVE;
                } elseif ($file->size > 0) {
                    echo ASSET_STATUS_OK;
                } else {
                    echo ASSET_STATUS_ERROR;
                }
                ?>
								</td>
							<?php 
            }
            ?>
							<td>
								<?php 
            echo $post->post_status;
            ?>
							</td>
							<!-- <td>buttons</td> -->
						</tr>
					<?php 
        }
        ?>
				</tbody>
			</table>
		</div>

		<style type="text/css">
		#validation h4 {
			font-size: 20px;
		}

		#validation .episode {
			margin: 0 0 15px 0;
		}

		#validation .slug {
			font-size: 18px;
			margin: 0 0 5px 0;
		}

		#validation .warning {
			color: maroon;
		}

		#validation .error {
			color: red;
		}
		</style>
		<?php 
    }