/**
  * Fetch episodes for the list
  */
 public function latest_episodes($number_of_episodes = 10, $orderby = "post_date", $order = "DESC")
 {
     global $wpdb;
     $podcasts = $this->podcasts();
     // sanitize order
     $order = $order == 'DESC' ? 'DESC' : 'ASC';
     // sanitize orderby
     $valid_orderby = ['post_date', 'post_title', 'ID', 'comment_count'];
     $orderby = in_array($orderby, $valid_orderby) ? $orderby : 'post_date';
     // Generate mySQL Query
     $subqueries = [];
     foreach ($podcasts as $podcast_key => $podcast) {
         $subqueries[] = $podcast->with_blog_scope(function () use($podcast) {
             global $wpdb;
             $query = "(SELECT p.ID, p.post_title, p.post_date, b.blog_id FROM " . $wpdb->posts . " p, " . $wpdb->blogs . " b\n";
             $query .= "WHERE p.post_type = 'podcast'";
             $query .= "AND p.post_status = 'publish'";
             $query .= "AND b.blog_id = " . $podcast->get_blog_id() . ")";
             return $query;
         });
     }
     $query = implode("UNION\n", $subqueries) . " ORDER BY {$orderby} {$order} LIMIT 0, " . (int) $number_of_episodes;
     $recent_posts = $wpdb->get_results($query);
     $episodes = [];
     foreach ($recent_posts as $post) {
         switch_to_blog($post->blog_id);
         if ($episode = \Podlove\Model\Episode::find_one_by_post_id($post->ID)) {
             $episodes[] = new \Podlove\Template\Episode($episode);
         }
         restore_current_blog();
     }
     return $episodes;
 }
 /**
  * Insert HTML meta tags into site head.
  */
 public function insert_twitter_card_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();
     $cover_art_url = $episode->cover_art_with_fallback()->url();
     // define meta tags
     $data = array(array('name' => 'twitter:card', 'content' => 'summary'), array('name' => 'twitter:url', 'content' => get_permalink()), array('name' => 'twitter:title', 'content' => get_the_title()), array('name' => 'twitter:description', 'content' => $episode->description()));
     if ($site = $this->get_module_option('site')) {
         $data[] = array('name' => 'twitter:site', 'content' => $site);
     }
     if ($creator = $this->get_module_option('creator')) {
         $data[] = array('name' => 'twitter:creator', 'content' => $creator);
     }
     if ($cover_art_url) {
         $data[] = array('name' => 'twitter:image', 'content' => $cover_art_url);
     }
     // print meta tags
     $dom = new DomDocumentFragment();
     foreach ($data as $meta_element) {
         $element = $dom->createElement('meta');
         foreach ($meta_element as $attribute => $value) {
             $element->setAttribute($attribute, $value);
         }
         $dom->appendChild($element);
     }
     echo $dom;
 }
