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'];
    }
    public function episode_relation_form_callback($form_base_name = '_podlove_meta')
    {
        $existing_episodes = array();
        foreach (\Podlove\Model\Episode::find_all_by_time() as $episode) {
            $existing_episodes[$episode->id] = get_the_title($episode->post_id);
        }
        $episode = \Podlove\Model\Episode::find_one_by_post_id(get_the_ID());
        $existing_episode_relations = array_map(function ($episode) {
            return $episode->to_array();
        }, \Podlove\Modules\RelatedEpisodes\Model\EpisodeRelation::get_related_episodes($episode->id));
        ?>
			<div id="episode-relation-form">
				<table class="podlove_alternating" border="0" cellspacing="0">
					<thead>
						<tr>
							<th><?php 
        _e('Episode', 'podlove');
        ?>
</th>
							<th><?php 
        _e('Remove', 'podlove');
        ?>
</th>
						</tr>
					</thead>
					<tbody id="episode_relation_table_body" style="min-height: 50px;">
						<tr class="episode_relation_table_body_placeholder" style="display: none;">
							<td><em><?php 
        echo __('No episode relations were added yet.', 'podlove');
        ?>
</em></td>
						</tr>
					</tbody>
				</table>

				<div id="add_new_episode_relation_wrapper">
					<input class="button" id="add_new_episode_relation_button" value="+" type="button" />
				</div>
			</div>

			<script type="text/template" id="episode-relation-row-template">
			<tr class="podlove-episode-relation-table">
				<td>
					<select name="<?php 
        echo $form_base_name;
        ?>
[related_episodes][{{id}}]" id="<?php 
        echo $form_base_name;
        ?>
_related_episodes_{{id}}"  class="chosen-related-episodes podlove_episode_relation_episodes_dropdown">
					<?php 
        foreach ($existing_episodes as $episode_id => $episode_title) {
            ?>
						<option value="<?php 
            echo $episode_id;
            ?>
"><?php 
            echo $episode_title;
            ?>
</option>
					<?php 
        }
        ?>
					</select>
				</td>
				<td>
					<span class="episode_relation_remove">
						<i class="clickable podlove-icon-remove"></i>
					</span>
				</td>
			</tr>
			</script>
			<script type="text/javascript">
				var PODLOVE = PODLOVE || {};
				
				PODLOVE.related_episodes_existing_episode_relations = <?php 
        echo json_encode($existing_episode_relations);
        ?>
;
			</script>
			<?php 
    }
 public static function prepare_statistics()
 {
     if (($statistics = get_transient('podlove_dashboard_stats')) !== FALSE) {
         return $statistics;
     } else {
         $episodes = Model\Episode::find_all_by_time();
         $prev_post = 0;
         $counted_episodes = 0;
         $time_stamp_differences = array();
         $episode_durations = array();
         $episode_status_count = array('publish' => 0, 'private' => 0, 'future' => 0, 'draft' => 0);
         $statistics = array('episodes' => array(), 'total_episode_length' => 0, 'average_episode_length' => 0, 'days_between_releases' => 0, 'average_media_file_size' => 0, 'total_media_file_size' => 0);
         foreach ($episodes as $episode_key => $episode) {
             $post = get_post($episode->post_id);
             $counted_episodes++;
             // duration in seconds
             if (self::duration_to_seconds($episode->duration) > 0) {
                 $episode_durations[$post->ID] = self::duration_to_seconds($episode->duration);
             }
             // count by post status
             if (!isset($episode_status_count[$post->post_status])) {
                 $episode_status_count[$post->post_status] = 1;
             } else {
                 $episode_status_count[$post->post_status]++;
             }
             // determine time in days since last publication
             if ($prev_post) {
                 $timestamp_current_episode = new \DateTime($post->post_date);
                 $timestamp_next_episode = new \DateTime($prev_post->post_date);
                 $time_stamp_differences[$post->ID] = $timestamp_current_episode->diff($timestamp_next_episode)->days;
             }
             $prev_post = $post;
         }
         // Episode Stati
         $statistics['episodes'] = $episode_status_count;
         // Number of Episodes
         $statistics['total_number_of_episodes'] = count($episodes);
         // Total Episode length
         $statistics['total_episode_length'] = array_sum($episode_durations);
         // Calculating average episode in seconds
         $statistics['average_episode_length'] = count($episode_durations) > 0 ? round(array_sum($episode_durations) / count($episode_durations)) : 0;
         // Calculate average time until next release in days
         $statistics['days_between_releases'] = count($time_stamp_differences) > 0 ? round(array_sum($time_stamp_differences) / count($time_stamp_differences)) : 0;
         // Media Files
         $episodes_to_media_files = function ($media_files, $episode) {
             return array_merge($media_files, $episode->media_files());
         };
         $media_files = array_reduce($episodes, $episodes_to_media_files, array());
         $valid_media_files = array_filter($media_files, function ($m) {
             return $m->size > 0;
         });
         $sum_mediafile_sizes = function ($result, $media_file) {
             return $result + $media_file->size;
         };
         $statistics['total_media_file_size'] = array_reduce($valid_media_files, $sum_mediafile_sizes, 0);
         $mediafile_count = count($valid_media_files);
         $statistics['average_media_file_size'] = $mediafile_count > 0 ? $statistics['total_media_file_size'] / $mediafile_count : 0;
         set_transient('podlove_dashboard_stats', $statistics, 3600);
         return $statistics;
     }
 }
 public function column_episodes($podcast)
 {
     return $podcast->with_blog_scope(function () {
         return count(Episode::find_all_by_time());
     });
 }
    public function load()
    {
        $this->api = new API_Wrapper($this);
        $module_url = $this->get_module_url();
        $user = null;
        $selected_role = $this->get_module_option('adn_contributor_filter_role');
        $selected_group = $this->get_module_option('adn_contributor_filter_group');
        add_action('podlove_module_was_activated_app_dot_net', array($this, 'was_activated'));
        add_action('wp_ajax_podlove-refresh-channel', array($this, 'ajax_refresh_channel'));
        add_action('wp_ajax_podlove-adn-post', array($this, 'ajax_post_to_adn'));
        add_action('wp_ajax_podlove-preview-adn-post', array($this, 'ajax_preview_alpha_post'));
        if ($this->get_module_option('adn_auth_key') !== "") {
            add_action('publish_podcast', array($this, 'post_to_adn_handler'));
            add_action('publish_future_podcast', array($this, 'post_to_adn_handler'));
            add_action('delayed_adn_post', array($this, 'post_to_adn'), 10, 2);
        }
        if (isset($_GET["page"]) && $_GET["page"] == "podlove_settings_modules_handle") {
            add_action('admin_bar_init', array($this, 'reset_adn_auth'));
        }
        // Import all posts as already published
        add_filter('wp_import_post_meta', function ($postmetas, $post_id, $post) {
            $postmetas[] = array('key' => '_podlove_episode_was_published', 'value' => true);
            return $postmetas;
        }, 10, 3);
        if ($this->get_module_option('adn_auth_key') == "") {
            $description = '<i class="podlove-icon-remove"></i> ' . __('You need to allow Podlove Publisher to access your App.net account. To do so please start the authorization process, follow the instructions and paste the obtained code in the field above.', 'podlove') . '<br><a href="https://auth.podlove.org/adn.php?step=1" class="button button-primary" target="_blank">' . __('Start authorization process now', 'podlove') . '</a>';
            $this->register_option('adn_auth_key', 'string', array('label' => __('Authorization', 'podlove'), 'description' => $description, 'html' => array('class' => 'regular-text podlove-check-input', 'placeholder' => 'App.net authentication code')));
        } else {
            if ($user = $this->api->fetch_authorized_user()) {
                $description = '<i class="podlove-icon-ok"></i> ' . sprintf(__('You are logged in as %s. If you want to logout, click %shere%s.', 'podlove'), '<strong>' . $user->username . '</strong>', '<a href="' . admin_url('admin.php?page=podlove_settings_modules_handle&reset_appnet_auth_code=1') . '">', '</a>');
            } else {
                $description = '<i class="podlove-icon-remove"></i> ' . sprintf(__('Something went wrong with the App.net connection. Please start the authorization process again. To do so click %shere%s', 'podlove'), '<a href="' . admin_url('admin.php?page=podlove_settings_modules_handle&reset_appnet_auth_code=1') . '">', '</a>');
            }
            $this->register_option('adn_auth_key', 'hidden', array('label' => __('Authorization', 'podlove'), 'description' => $description, 'html' => array('class' => 'regular-text podlove-check-input')));
            $this->register_option('adn_language_annotation', 'select', array('label' => __('Language of Announcement', 'podlove'), 'description' => 'Selecting the language of the Announcement, will include an <a href="http://developers.app.net/docs/meta/annotations/" target="_blank">App.net language annotation</a>.', 'html' => array('class' => 'regular-text adn-dropdown'), 'options' => $this->get_languages()));
            $this->register_option('adn_patter_room_announcement', 'checkbox', array('label' => __('Patter', 'podlove'), 'description' => 'Post announcement to Patter room, too.'));
            $this->register_option('adn_patter_room', 'select', array('description' => '<span class="podlove_adn_patter_refresh" data-category="patter_room"><i class="podlove-icon-repeat"></i></span>From the list of subscribed <a href="http://patter-app.net/faq.html" target="_blank">Patter rooms</a>, choose the one related to your Podcast.', 'html' => array('class' => 'regular-text adn-dropdown'), 'options' => $this->api->fetch_patter_rooms()));
            $this->register_option('adn_broadcast', 'checkbox', array('label' => __('Broadcast', 'podlove'), 'description' => 'Send announcement via App.net Broadcast Channel.'));
            $this->register_option('adn_broadcast_channel', 'select', array('description' => '<span class="podlove_adn_broadcast_refresh" data-category="broadcast_channel"><i class="podlove-icon-repeat"></i></span> From the list of your Broadcast channels, choose the one related to your Podcast.', 'html' => array('class' => 'regular-text adn-dropdown'), 'options' => $this->api->fetch_broadcast_channels()));
            $this->register_option('adn_automatic_announcement', 'checkbox', array('label' => __('Automatic Announcement', 'podlove'), 'description' => 'Announces new podcast episodes on App.net'));
            $adn_post_delay_hours = str_pad($this->get_module_option('adn_post_delay_hours'), 2, 0, STR_PAD_LEFT);
            $adn_post_delay_minutes = str_pad($this->get_module_option('adn_post_delay_minutes'), 2, 0, STR_PAD_LEFT);
            $this->register_option('adn_post_delay', 'callback', array('label' => __('Post delay', 'podlove'), 'callback' => function () use($adn_post_delay_hours, $adn_post_delay_minutes) {
                ?>
							<input type="text" name="podlove_module_app_dot_net[adn_post_delay_hours]" id="podlove_module_app_dot_net_adn_post_delay_hours" value="<?php 
                echo $adn_post_delay_hours ? $adn_post_delay_hours : '';
                ?>
" class="regular-text podlove-check-input" placeholder="00" >
								<label for="podlove_module_app_dot_net_adn_post_delay_hours">Hours</label>
							<input type="text" name="podlove_module_app_dot_net[adn_post_delay_minutes]" id="podlove_module_app_dot_net_adn_post_delay_minutes" value="<?php 
                echo $adn_post_delay_minutes ? $adn_post_delay_minutes : '';
                ?>
" class="regular-text podlove-check-input" placeholder="00" >
								<label for="podlove_module_app_dot_net_adn_post_delay_minutes">Minutes</label>
						<?php 
            }));
            $description = '';
            if ($this->get_module_option('adn_poster_announcement_text') == "") {
                $description = '<i class="podlove-icon-remove"></i>' . __('You need to set a text to announce new episodes.', 'podlove');
            }
            $description .= __('App.net allows 256 characters per post. Try to keep the announcement text short. Your episode titles will need more space than the placeholders.', 'podlove');
            $description .= '
					' . __('Use these placeholders to customize your announcement', 'podlove') . ':
					<code title="' . __('The title of your podcast', 'podlove') . '">{podcastTitle}</code>
					<code title="' . __('The title of your episode, linking to it', 'podlove') . '">{linkedEpisodeTitle}</code>
					<code title="' . __('The title of the episode', 'podlove') . '">{episodeTitle}</code>
					<code title="' . __('The permalink of the current episode', 'podlove') . '">{episodeLink}</code>
					<code title="' . __('The subtitle of the episode', 'podlove') . '">{episodeSubtitle}</code>';
            $preview_text = $this->get_module_option('adn_poster_announcement_text');
            $this->register_option('adn_poster_announcement_text', 'callback', array('label' => __('Announcement text', 'podlove'), 'callback' => function () use($description, $preview_text) {
                $description = apply_filters('podlove_adn_tags_description', $description);
                ?>
							<div>		
								<textarea name="podlove_module_app_dot_net[adn_poster_announcement_text]" class"podlove-check-input" id="podlove_module_app_dot_net_adn_poster_announcement_text" cols="50" rows="4" placeholder="Check out the new {podcastTitle} episode: {linkedEpisodeTitle}"><?php 
                echo $preview_text;
                ?>
</textarea>
							</div>
							<span class="description"><?php 
                echo $description;
                ?>
</span>
						<?php 
            }));
            $this->register_option('adn_poster_image_fallback', 'checkbox', array('label' => __('Announcement Image', 'podlove'), 'description' => 'Use Podcast Cover as Fallback'));
            $this->register_option('adn_preview', 'callback', array('label' => __('Announcement preview', 'podlove'), 'callback' => function () use($selected_role, $selected_group, $user, $module_url) {
                if (!$user) {
                    return;
                }
                $podcast = Model\Podcast::get();
                if ($episode = Model\Episode::find_one_by_where('slug IS NOT NULL')) {
                    $example_data = array('episode' => get_the_title($episode->post_id), 'episode-link' => get_permalink($episode->post_id), 'subtitle' => $episode->subtitle, 'episode-image' => $episode->cover_art() ? $episode->cover_art()->url() : '', 'podcast-image' => $podcast->cover_art()->url(), 'contributors' => '');
                    $example_data = apply_filters('podlove_adn_example_data', $example_data, $episode->post_id, $selected_role, $selected_group);
                } else {
                    $example_data = array('episode' => 'My Example Episode', 'episode-link' => 'http://www.example.com/episode/001', 'subtitle' => 'My Example Subtitle', 'contributors' => '@example @elpmaxe', 'podcast-image' => '', 'episode-image' => '');
                }
                ?>
						<div id="podlove_adn_post_preview"
								data-podcast="<?php 
                echo $podcast->title;
                ?>
"
								data-episode="<?php 
                echo $example_data['episode'];
                ?>
"
								data-episode-link="<?php 
                echo $example_data['episode-link'];
                ?>
"
								data-episode-subtitle="<?php 
                echo $example_data['subtitle'];
                ?>
"
								data-contributors="<?php 
                echo $example_data['contributors'];
                ?>
"
								data-episode-image="<?php 
                echo $example_data['episode-image'];
                ?>
"
								data-podcast-image="<?php 
                echo $example_data['podcast-image'];
                ?>
">
							<div class="adn avatar" style="background-image:url(<?php 
                echo $user->avatar_image->url;
                ?>
);"></div>
							<div class="adn content">
								<div class="adn image"><img src="" /></div>
								<div class="adn username"><?php 
                echo $user->username;
                ?>
</div>
								<div class="adn body">Lorem ipsum dolor ...</div>
						
								<div class="adn footer">
									<ul>
										<li>
											<i class="podlove-icon-time"></i> now
										</li>
										<li>
											<i class="podlove-icon-reply"></i> Reply
										</li>
										<li>
											<i class="podlove-icon-share"></i> via Podlove Publisher
										</li>
									</ul>
								</div>
							</div>

							<div style="clear: both"></div>
						</div>

						<script type="text/javascript" src="<?php 
                echo $module_url;
                ?>
/adn.js"></script>
						<link rel="stylesheet" type="text/css" href="<?php 
                echo $module_url;
                ?>
/adn.css" />
						<?php 
            }));
            $this->register_option('adn_manual_post', 'callback', array('label' => __('Manual Announcement', 'podlove'), 'callback' => function () {
                ?>
							<select id="adn_manual_post_episode_selector" class="chosen">
								<?php 
                $episodes = Model\Episode::find_all_by_time();
                foreach ($episodes as $episode) {
                    $post = get_post($episode->post_id);
                    echo "<option value='" . $episode->post_id . "'>" . $post->post_title . "</option>";
                }
                ?>
							</select>
							<span class="button" id="adn_manual_post_alpha">
								Announce, as configured 
								<span class="adn-post-status-pending">
									<i class="podlove-icon-spinner rotate"></i>
								</span>
								<span class="adn-post-status-ok">
									<i class="podlove-icon-ok"></i>
								</span>
							</span>
						<?php 
            }));
        }
    }
 * contents into local field.
 */
add_filter('pre_update_option_podlove_asset_assignment', function ($new, $old) {
    global $wpdb;
    if (!isset($old['chapters']) || !isset($new['chapters'])) {
        return $new;
    }
    if ($new['chapters'] != 'manual') {
        // just changes to manual
        return $new;
    }
    if ((int) $old['chapters'] <= 0) {
        // just changes from an asset
        return $new;
    }
    $episodes = \Podlove\Model\Episode::find_all_by_time();
    // 10 seconds per episode or 30 seconds since 1 request per asset
    // is required if it is not cached
    set_time_limit(max(30, count($episodes) * 10));
    foreach ($episodes as $episode) {
        if ($chapters = $episode->get_chapters('mp4chaps')) {
            $episode->update_attribute('chapters', esc_sql($chapters));
        }
    }
    // delete chapters caches
    $wpdb->query('DELETE FROM `' . $wpdb->options . '` WHERE option_name LIKE "%podlove_chapters_string_%"');
    return $new;
}, 10, 2);
// extend episode form
add_filter('podlove_episode_form_data', function ($form_data, $episode) {
    if (Model\AssetAssignment::get_instance()->chapters !== 'manual') {