function podlove_episode_license_add_js()
{
    $episode = Model\Episode::find_or_create_by_post_id(get_the_ID());
    ?>
	<script type="text/javascript">
	PODLOVE.License({
		plugin_url: "<?php 
    echo \Podlove\PLUGIN_URL;
    ?>
",

		locales:  JSON.parse('<?php 
    echo json_encode(\Podlove\License\locales_cc());
    ?>
'),
		versions: JSON.parse('<?php 
    echo json_encode(\Podlove\License\version_per_country_cc());
    ?>
'),
		license:  JSON.parse('<?php 
    echo json_encode(\Podlove\Model\License::get_license_from_url($episode->license_url));
    ?>
'),

		license_name_field_id: '#_podlove_meta_license_name',
		license_url_field_id:  '#_podlove_meta_license_url'
	});
	</script>
	<?php 
}
 public function analytics_downloads_per_day()
 {
     \Podlove\Feeds\check_for_and_do_compression('text/plain');
     $episode_id = isset($_GET['episode']) ? (int) $_GET['episode'] : 0;
     $cache = \Podlove\Cache\TemplateCache::get_instance();
     echo $cache->cache_for('podlove_analytics_dpd_' . $episode_id, function () use($episode_id) {
         global $wpdb;
         $episode_cond = "";
         if ($episode_id) {
             $episode_cond = " AND episode_id = {$episode_id}";
         }
         $sql = "SELECT COUNT(*) downloads, post_title, access_date, episode_id, post_id\n\t\t\t\t\tFROM (\n\t\t\t\t\t\tSELECT\n\t\t\t\t\t\t\tmedia_file_id, accessed_at, DATE(accessed_at) access_date, episode_id\n\t\t\t\t\t\tFROM\n\t\t\t\t\t\t\t" . Model\DownloadIntent::table_name() . " di \n\t\t\t\t\t\t\tINNER JOIN " . Model\MediaFile::table_name() . " mf ON mf.id = di.media_file_id\n\t\t\t\t\t\tWHERE 1 = 1 {$episode_cond}\n\t\t\t\t\t\tGROUP BY media_file_id, request_id, access_date\n\t\t\t\t\t) di\n                    INNER JOIN " . Model\Episode::table_name() . " e ON episode_id = e.id\n\t\t\t\t\tINNER JOIN {$wpdb->posts} p ON e.post_id = p.ID\n\t\t\t\t\tWHERE accessed_at > p.post_date_gmt\n\t\t\t\t\tGROUP BY access_date, episode_id";
         $results = $wpdb->get_results($sql, ARRAY_N);
         $release_date = min(array_column($results, 2));
         $csv = '"downloads","title","date","episode_id","post_id","days"' . "\n";
         foreach ($results as $row) {
             $row[1] = '"' . str_replace('"', '""', $row[1]) . '"';
             // quote & escape title
             $row[] = date_diff(date_create($release_date), date_create($row[2]))->format('%a');
             $csv .= implode(",", $row) . "\n";
         }
         return $csv;
     }, 3600);
     exit;
 }
 public function batch_enable()
 {
     if (!isset($_REQUEST['episode_asset'])) {
         return;
     }
     $podcast = Model\Podcast::get();
     $asset = Model\EpisodeAsset::find_by_id($_REQUEST['episode_asset']);
     $episodes = Model\Episode::all();
     foreach ($episodes as $episode) {
         $post_id = $episode->post_id;
         $post = get_post($post_id);
         // skip deleted podcasts
         if (!in_array($post->post_status, array('pending', 'draft', 'publish', 'future'))) {
             continue;
         }
         // skip versions
         if ($post->post_type != 'podcast') {
             continue;
         }
         $file = Model\MediaFile::find_by_episode_id_and_episode_asset_id($episode->id, $asset->id);
         if ($file === NULL) {
             $file = new Model\MediaFile();
             $file->episode_id = $episode->id;
             $file->episode_asset_id = $asset->id;
             $file->save();
         }
     }
     $this->redirect('index', NULL, array('message' => 'media_file_batch_enabled_notice'));
 }
    public function widget($args, $instance)
    {
        $number_of_episodes = is_numeric($instance['number_of_episodes']) ? $instance['number_of_episodes'] : 10;
        // Fallback for old browsers that allow a non-numeric string to be entered in the "number_of_episodes" field
        $episodes = array_slice(Episode::find_all_by_time(['post_status' => ['private', 'publish']]), 0, $number_of_episodes);
        echo $args['before_widget'];
        if (!empty($instance['title'])) {
            echo $args['before_title'] . apply_filters('widget_title', $instance['title']) . $args['after_title'];
        }
        echo "<ul style='list-style-type: none;'>";
        foreach ($episodes as $episode) {
            $post = get_post($episode->post_id);
            $episode_duration = new \Podlove\Duration($episode->duration);
            ?>
				<li>
					<?php 
            if ($instance['show_image']) {
                ?>
					<img src="<?php 
                echo $episode->cover_art_with_fallback()->setWidth(400)->url();
                ?>
" alt="<?php 
                echo $post->post_title;
                ?>
" style="width: 20%; vertical-align: top; margin-right: 2%;"/>
					<div style="display: inline-block; width: 75%;">
					<?php 
            }
            ?>
					<p>
						<a href="<?php 
            echo post_permalink($episode->post_id);
            ?>
"><?php 
            echo $post->post_title;
            ?>
</a><br />
						<i class="podlove-icon-calendar"></i> <?php 
            echo get_the_date(get_option('date_format'), $episode->post_id);
            ?>
						<?php 
            if ($instance['show_duration']) {
                echo "<br /><i class='podlove-icon-time'></i> " . $episode_duration->get('human-readable');
            }
            ?>
					</p>
					<?php 
            if ($instance['show_image']) {
                ?>
					</div>
					<?php 
            }
            ?>
				</li>
			<?php 
        }
        echo "</ul>";
        echo $args['after_widget'];
    }
Example #7
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 
    }
 /**
  * List of Related Episodes
  * 
  * @accessor
  * @dynamicAccessor episode.relatedEpisodes
  */
 public static function accessorRelatedEpisodes($return, $method_name, $episode, $post, $args = array())
 {
     $episodes = array();
     foreach (EpisodeRelation::get_related_episodes($episode->id, ['only_published' => true]) as $related_episode) {
         $episodes[] = new Template\Episode(Model\Episode::find_by_id($related_episode->id));
     }
     return $episodes;
 }
 /**
  * List of Related Episodes
  * 
  * @accessor
  * @dynamicAccessor episode.relatedEpisodes
  */
 public static function accessorRelatedEpisodes($return, $method_name, $episode, $post, $args = array())
 {
     $episodes = array();
     foreach (EpisodeRelation::get_related_episodes($episode->id) as $related_episode) {
         $episodes[] = new \Podlove\Template\Episode(\Podlove\Model\Episode::find_by_id($related_episode->id));
     }
     return $episodes;
 }
 public function handle_model_change($model)
 {
     $tainting_classes = array(Model\Episode::name(), Model\Feed::name(), Model\Podcast::name(), Model\Template::name(), Model\TemplateAssignment::name());
     $tainting_classes = apply_filters('podlove_cache_tainting_classes', $tainting_classes);
     if (in_array($model::name(), $tainting_classes)) {
         $this->taint();
     }
 }
 /**
  * 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 static function get_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();
     $cover_art_url = $episode->cover_art_with_fallback()->url();
     // determine featured image (thumbnail)
     $thumbnail = NULL;
     if (has_post_thumbnail()) {
         $post_thumbnail_id = get_post_thumbnail_id($post_id);
         $thumbnailInfo = wp_get_attachment_image_src($post_thumbnail_id);
         if (is_array($thumbnailInfo)) {
             list($thumbnail, $width, $height) = $thumbnailInfo;
         }
     }
     $description = NULL;
     if ($episode->summary && $episode->subtitle) {
         $description = $episode->subtitle . "\n" . $episode->summary;
     } elseif ($episode->summary) {
         $description = $episode->summary;
     } elseif ($episode->subtitle) {
         $description = $episode->subtitle;
     }
     // define meta tags
     $data = array(array('property' => 'og:type', 'content' => 'website'), array('property' => 'og:site_name', 'content' => $podcast->title ? $podcast->title : get_the_title()), array('property' => 'og:title', 'content' => get_the_title()), array('property' => 'og:url', 'content' => get_permalink()));
     if ($description) {
         $data[] = array('property' => 'og:description', 'content' => $description);
     }
     if ($cover_art_url) {
         $data[] = array('property' => 'og:image', 'content' => $cover_art_url);
     }
     if (isset($thumbnail)) {
         $data[] = array('property' => 'og:image', 'content' => $thumbnail);
     }
     foreach ($episode->media_files() as $media_file) {
         $mime_type = $media_file->episode_asset()->file_type()->mime_type;
         if (stripos($mime_type, 'audio') !== false) {
             $data[] = array('property' => 'og:audio', 'content' => $media_file->get_file_url());
             $data[] = array('property' => 'og:audio:type', 'content' => $mime_type);
         }
     }
     // print meta tags
     $dom = new DomDocumentFragment();
     foreach ($data as $meta_element) {
         $element = $dom->createElement('meta');
         foreach ($meta_element as $attribute => $value) {
             $element->setAttribute($attribute, $value);
         }
         $dom->appendChild($element);
     }
     return $dom;
 }
 private function validate_post(\WP_Post $post)
 {
     $episode = Model\Episode::find_or_create_by_post_id($post->ID);
     if ($episode && $episode->is_valid()) {
         Log::get()->addInfo('Validate episode', array('episode_id' => $episode->id));
         $episode->refetch_files();
         update_post_meta($post->ID, '_podlove_last_validated_at', time());
     }
 }
 public static function shortcode()
 {
     if (is_feed()) {
         return '';
     }
     $episode = Episode::find_or_create_by_post_id(get_the_ID());
     $printer = new Html5Printer($episode);
     $printer->setAttributes(['data-podlove-web-player-source' => add_query_arg(['podloveEmbed' => true], get_permalink())]);
     return $printer->render(null);
 }
 public static function add_column_content_to_episodes_table($column_name)
 {
     global $wpdb;
     switch ($column_name) {
         case 'downloads':
             if ($episode = \Podlove\Model\Episode::find_one_by_post_id(get_the_ID())) {
                 echo number_format_i18n(\Podlove\Model\DownloadIntentClean::total_by_episode_id($episode->id));
             }
             break;
     }
 }
 public function column_latest_episode($podcast)
 {
     return $podcast->with_blog_scope(function () {
         if ($latest_episode = Episode::latest()) {
             $latest_episode_blog_post = get_post($latest_episode->post_id);
             return "<a title='Published on " . date('Y-m-d h:i:s', strtotime($latest_episode_blog_post->post_date)) . "' href='" . admin_url() . "post.php?post=" . $latest_episode->post_id . "&action=edit'>" . $latest_episode_blog_post->post_title . "</a>" . "<br />" . \Podlove\relative_time_steps(strtotime($latest_episode_blog_post->post_date));
         } else {
             return "—";
         }
     });
 }
 public function create($args = [])
 {
     if (!isset($args['post_id']) || !$args['post_id']) {
         $post_factory = new WP_UnitTest_Factory_For_Post($this->factory);
         $args['post_id'] = $post_factory->create(['post_type' => 'podcast']);
     } else {
         // just make sure the connected post has the correct post type
         wp_update_post(['ID' => $args['post_id'], 'post_type' => 'podcast']);
     }
     return \Podlove\Model\Episode::create($args);
 }
 /**
  * {@inheritdoc}
  */
 protected function send($content, array $records)
 {
     $record = $records[0];
     $content = wordwrap($content, 70);
     if (isset($record['context']['episode_id'])) {
         $episode = Model\Episode::find_by_id($record['context']['episode_id']);
         $content .= "\n\n" . wp_specialchars_decode(get_edit_post_link($episode->post_id));
     }
     foreach ($this->to as $to) {
         wp_mail($to, $this->subject, $content);
     }
 }
 /**
  * Apply Twig to given template
  * 
  * @param  string $html File path or HTML string.
  * @param  array  $vars optional variables for Twig context
  * @return string       rendered template string
  */
 public static function apply_to_html($html, $vars = array())
 {
     // file loader for internal use
     $file_loader = new \Twig_Loader_Filesystem();
     $file_loader->addPath(implode(DIRECTORY_SEPARATOR, array(\Podlove\PLUGIN_DIR, 'templates')), 'core');
     // other modules can register their own template directories/namespaces
     $file_loader = apply_filters('podlove_twig_file_loader', $file_loader);
     // database loader for user templates
     $db_loader = new TwigLoaderPodloveDatabase();
     $loaders = array($file_loader, $db_loader);
     $loaders = apply_filters('podlove_twig_loaders', $loaders);
     $loader = new \Twig_Loader_Chain($loaders);
     $twig = new \Twig_Environment($loader, array('autoescape' => false));
     $twig->addExtension(new \Twig_Extensions_Extension_I18n());
     $twig->addExtension(new \Twig_Extensions_Extension_Date());
     $formatBytesFilter = new \Twig_SimpleFilter('formatBytes', function ($string) {
         return \Podlove\format_bytes($string, 0);
     });
     $padLeftFilter = new \Twig_SimpleFilter('padLeft', function ($string, $padChar, $length) {
         while (strlen($string) < $length) {
             $string = $padChar . $string;
         }
         return $string;
     });
     $twig->addFilter($formatBytesFilter);
     $twig->addFilter($padLeftFilter);
     // add functions
     foreach (self::$template_tags as $tag) {
         $func = new \Twig_SimpleFunction($tag, function () use($tag) {
             return $tag();
         });
         $twig->addFunction($func);
     }
     $context = ['option' => $vars];
     // add podcast to global context
     $context = array_merge($context, ['podcast' => new Podcast(Model\Podcast::get())]);
     // Apply filters to twig templates
     $context = apply_filters('podlove_templates_global_context', $context);
     // add podcast to global context if we are in an episode
     if ($episode = Model\Episode::find_one_by_property('post_id', get_the_ID())) {
         $context = array_merge($context, array('episode' => new Episode($episode)));
     }
     try {
         return $twig->render($html, $context);
     } catch (\Twig_Error $e) {
         $message = $e->getRawMessage();
         $line = $e->getTemplateLine();
         $template = $e->getTemplateFile();
         \Podlove\Log::get()->addError($message, ['type' => 'twig', 'line' => $line, 'template' => $template]);
     }
     return "";
 }
 private static function get_downloads_per_hour_for_episode($episode_id)
 {
     global $wpdb;
     $sql = "\n\t\t\tSELECT\n\t\t\t  \tCOUNT(*) downloads, DATE_FORMAT(accessed_at, '%%Y-%%m-%%d %%H') AS access_hour\n\t\t\tFROM\t\t\n\t\t\t\t\t`" . \Podlove\Model\DownloadIntentClean::table_name() . "` di \n\t\t\t\t\tINNER JOIN `" . \Podlove\Model\MediaFile::table_name() . "` mf ON mf.id = di.media_file_id\n\t\t\t\t\tWHERE episode_id = %d\n\t\t\tGROUP BY access_hour\n\t\t\tORDER BY access_hour\n\t\t\tLIMIT %d\n\t\t";
     $data = $wpdb->get_results($wpdb->prepare($sql, $episode_id, self::HOURS_TO_CALCULATE), ARRAY_A);
     $release_date = $wpdb->get_var($wpdb->prepare("SELECT post_date FROM {$wpdb->posts} p JOIN " . \Podlove\Model\Episode::table_name() . " e ON e.post_id = p.ID WHERE e.id = %d", $episode_id));
     if ($data) {
         $missing_hours = self::add_missing_hours($data, $release_date);
         array_splice($missing_hours, self::HOURS_TO_CALCULATE);
         return array_column($missing_hours, 'downloads');
     } else {
         return array();
     }
 }
 public function get_current_episode($post_id)
 {
     if (get_post_status($post_id) !== 'publish' || get_post_type($post_id) !== 'podcast') {
         // If post is not public, 404 will be replied
         status_header(404);
         return;
     }
     $episode = \Podlove\Model\Episode::find_one_by_post_id($post_id);
     $podcast = \Podlove\Model\Podcast::get();
     $permalink = get_permalink($post_id);
     $player_width = "560px";
     $player_height = "140px";
     return array('version' => '1.0', 'type' => 'rich', 'width' => $player_width, 'height' => $player_height, 'title' => $episode->full_title(), 'url' => get_permalink($post_id), 'author_name' => $podcast->full_title(), 'author_url' => site_url(), 'thumbnail_url' => $episode->cover_art_with_fallback()->url(), 'html' => '<iframe width="' . $player_width . '" height="' . $player_height . '" src="' . $permalink . (strpos($permalink, '?') === FALSE ? "?" : "&amp;") . 'standalonePlayer"></iframe>');
 }
 public static function clone_contributors($new_post_id, $old_post_object)
 {
     $old_episode = Episode::find_one_by_post_id($old_post_object->ID);
     $new_episode = Episode::find_or_create_by_post_id($new_post_id);
     $old_contributions = EpisodeContribution::find_all_by_episode_id($old_episode->id);
     foreach ($old_contributions as $old_contribution) {
         $c = new EpisodeContribution();
         $c->contributor_id = $old_contribution->contributor_id;
         $c->episode_id = $new_episode->id;
         $c->role_id = $old_contribution->role_id;
         $c->group_id = $old_contribution->group_id;
         $c->position = $old_contribution->position;
         $c->comment = $old_contribution->comment;
         $c->save();
     }
 }
    public static function get_related_episodes($episode_id = FALSE)
    {
        global $wpdb;
        if (!$episode_id) {
            return array();
        }
        $sql = sprintf('SELECT
			*
			FROM
			' . Episode::table_name() . ' e
			WHERE id IN (
				SELECT right_episode_id FROM ' . self::table_name() . ' WHERE left_episode_id = %1$d
				UNION
				SELECT left_episode_id FROM ' . self::table_name() . ' WHERE right_episode_id = %1$d
			)', $episode_id);
        return Episode::find_all_by_sql($sql);
    }
Example #23
0
 /**
  * Try to guess next episode number/id from previous slug.
  *
  * The slug is expected to consist of the podcast slug and the episode number.
  * Something like these: cre162, ppp000, wrint42
  * I am looking for the first number and add one. If there is no episode,
  * start with number 1.
  * 
  * Add leading zeroes if it is configured.
  * 
  * @return string episode number/id with or without leading zeroes
  */
 public function guess_next_episode_id_for_show()
 {
     // try to derive next number from previous episode slug
     $number = 1;
     $episode = Model\Episode::last();
     if ($episode && preg_match("/\\d+/", $episode->slug, $matches)) {
         $number = (int) $matches[0] + 1;
     }
     $number = "{$number}";
     // add leading zeros
     $leading_zeros = $this->get_module_option('leading_zeros', 3);
     if ($leading_zeros !== 'no') {
         while (strlen($number) < $leading_zeros) {
             $number = "0{$number}";
         }
     }
     return $number;
 }
function podlove_get_duplicate_episode_id(Episode $current_episode)
{
    global $wpdb;
    $sql = $wpdb->prepare('
		SELECT
		  p.ID
		FROM
		  `' . $wpdb->posts . '` p
		JOIN
		  `' . Episode::table_name() . '` e ON e.`post_id` = p.`ID`
		WHERE
		  p.`post_status` IN (\'publish\', \'private\')
		  AND p.post_type = "podcast"
		  AND p.ID != %d
		  AND e.slug = %s
		LIMIT 0, 1', $current_episode->post_id, $current_episode->slug);
    return $wpdb->get_var($sql);
}
 public static function remove_duplicate_episodes()
 {
     global $wpdb;
     // find duplicate episodes
     $sql = "SELECT post_id, COUNT(*) cnt FROM " . Model\Episode::table_name() . " GROUP BY post_id HAVING cnt > 1";
     $duplicate_post_ids = $wpdb->get_col($sql, 0);
     if ($duplicate_post_ids && count($duplicate_post_ids)) {
         foreach ($duplicate_post_ids as $post_id) {
             // only keep first created episode entry
             $sql = $wpdb->prepare("DELETE FROM\n\t\t\t\t\t\t" . Model\Episode::table_name() . "\n\t\t\t\t\tWHERE post_id = %d AND id != (SELECT id FROM (\n\t\t\t\t\t\tSELECT\n\t\t\t\t\t\t\tid\n\t\t\t\t\t\tFROM\n\t\t\t\t\t\t\t" . Model\Episode::table_name() . "\n\t\t\t\t\t\tWHERE\n\t\t\t\t\t\t\tpost_id = %d\n\t\t\t\t\t\tORDER BY\n\t\t\t\t\t\t\tid ASC\n\t\t\t\t\t\tLIMIT 1\n\t\t\t\t\t) x)", $post_id, $post_id);
             $wpdb->query($sql);
         }
         self::add_to_repair_log(sprintf(__('Removed duplicate episode datasets (%s) You should verify that they are correct.', 'podlove'), implode(', ', array_map(function ($post_id) {
             $link = \get_edit_post_link($post_id);
             $title = \get_the_title($post_id);
             return sprintf('<a href="%s" target="_blank">%s</a>', $link, $title);
         }, $duplicate_post_ids))));
     }
 }
    public function standalone_player_page()
    {
        if (!isset($_GET['standalonePlayer'])) {
            return;
        }
        if (!is_single()) {
            return;
        }
        if (!($episode = Episode::find_or_create_by_post_id(get_the_ID()))) {
            return;
        }
        ?>
<!DOCTYPE html>
    <head>
        <script type="text/javascript" src="<?php 
        echo $this->get_module_url();
        ?>
/js/html5shiv.js"></script>
        <script type="text/javascript" src="<?php 
        echo $this->get_module_url();
        ?>
/js/jquery-1.9.1.min.js"></script>
        <script type="text/javascript" src="<?php 
        echo $this->get_module_url();
        ?>
/player/podlove-web-player/static/podlove-web-player.js"></script>
        <link rel="stylesheet" href="<?php 
        echo $this->get_module_url();
        ?>
/player/podlove-web-player/static/podlove-web-player.css" />
    </head>
    <body>
	    <?php 
        $printer = new Printer($episode);
        echo $printer->render();
        ?>
    </body>
</html>
		<?php 
        exit;
    }
 public static function adn_tags($text, $post_id, $selected_role, $selected_group)
 {
     $contributor_adn_accounts = '';
     $episode = \Podlove\Model\Episode::find_or_create_by_post_id($post_id);
     $contributions = \Podlove\Modules\Contributors\Model\EpisodeContribution::find_all_by_episode_id($episode->id);
     $adn_service = \Podlove\Modules\Social\Model\Service::find_one_by_property('type', 'app.net');
     if (!$adn_service) {
         return;
     }
     foreach ($contributions as $contribution) {
         $contributor_adn_accounts .= '';
         $social_accounts = \Podlove\Modules\Social\Model\ContributorService::find_all_by_contributor_id($contribution->contributor_id);
         array_map(function ($account) use($adn_service, &$contributor_adn_accounts, $contribution, $selected_role, $selected_group) {
             if ($account->service_id == $adn_service->id) {
                 if ($selected_role == '') {
                     if ($selected_group == '') {
                         $contributor_adn_accounts .= "@" . $account->value . " ";
                     } else {
                         if ($contribution->group_id == $selected_group) {
                             $contributor_adn_accounts .= "@" . $account->value . " ";
                         }
                     }
                 } else {
                     if ($selected_group == '' && $contribution->role_id == $selected_role) {
                         $contributor_adn_accounts .= "@" . $account->value . " ";
                     } else {
                         if ($contribution->group_id == $selected_group && $contribution->role_id == $selected_role) {
                             $contributor_adn_accounts .= "@" . $account->value . " ";
                         }
                     }
                 }
             }
         }, $social_accounts);
     }
     $total_text_length = strlen($text) + strlen($contributor_adn_accounts);
     if ($total_text_length <= 256) {
         return str_replace('{episodeContributors}', $contributor_adn_accounts, $text);
     }
     return str_replace('{episodeContributors}', '', $text);
 }
 public static function content()
 {
     global $wpdb;
     $sql = "\n\t\tSELECT\n\t\t\tp.post_status,\n\t\t\tmf.episode_id,\n\t\t\tmf.episode_asset_id,\n\t\t\tmf.size,\n\t\t\tmf.id media_file_id\n\t\tFROM\n\t\t\t`" . Model\MediaFile::table_name() . "` mf\n\t\t\tJOIN `" . Model\Episode::table_name() . "` e ON e.id = mf.`episode_id`\n\t\t\tJOIN `" . $wpdb->posts . "` p ON e.`post_id` = p.`ID`\n\t\tWHERE\n\t\t\tp.`post_type` = 'podcast'\n\t\t\tAND p.post_status in ('private', 'draft', 'publish', 'pending', 'future')\n\t\t";
     $rows = $wpdb->get_results($sql, ARRAY_A);
     $media_files = [];
     foreach ($rows as $row) {
         if (!isset($media_files[$row['episode_id']])) {
             $media_files[$row['episode_id']] = ['post_status' => $row["post_status"]];
         }
         $media_files[$row['episode_id']][$row['episode_asset_id']] = ['size' => $row['size'], 'media_file_id' => $row['media_file_id']];
     }
     $podcast = Model\Podcast::get();
     $episodes = $podcast->episodes();
     $assets = Model\EpisodeAsset::all();
     $header = [__('Episode', 'podlove')];
     foreach ($assets as $asset) {
         $header[] = $asset->title;
     }
     $header[] = __('Status', 'podlove');
     \Podlove\load_template('settings/dashboard/file_validation', ['episodes' => $episodes, 'assets' => $assets, 'media_files' => $media_files, 'header' => $header]);
 }
    /**
     * Get episodes related to the given episode.
     * 
     * @param  int|bool $episode_id
     * @param  array $args  List of optional arguments.
     *                      only_published - If true, only return already published episodes. Default: false.
     * @return array
     */
    public static function get_related_episodes($episode_id = false, $args = [])
    {
        global $wpdb;
        $defaults = ['only_published' => false];
        $args = wp_parse_args($args, $defaults);
        if (!$episode_id) {
            return [];
        }
        $join = '';
        if ($args['only_published']) {
            $join = 'INNER JOIN ' . $wpdb->posts . ' p ON p.ID = e.post_id AND p.post_status IN (\'publish\', \'private\')';
        }
        $sql = sprintf('SELECT
			e.*
			FROM
			' . Episode::table_name() . ' e
			' . $join . '
			WHERE e.id IN (
				SELECT right_episode_id FROM ' . self::table_name() . ' WHERE left_episode_id = %1$d
				UNION
				SELECT left_episode_id FROM ' . self::table_name() . ' WHERE right_episode_id = %1$d
			)', $episode_id);
        return Episode::find_all_by_sql($sql);
    }
    public function dashoard_template()
    {
        ?>
		<style type="text/css">
		#podlove-log {
			height: 500px;
			overflow: auto;
			font-family: monospace;
			font-size: 14px;
			line-height: 18px;
			padding: 5px;
		}

		.log-level-200 {  }
		.log-level-400 { color: #95002B; }
		.log-level-550 { background: #95002B; color: #FAD4AF; }
		.log-level-550 a { color: #F4E6AD; }
		</style>

		<script type="text/javascript">
		jQuery(function($) {
			$(document).ready(function() {
				// scroll down
				$("#podlove-log").scrollTop($("#podlove-log")[0].scrollHeight);
			});
		});
		</script>

		<?php 
        if ($timezone = get_option('timezone_string')) {
            date_default_timezone_set($timezone);
        }
        ?>

		<div id="podlove-log">
		<?php 
        foreach (LogTable::find_all_by_where("time > " . strtotime("-1 week")) as $log_entry) {
            ?>
			<div class="log-entry log-level-<?php 
            echo $log_entry->level;
            ?>
">
				<span class="log-date">
					[<?php 
            echo date('Y-m-d H:i:s', $log_entry->time);
            ?>
]
				</span>
				<span class="log-message">
					<?php 
            echo $log_entry->message;
            ?>
				</span>
				<span class="log-extra">
					<?php 
            $data = json_decode($log_entry->context);
            if (isset($data->media_file_id)) {
                if ($media_file = Model\MediaFile::find_by_id($data->media_file_id)) {
                    if ($episode = $media_file->episode()) {
                        if ($asset = $media_file->episode_asset()) {
                            echo sprintf('<a href="%s">%s/%s</a>', get_edit_post_link($episode->post_id), $episode->slug, $asset->title);
                        }
                    }
                }
            }
            if (isset($data->error)) {
                echo sprintf(' "%s"', $data->error);
            }
            if (isset($data->episode_id)) {
                if ($episode = Model\Episode::find_by_id($data->episode_id)) {
                    echo sprintf(' <a href="%s">%s</a>', get_edit_post_link($episode->post_id), get_the_title($episode->post_id));
                }
            }
            if (isset($data->http_code)) {
                echo " HTTP Status: " . $data->http_code;
            }
            if (isset($data->mime_type) && isset($data->expected_mime_type)) {
                echo " Expected: {$data->expected_mime_type}, but found: {$data->mime_type}";
            }
            if (isset($data->type) && $data->type == 'twig') {
                echo sprintf('in template "%s" line %d', $data->template, $data->line);
            }
            ?>
				</span>
			</div>
		<?php 
        }
        ?>
		</div>
		<?php 
    }