Warning: chmod(): Operation not permitted in /var/www/hopeinstoughton_www/wp-content/plugins/simple-universal-google-analytics/main.php on line 8
[0] in function chmod in /var/www/hopeinstoughton_www/wp-content/plugins/simple-universal-google-analytics/main.php on line 8
[1] in function include_once in /var/www/hopeinstoughton_www/wp-settings.php on line 560
[2] in function require_once in /var/www/hopeinstoughton_www/wp-config.php on line 85
[3] in function require_once in /var/www/hopeinstoughton_www/wp-load.php on line 50
[4] in function require_once in /var/www/hopeinstoughton_www/wp-blog-header.php on line 13
[5] in function require in /var/www/hopeinstoughton_www/index.php on line 17
403WebShell
403Webshell
Server IP : 94.177.8.99  /  Your IP : 216.73.217.165
Web Server : Apache/2.4.58 (Ubuntu)
System : Linux aries 6.8.0-134-generic #134-Ubuntu SMP PREEMPT_DYNAMIC Fri Jun 26 18:43:11 UTC 2026 x86_64
User : www-data ( 33)
PHP Version : 8.1.34
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : OFF  |  Sudo : ON  |  Pkexec : OFF
Directory :  /var/www/hopeinstoughton_www/wp-content/plugins/xapian-search/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /var/www/hopeinstoughton_www/wp-content/plugins/xapian-search/media-search.php
<?php

/*
Plugin Name: Media Search
Plugin URI: http://hostz.org/
Description: Audio / Video Search
Version: 1.0.0
Author: Phil Young
Author URI: http://hostz.org/
License: Whatever
*/

require_once __DIR__ . '/lib/class-xap.php';

// https://github.com/DaveChild/Text-Statistics/tree/master/src/DaveChild/TextStatistics

// This uses the sshfs link to hostz.org to generate thumbnails

class media_search_class {

    const PAGESIZE = 15;
    const SPECIAL_PAGE = 2178;      // this is our Archive Page
    const EXHORT = 364;

    private $header;
    private $slug;
    private $pagenum;
    private $active_hash;

	public function __construct() {
        $this->DATABASE = __DIR__ . '/database';
        $this->BASE = realpath(__DIR__ . '/../../..');
        $this->header = FALSE;
        $this->pagenum = FALSE;
        $this->ActiveHash(NULL);
		add_shortcode('xapian-search', [$this, '_shortcode_searchbar' ]);
		add_shortcode('xapian-results', [$this, '_shortcode_searchresults' ]);
		add_action( 'wp_enqueue_scripts', [$this, 'add_css'] );
		add_action( 'admin_enqueue_scripts', [$this, 'add_css'] );
		add_action( 'admin_menu', [$this, 'main_menu'], 1 );            // broken - accessing wrong tables
		add_action( 'wp', [$this, '__wp_page'], 51 );
        add_filter( 'wp_audio_shortcode', [$this, '_audiowidget'], 1, 5 );
        add_filter( 'wp_audio_extensions', [$this, '_audioparam'], 1 ); // add our extra shortcode parameter names here
        add_action( 'wp_loaded', [$this, 'custom_urls'], 51 );

        add_filter( 'catalyst_site_layout', [$this, '__setlayout'], 1 );
        add_filter( '_get_page_link', [$this, '__canonical'], 80, 2 );
        add_filter( 'catalyst_byline_meta', [$this, '__author'], 90 );
        add_filter( 'catalyst_post_categories_shortcode', [$this, '__categories'], 90 );
        add_filter( 'wp_title', [$this, '__build_site_title'], 11, 3 );           // restore title of page (ignore catalyst changes)
        add_filter( 'mm_generate_link', [$this, '__generate_link'], 1, 4 );
        add_filter( 'mm_get_contents', [$this, '__get_contents'], 1, 4 );      // get the contents of a file
        add_filter( 'edit_post_link', [$this, '__edit_post_link'], 80, 3 );
    }
    
    public function ActiveHash() {
        if (func_num_args()) {
            $this->active_hash = func_get_arg(0);
            return $this;
        }
        return $this->active_hash;
    }

    public function custom_urls() {
		$cache = $_SERVER['REQUEST_URI'];
		if (($p = strpos($cache, '?')) !== FALSE) {
            $cache = substr($cache, 0, $p);
        }
        if (preg_match('#/~/media-search-vtt/(\w+)/(\w+)/([0-9a-f]{10})$#', $cache, $matches)) {    // this is deprecated - replaced by media-manager.
            header('Content-type: text/plain');
            $type = $matches[1];
            $docid = $matches[2];
            $auth = $matches[3];
            $expected_auth = $this->generateAuth($type, $docid);
            if ($auth != $expected_auth) {
                echo "\nPermission Denied\n";
            } else {
                echo $this->renderTranscript($type, $docid, FALSE);
            }
            exit;
            
        } elseif (preg_match('#/~/testpage/(.*?)$#', $cache, $matches)) {
            list($basic, $pagenum) = explode('/', $matches[1] . '//', 3);
            if (empty($pagenum)) $pagenum = 0;
            if (is_numeric($pagenum)) {
                $ok = $this->_checkIfvalid($basic, $pagenum, TRUE);
            } else {
                $ok = FALSE;
            }
            if ($ok) {
                $this->slug = $basic;
                $this->pagenum = $pagenum;
                $_GET['type'] = $this->header->mi_type;
                $_GET['item'] = $this->header->mi_id;
                $_GET['auth'] = $this->generateAuth($_GET['type'], $_GET['item']);
                $this->_loadThePage($pagenum);
            } else {
                $this->header = FALSE;
            }
        } elseif (preg_match('#/~/media-search-reindex/(.*?)$#', $cache, $matches)) {                    // reindex a vtt file
            $id = $matches[1];
            $this->command_reindex($id);
            wp_redirect('/wp-admin/admin.php?page=talks-single&action=class&cid=' . $id);
            exit;
            
        } elseif (preg_match('#/~/media-search-remove/(.*?)$#', $cache, $matches)) {                    // reindex a vtt file
            $id = $matches[1];
//            $this->remove_from_xapian($id);
            die("Document $id removed.");
            
        // This must be last
        } elseif (preg_match('#/(.*?)$#', $cache, $matches)) {

            list($basic, $pagenum) = explode('/', $matches[1] . '//', 3);
            if (empty($pagenum)) $pagenum = 0;
            if (is_numeric($pagenum)) {
                $ok = $this->_checkIfvalid($basic, $pagenum, FALSE);
            } else {
                $ok = FALSE;
            }
            if ($ok) {
                $this->slug = $basic;
                $this->pagenum = $pagenum;
                $_GET['type'] = $this->header->mi_type;
                $_GET['item'] = $this->header->mi_id;
                $_GET['auth'] = $this->generateAuth($_GET['type'], $_GET['item']);
                $this->_loadThePage($pagenum);
            } else {
                $this->header = FALSE;
            }
        }
    }
    
    // change the canonical link to something more suitable
    public function __canonical($link, $post_id) {
        if ($this->header && $post_id == static::SPECIAL_PAGE) {
            $elements = parse_url($link);
            $pagenum = $this->pagenum > 1 ? '/' . $this->pagenum : '';
//            return $elements['scheme'] . '://' . $elements['host'] . '/~/testpage/' . $this->slug . $pagenum;
            return $elements['scheme'] . '://' . $elements['host'] . '/' . $this->slug . $pagenum;
        }
        return $link;
    }

    // this is overwritten by media-manager
    public function __generate_link($link, $id, $download_name, $source) {
        if (strpos($download_name, '.vtt') !== FALSE) {
            $type = 'mt';
            $subdir = substr($id, 0, 1);
            $cc = "/var/www/hopeinstoughton_cdn1/{$subdir}/{$download_name}";
            if (file_exists($cc)) {
                $auth = $this->generateAuth($type, $id);
                $link = "/~/media-search-vtt/{$type}/{$id}/{$auth}";
            } else {
                $link = '';
            }
        } elseif ($download_name == '') {
            $link = "https://audio.hopeinstoughton.org/play/$id";
        } else {
            $dfilename = rawurlencode($download_name);
            $link = "https://audio.hopeinstoughton.org/download/$id/$dfilename";
        }
        return $link;
    }
    
    // get the contents of a file - this too is overwritten
    public function __get_contents($data, $id, $extension, $source) {
        if ($extension == 'vtt') {
            //$filename = "{$this->BASE}/av/moorestown-vtt/{$id}.vtt";
            $subdir = substr($id, 0, 1);
            $filename = "/var/www/hopeinstoughton_cdn1/{$subdir}/{$id}.vtt";
            if (file_exists($filename)) {
                $data = file_get_contents($filename);
            } else {
                $data = '';
            }
        }
        return $data;
    }

// Tools -> Reindex Audio and Video VTT files
	public function main_menu() {
//		add_options_page('Closed Captions', 'Closed Captions', 10, 'msc_reindex', [$this, 'reindex_all']);
    }

	public function add_css() {
		// add .js file
		wp_enqueue_script('jquery');
        wp_enqueue_script('formwizard_js', '//code.jquery.com/ui/1.10.3/jquery-ui.js', [], '' );
		wp_enqueue_script('media_search_js', plugins_url('media-search.js', __FILE__), [], '0.3' );
		wp_enqueue_style('media_search_ui', '//code.jquery.com/ui/1.10.3/themes/smoothness/jquery-ui.css'); 
		wp_enqueue_style('media_search', plugins_url('media-search.css', __FILE__), [], '1.1'); 
	}
        
    public function _reindex_mm($vtt, $type = 'mt') {
        global $wpdb;
        set_time_limit(0);
        $vtt->DocumentType($type);
        $rows = $wpdb->get_results('select mi_id,mi_type,present_vtt from mm_item where mi_type="' . $type . '" and present_vtt <> 0');
        foreach($rows as $row) {
            $item = $row->mi_id;
            echo "Reindexing {$item}...<br>\n";
            $text = apply_filters('mm_get_contents', '', $item, 'vtt', $row->present_vtt);
            $vtt->DocumentId($item)
                ->Content($text)
                ->Reindex();
        }
    }
    
    function _reindex_folder($vtt, $doctype, $folder, $generateId) {
        $vtt->DocumentType($doctype);
        echo "Reindexing files in $folder...<br>";
        $files = glob("$folder/*.vtt");
        foreach($files as $filename) {
            if (filesize($filename) > 20) {

                //$mt = filemtime($filename);
                //if ($mt < time() - 7200) continue;

                $id = $generateId($filename);
                if ($id === NULL) {
                    echo "Skipping $filename...<br>\n";
                } else {
                    echo "Reindexing $id...<br>\n";
                    $vtt->DocumentId($id)
                        ->FilenameDocument($filename)
                        ->Reindex();
                }
            }
        }
    }
  
	public function _shortcode_searchbar($atts) {
		global $wpdb;
        $mediasearch = isset($_GET['mediasearch']) ? trim(stripslashes_deep($_GET['mediasearch'])) : '';
        $width = isset($atts['width']) ? $atts['width'] : '';
        $action = isset($atts['action']) ? $atts['action'] : '';
        if ($action != '') $action = " action=\"$action\"";
        if ($width == '') $width = '100%';
        $safe_q = htmlspecialchars($mediasearch);
        return <<<BLOCK
        <form class="search-wrapper"$action>
            <div class="search-inner-wrapper" style="width:{$width};">
                <input name="mediasearch" maxlength="256" type="text" aria-controls="searchSuggestionTable" aria-expanded="false" placeholder="Search Audio and Video" title="Search Audio and Video" value="{$safe_q}" aria-label="Search Audio and Video">
                <button id="searchSubmit" class="search-buttonx action-searching fa fa-arrow-right fa-2x" title="Search" aria-label="Search"></button>
            </div>
        </form>
BLOCK;
    }
    
	private function _remove(&$after, $d) {		// number of items to delete
		$x = intval((count($after) - $d) / 2);
		array_splice($after, $x, $d, ['...']);
	}
	
	private function getAuthors(&$res) {		
        global $wpdb;
		$docs = [];
		$result = [];
		$wherein = '';
		foreach($res as $index => $item) {
			$docs[$item['docid']] = $index;
			if ($wherein != '') $wherein .= ',';
			$wherein .= '"' . esc_sql($item['docid']) . '"';
		}
		$sql = <<<BLOCK
select mi_id,mi_speaker,ms_firstname,ms_lastname,ms_suffix
  from mm_item 
    left join mm_speaker on mi_speaker=ms_id
  where mi_id in ($wherein)
BLOCK;
        $rows = $wpdb->get_results($sql);
		foreach($rows as $row) {
			$entry = &$res[$docs[$row->mi_id]];
			$entry['speaker'] = $row->mi_speaker;
			if (!isset($result[$entry['speaker']])) {
				$result[$entry['speaker']] = [
					'firstname' => $row->ms_firstname,
					'lastname' => $row->ms_lastname,
					'suffix' => $row->ms_suffix,
					'count' => 1
				];
			} else {
				$result[$entry['speaker']]['count']++;
			}
		}
		uasort($result, function($a, $b) {
			$x = strcasecmp($a['lastname'], $b['lastname']);
			if ($x) return $x;
			return strcasecmp($a['firstname'], $b['firstname']);
		});
		$output = '';
		$a = explode('.', $_GET['a']);
		$a = array_flip($a);
		foreach($result as $id => $item) {
			$name = $item['lastname'] . ', ' . $item['firstname'];
			if (!empty($item['suffix'])) $name .= ', ' . $item['suffix'];
			$select = isset($a[$id]) ? 'checked' : '';
			$output .= "<div><label><input type=\"checkbox\" name=\"author[]\" value=\"$id\" $select> $name ({$item['count']})</label></div>\n";
		}
		return $output;
	}
	
	private function _assemble_navigation($emediasearch, $start, $count) {
/*		$before = $after = [];
		$current = ''; */
		$pagination = '';
		$begin = $start - 6 * self::PAGESIZE;
		if ($begin < 0) $begin = 0;
		$end = $begin + 13 * self::PAGESIZE;
		if ($end > $count) $end = $count;
		
		for($i = $begin; $i < $end; $i += self::PAGESIZE) {
            $page = intval($i / self::PAGESIZE) + 1;
            if ($i == $start) {
                $link = " <span class=\"xs-button current\">$page</span>";
            } else {
                $link = " <a class=\"xs-button\" href=\"?mediasearch=$emediasearch&amp;start=$i\">$page</a>";
            }
/*			if ($i < $start) {
				$before[] = $link;
			} elseif ($i > $start) {
				$after[] = $link;
			} else {
				$current = $link;
			} */
            $pagination .= $link;
        }
/*		if (count($before) < 5) {	// remove only from after
			$this->_remove($after, 14 - count($before));
		} elseif (count($after) < 5) {
			$this->_remove($before, 14 - count($after));
		}
		$pagination = join('', $before) . $current . join('', $after); */
		return $pagination;
	}
	
	private function filterList(&$res) {
		$a = explode('.', $_GET['a']);
		$a = array_flip($a);
		foreach($res as $index => $item) {
			$speaker = $item['speaker'];
			if (!isset($a[$speaker])) {
				unset($res[$index]);
			}
		}
		$res = array_values($res);
	}
	
    // mediasearch
	public function _shortcode_searchresults($atts) {
		global $wpdb;
        $mediasearch = isset($_GET['mediasearch']) ? trim(stripslashes_deep($_GET['mediasearch'])) : '';
        if ($mediasearch == '') return;
        $emediasearch = rawurlencode($mediasearch);

        $start = isset($_GET['start']) ? (int) $_GET['start'] : 0;

        $vtt = new class_xap;
        $vtt->FilenameDatabase($this->DATABASE)
            ->Open();      // open for reading

        $res = $vtt->Search($mediasearch);
		$authors = $this->getAuthors($res);			// add the speakers to the results
		if ($_GET['a'] != '') {
			$this->filterList($res);				// restrict to just selected authors
		}
        $count = count($res);
		if ($start >= $count && $_GET['a'] != '') {		// filter
			$start = 0;
		}
        
        // take a slice, based on page number
        $page_res = array_slice($res, $start, static::PAGESIZE);

        // fill in details about each search result using info from the database
        $this->loadDetails($page_res);
        
        // render the search results 
        $output = '';
        foreach($page_res as $result) {
            $output .= $this->_asSearchResult($result);
        }
        
        // generate pagination
        $pagination = '';
        if ($start > 0) {
            $prev = $start - self::PAGESIZE;
            $pagination = "<a class=\"xs-button\" href=\"?mediasearch=$emediasearch&amp;start=$prev\">&lt;</a>";
        } else {
            $pagination = "<span class=\"xs-button disabled\">&lt;</span>";
        }
		
		$pagination .= $this->_assemble_navigation($emediasearch, $start, $count);

        if ($start + self::PAGESIZE < $count) {
            $prev = $start + self::PAGESIZE;
            $pagination .= " <a class=\"xs-button\" href=\"?mediasearch=$emediasearch&amp;start=$prev\">&gt;</a>";
        } else {
            $pagination .= " <span class=\"xs-button disabled\">&gt;</span>";
        }
        
        $vtt->Close();
        $summary = $count == 1 ? '1 result.' : "$count results.";
		
		$filter2 = <<<BLOCK
<div class="xs-filteritems" style="display:none">
	<div class="xs-buttons"><a class="action-xs" data-action="apply" href="#">Apply</a> | <a class="action-xs" data-action="clear" href="#">Clear</a> | <a class="action-xs" data-action="close" href="#">Close</a></div>
	<div class="xs-body"><h3>Speakers</h3>$authors</div>
</div>
BLOCK;
		$filter = <<<BLOCK
<a href="#" class="xs-filter action-xs" data-action="open">Filter...</a>
BLOCK;
		
        return <<<BLOCK
{$filter2}<div class="xp-summary">{$summary}{$filter}</div>
<ul class="xp-results">
$output
</ul>
<div class="xp-pagination">$pagination</div>
BLOCK;
        //return '<pre>' . htmlspecialchars(var_export($page_res, 1)) . '</pre>';
    }
    
    protected function _asSearchResult($result) {
        $title = isset($result['title']) ? $result['title'] : 'title goes here';
        $link = isset($result['link']) ? htmlspecialchars($result['link']) : '';
        $foots = isset($result['foot']) ? htmlspecialchars($result['foot']) : '';
        if (isset($result['offset'])) {
            list($hms, $ms) = explode('.', $result['offset']);
            $link .= '/' . $hms;
            if (isset($result['test'])) {
                $result['test'] .= '/' . $hms;
            }
        } elseif ($link == '') {
            $link = '#';
        }
        if (isset($result['test'])) {
            $foots .= ' &nbsp; <a href="' . $result['test'] . '">.</a>';
        }
        $dtitle = htmlspecialchars($title);
        if (!empty($result['thumb'])) {
            $thumb = <<<BLOCK
<a href="$link" class="xp-thumb">
{$result['thumb']}
</a>
BLOCK;
        } else {
            $thumb = '';
        }
        return <<<BLOCK
<li class="xp-result">
    $thumb<h2><a href="$link" target="_blank">$dtitle</a></h2>
    <p>{$result['snip']}</p>
    <cite>$foots</cite>
</li>
BLOCK;
    }

    private function _asDuration($x) {
        $duration = $x;
        $hours = floor($duration / 3600);
        $duration %= 3600;
        $duration = sprintf('%d:%02d:%02d', $hours, floor($duration / 60), $duration % 60);
        return $duration;
    }
    
    private function _loadThumbnail($filename_source) {
        if (file_exists($filename_source)) {
            $image_info = getimagesize($filename_source);
            $wd = $image_info[0];
            $ht = $image_info[1];
            
            $new_height = 110;
            $new_width  = intval($wd * $new_height / $ht);
            $original = ImageCreateFromJPEG($filename_source);
            $image = imagecreatetruecolor($new_width, $new_height);
            imagecopyresized($image, $original, 0, 0, 0, 0, $new_width, $new_height, $wd, $ht);
            imagedestroy($original);
            // create the image tag, with size, class, and data
            
            ob_start();
            imagejpeg($image, NULL, 90);
            $rawImageBytes = base64_encode(ob_get_clean());
            imagedestroy($image);

            return "<img width=\"$new_width\" height=\"$new_height\" src=\"data:image/jpeg;base64,$rawImageBytes\">";
        }
        return '';
    }

    protected function loadDetails(&$res) {
		global $wpdb;
        $ids = [];
        foreach($res as $item) {
            $dhash = '"' . esc_sql($item['docid']) . '"';
            $ids[] = $dhash;
        }
        unset($item);
        if (count($ids)) {
            $temp = join(',', $ids);
            $rows = media_manager_class::Load(["mi_id in ($temp)"], ['group' => 'mixe_item', 'first' => FALSE, 'thumb' => TRUE]);
            $resultindex = [];
            foreach($rows as $index => $row) {
                $resultindex[$row->mi_id] = $index;
            }
            foreach($res as &$item) {
                if (isset($resultindex[$item['docid']])) {
                    $cliprow = $rows[$resultindex[$item['docid']]];
					$type = empty($cliprow->me_aslisting) ? $cliprow->mi_type : 'in';
                    $item['link'] = $this->generateAuth($type, $cliprow->mi_id, TRUE) . '#' . $type .'_' . $cliprow->mi_id;
                    $speaker = $cliprow->_speaker;
                    if ($cliprow->mi_type == 'mt' || $cliprow->mi_type == 'cbt') {
                        // breadcrumb: location (year) -> topic -> subject (1 of 9)
                        $item['title'] = $cliprow->mi_title;
                        $item['foot']  = $cliprow->me_year . ' – ' . $speaker . ' – ' . $this->_asDuration($cliprow->duration) . ' – ' .
                                         $cliprow->me_location . ' – ' . $cliprow->me_topic;
                    } else {
                        if (!empty($cliprow->mi_date)) {
                            $dt = strtotime($cliprow->mi_date . ' 12:00Z');
                            $item['foot'] = gmdate('l, F j, Y', $dt);
                        } else {
                            $item['foot'] = '';
                        }
                        if ($item['foot'] != '') $item['foot'] .= ' – ';
                        $item['foot'] .= $speaker;

                        if ($item['foot'] != '') $item['foot'] .= ' – ';
                        $item['foot'] .= $this->_asDuration($cliprow->duration);

                        if ($cliprow->mi_type == 'ex') {
                            $item['title'] = "Exhortation — $speaker";
                        } elseif (empty($cliprow->mi_title)) {
                            $item['title'] = $cliprow->me_topic;
                        } else {
                            $item['title'] = $cliprow->mi_title;
                            if ($item['foot'] != '') $item['foot'] .= ' – ';
                            $item['foot'] .= $cliprow->me_topic;
                        }
                        $item['thumb'] = $cliprow->mi_thumbnail;
                        if (!empty($cliprow->me_slug) && empty($cliprow->me_aslisting)) {
                            $item['link'] = $result = '/' . $cliprow->me_slug;
                            $seq = (int) $cliprow->mixe_sequence;
                            if ($seq > 1) $item['link'] .= '/' . $seq;
                            $item['link'] .= '#';
                        }
                    }
                } else {
                    $item['foot'] = $item['doctype'];
                }
            }
        }
    }

    public function generateAuth($doctype, $docid, $asLink = FALSE) {
        // doctype = ['mt', 'ex' ...];
        $auth = substr(md5('a^media#item:' . $doctype .':' . $docid), 7, 10);
        if ($asLink) {
            $safe_key = rawurlencode($docid);
            $auth = "/archive/?type={$doctype}&item={$safe_key}&auth={$auth}";
        }
        return $auth;
    }
	
    // generate a list of all audio for the given topic/year/location. 
    //  mark current one as active
    // if it's an exhort, don't get related exhorts!
    private function getRelatedContent($item, $event, $thumbs = FALSE, $specific = FALSE) {
        global $wpdb;
        $EX = static::EXHORT;
        $ditem = '"' . esc_sql($item) . '"';
        if (!$event) {
            $row = $wpdb->get_row("select mixe_event from `mm_item_x_event` where mixe_item={$ditem}");
            $event = is_null($row) ? 0 : (int) $row->mixe_event;
        }
        $devent    = '"' . esc_sql($event) . '"';
        if ($event == $EX || $specific) {
            $rows = media_manager_class::Load($item, [ 'first' => FALSE ] );    // exhort - no related
        } else {
            $param = [
                'first' => FALSE, 
                'order' => 'mixe_sequence',
            ];
            if ($thumbs) $param['thumb'] = TRUE;
            $rows = media_manager_class::Load(["mixe_event=$devent"], $param );
        }
        $max = 1;
        foreach($rows as $row) {
            if ($row->mixe_sequence > $max) $max = $row->mixe_sequence;
        }
        
        $group = [];
        foreach($rows as $row) {
            $node = [
                'type' => $row->mi_type,
                'item' => $row->mi_id,
                'active' => $item == $row->mi_id ? 1 : 0,
                'caption' => $row->mixe_sequence . ' of ' . $max,
                'hint' => $row->mi_title,
                'mp4' => (int) $row->present_mp4,
                'mp3' => (int) $row->present_mp3,
                'vtt' => (int) $row->present_vtt,
                'date' => $row->mi_date,
            ];
            if ($thumbs) $node['thumb'] = $row->mi_thumbnail;
            if ($node['mp3'] || $node['mp4']) {     // only if we have loaded some content
                $group[] = $node;
            }
        }
        return $group;
    }

	protected function _getAttachments($row) {
		$attach_source = 0;
		$attach_html = 0;
		if (is_array($row->_media)) {
			foreach($row->_media as $entry) {
				if ($entry->mm_type == 'zip') {
					$attach_source = (int) $entry->mm_source;
					break;
				} elseif ($entry->mm_type == 'html') {
					$attach_html = (int) $entry->mm_source;
				}
			}
		}
		if (!$attach_source && !$attach_html) return '';		// no attachments

		$output = '';
		if ($attach_html) {
			$contents = apply_filters('mm_get_contents', '', $row->mi_id, 'html', 1 << $attach_html);
			$output .= $contents;
		}
		if ($attach_source) {
			$link_zip = apply_filters('mm_generate_link', '', $row->mi_id, $item. '.zip', 1 << $attach_source);
			
			// get source file
			$contents = apply_filters('mm_get_contents', '', $row->mi_id, 'zip', 1 << $attach_source);
			$filename_temp = tempnam(sys_get_temp_dir(), 'zip');
			$filename_zip  = $filename_temp . '.zip';
			file_put_contents($filename_zip, $contents);

			$lines = '';
			$zip = new \ZipArchive;
			$zip->open($filename_zip);
			$result_stats = [];
			for ($i = 0; $i < $zip->numFiles; $i++)  {
				$stat = $zip->statIndex($i);
				$filename = htmlspecialchars($stat['name']);
				$sz = MediaSource::formatBytes($stat['size'], 1);
				$lines .= "<tr><td><a href=\"$link_zip/{$stat['index']}/$filename\" target=\"_blank\">$filename</a> ($sz)</td></tr>\n";
			}
			$zip->close();
			@unlink($filename_zip);
			@unlink($filename_temp);
			
			$output = <<<BLOCK
<h3>Attachments</h3>
<table>
$lines
</table>
BLOCK;
		}
		return $output;
	}

    private function _renderMediaPlayer($item, $event, $transcript = FALSE, $specific = FALSE, $single = FALSE) {
        global $wpdb, $post;
		
		
        $group = $this->getRelatedContent($item, $event, FALSE, $single);
        if ($item == '' && count($group)) $item = $group[0]['item'];
        $this->ActiveHash($item);

        $j = '';
        $activetab = FALSE;
        foreach($group as $tabindex => $groupitem) {
            $type = $groupitem['type'];
            $item = $groupitem['item'];

            $link_vtt = apply_filters('mm_generate_link', '', $item, $item. '.vtt', $groupitem['vtt']);
            if ($link_vtt != '') {
                if (strpos($link_vtt, '?') === FALSE) {
                    $link_vtt .= '?short=1';
                } else {
                    $link_vtt .= '&short=1';
                }
                $link_vtt = " cc=\"{$link_vtt}\"";
            }

            $row = media_manager_class::Load($item, ['first' => TRUE, 'media' => TRUE]);
            $other = $row->_other;
            
			$youtube = empty($row->_other->youtube) ? '' : $row->_other->youtube;
			$safelink = htmlspecialchars($youtube);
            if ($row->present_mp4) {
                $link_download = apply_filters('mm_generate_link', '', $item, $other->original, $row->present_mp4);
                if ($type == 'ex') {
                    $post->post_title = "Exhortation — {$row->_speaker}";
                } else {
                    $post->post_title = $row->me_topic;
                }
				$date = '';
                $dt = strtotime(str_replace('.', '-', $row->mi_date));
                if (!empty($dt)) {
					$date = date('l, F j, Y', $dt);
				}
				if ($row->me_showlink && !empty($youtube)) {
					if ($date == '') $date = '&nbsp;';
					$date = <<<BLOCK
<span style="float:right">
<a href="{$safelink}" target="_blank" style="font-weight:bold">Original URL<i class="fas fa-external-link-alt" title="Open external link in a new tab"></i></a> &nbsp;
</span>$date
BLOCK;
                }
				if ($date != '') $date = '<p style="max-width:870px">' . $date . '</p>' ;
                if (!empty($row->mi_title)) {
                    $safetitle = htmlspecialchars($row->mi_title);
                    $link_vtt .= " title=\"$safetitle\"";
                }
                $jpg = (int) $row->present_jpg;
                if ($jpg) {
                    $link_thumb = apply_filters('mm_generate_link', '', $item, '.jpg', $jpg);
                    $safeposter = htmlspecialchars($link_thumb);
                    if ($link_thumb != '') $link_vtt .= " poster=\"$safeposter\"";
                }
                $link_vtt .= " width=\"870\"";
				if ($item == '3f85ed78' || $item == 'f6589c42' || $item == '725cf0f6' || $item == '02fa2e3e') {
					$link_download = str_replace(['cdn1', 'cdn3' ], 'media',  $link_download);
				}
                $block = "[videoplayer src=\"$link_download\"{$link_vtt}]{$date}";
            } else {        // no video, so must be audio-only
                $thumb = $this->_getThumbnail_moorsetown($item);
				$date = '';

                // add duration & download size?
                $dlocation = htmlspecialchars($row->me_location);
                $dtopic = htmlspecialchars($row->me_topic);
                $dtitle = htmlspecialchars($row->mi_title);
                $dspeaker = htmlspecialchars($row->_speaker);
                $dfilename = rawurlencode($other->original);
                if (!empty($row->present_mp3)) {
                    $link_download = apply_filters('mm_generate_link', '', $item, $other->original, $groupitem['mp3']);
                    $download = "<a href=\"$link_download\">{$other->original}</a>";
                    $download = "<div><span class=\"xp-audio-field\">Download:</span><span class=\"xp-audio-value ellipsis\">$download</span></div>";
                } else {
                    $link_download = $download = '';
                }
				if ($row->me_showlink && !empty($youtube)) {
					$date = <<<BLOCK
<span style="float:right;padding-bottom:10px">
	<a href="{$safelink}" target="_blank" style="font-weight:bold">Original URL<i class="fas fa-external-link-alt" title="Open external link in a new tab."></i></a> |
</span>
BLOCK;
				} elseif ($type == 'cbt') {
					$date = <<<BLOCK
<span style="float:right;padding-bottom:10px">
	<a href="http://christadelphianbibletalks.com/#study/{$row->mi_moorestown}" target="_blank" style="font-weight:bold">Original URL<i class="fas fa-external-link-alt" title="open external link in a new tab"></i></a> |
</span>
BLOCK;
				} elseif ($type == 'mt') {
/*					$date = <<<BLOCK
<span style="float:right;padding-bottom:10px">
	Source: <a href="http://archive.moorestownchristadelphians.org/">archive.moorestownchristadelphians.org</a> |
</span>
BLOCK;
*/
					$date = '';			// archive site is gone
				}

                $year = ($row->me_year == '0000' || $row->me_year == '') ? '' : " ({$row->me_year})";

                $post->post_title = 'Audio Archive';
                $block = <<<BLOCK
<div class="xp-audio-container">
<div class="xp-audio-thumbnail">
$thumb
</div>
<div class="xp-audio-captions">
<div><span class="xp-audio-field">Location:</span><span class="xp-audio-value">$dlocation <span style="font-weight: normal">$year</span></span></div>
<div><span class="xp-audio-field">Topic:</span><span class="xp-audio-value">$dtopic</span></div>
<div><span class="xp-audio-field">Title:</span><span class="xp-audio-value">$dtitle</span></div>
<div><span class="xp-audio-field">Speaker:</span><span class="xp-audio-value">$dspeaker</span></div>
$download
</div>
</div>

BLOCK;
                if ($link_download != '') {
                    $block .= "[audio src=\"$link_download\" info=\"{$item}\"{$link_vtt}]{$date}";
                } else {
                    $block .= '<p>We do not have the audio for this item... are you able to help us?</p>';
                }
            }
            if ($this->pagenum && ($row->mixe_sequence == $this->pagenum)) {
                $activetab = $groupitem['type'] . '_' . $groupitem['item'];
            } elseif ($specific !== FALSE && $groupitem['item'] == $specific) {
                $activetab = $groupitem['type'] . '_' . $groupitem['item'];
            }
			$block .= $this->_getAttachments($row);

            if (!empty($transcript)) {
                $block .= $this->renderTranscript($type, $item, TRUE);
            }
            $j .= "\n<DIV class=\"media-search-tab\" id=\"{$groupitem['type']}_{$groupitem['item']}\">$block</DIV>\n";
        }
        if (count($group) > 1) {    // create tabs
            $tabline = '';
            foreach($group as $groupitem) {
                $type = $groupitem['type'];
                $item = $groupitem['item'];
                $safehint = htmlspecialchars($groupitem['hint']);
                $tabline .= "<li title=\"{$safehint}\"><a href=\"#{$groupitem['type']}_{$groupitem['item']}\">{$groupitem['caption']}</a></li>";
            }
            $j = <<<BLOCK
<div class="media-search-tabs">
<ul>$tabline</ul>
$j
</div>
BLOCK;
            if ($activetab) {
                $j .= <<<BLOCK
<script type="text/javascript">
window.mediaSearchTab = '{$activetab}';
</script>
BLOCK;
            };
			// since we have tabs, we may have a youtube playlist
			if (($row->me_playlist ?? 0) && ($row->me_playlistid ?? '') != '') {
				$j .= <<<BLOCK
<div style="text-align: center;margin-top:20px"><a href="https://www.youtube.com/playlist?list={$row->me_playlistid}">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"  width="40px" height="40px" viewBox="0 0 100 100">
<g transform="translate(0,100) scale(0.1,-0.1)" fill="#f00" stroke="none">
<path fill="#999" d="M197 832 c-10 -10 -17 -22 -17 -25 0 -4 153 -7 339 -7 187 0 347 -3
356 -6 39 -15 45 -54 45 -279 0 -128 4 -215 9 -215 6 0 17 10 25 22 13 19 16
61 16 253 0 192 -3 234 -16 253 l-15 22 -363 0 c-339 0 -364 -1 -379 -18z"/>
<path fill="#666" d="M127 762 c-10 -10 -17 -21 -17 -25 0 -3 156 -8 346 -9 l346 -3 19
-24 c17 -21 19 -42 19 -245 0 -123 3 -226 7 -229 3 -4 15 0 25 10 16 15 18 36
18 263 0 226 -2 248 -18 263 -17 15 -57 17 -374 17 -331 0 -356 -1 -371 -18z"/>
<path d="M46 678 c-24 -33 -24 -473 0 -506 l15 -22 363 0 c339 0 364 1 379 18
15 16 17 49 17 260 0 205 -2 243 -16 256 -13 14 -62 16 -379 16 l-364 0 -15
-22z m397 -194 c42 -24 77 -51 77 -58 0 -17 -159 -108 -177 -102 -9 4 -13 32
-13 106 0 120 0 120 113 54z"/>
</g>
</svg><span style="font-size:24pt;padding-left:13px;vertical-align: 10px;">YouTube Playlist</span></a></div>
BLOCK;
			}

        }
        return $j;
    }

    // Update 
	public function __wp_page() {
		global $post, $wpdb;
        // this plug intercepts the text '[videoplayer archive="hash"]' within a post/page
        $type = isset($_GET['type']) ? trim($_GET['type']) : '';
        $item = isset($_GET['item']) ? trim($_GET['item']) : '';
        $auth = isset($_GET['auth']) ? trim($_GET['auth']) : '';
        $expected_auth = $this->generateAuth($type, $item);

        $hasPlayer = preg_match('~\[videoplayer(.*?)\]~ims', $post->post_content, $match) ? $match[0] : '';
        $isListing = empty($this->header->me_aslisting) ? 0 : (int) $this->header->me_aslisting;
        if (strpos($hasPlayer, 'archive="hash"') !== FALSE && $auth != '' && $auth == $expected_auth) {
            $transcript = preg_match('~transcript="(.*?)"~', $hasPlayer, $match) ? $match[1] : FALSE;
            if (is_numeric($type)) {
                $event = $type;
            } else {
                $event = empty($this->header->__event) ? 0 : (int) $this->header->__event;
            }
            if ($isListing) {
                $j = $this->_generateEventListing($event);
            } else {
                $j = $this->_renderMediaPlayer($item, $event, $transcript, FALSE, $type == 'in');
            }
            $post->post_content = str_replace($hasPlayer, $j, $post->post_content);
		
        } elseif (preg_match('~archive="([0-9a-f]{8,10})"~', $hasPlayer, $matches )) {
            $transcript = preg_match('~transcript="(.*?)"~', $hasPlayer, $match) ? $match[1] : FALSE;
            if ($isListing) {
                $j = $this->_generateEventListing($event);
            } else {
                $j = $this->_renderMediaPlayer($matches[1], 0, $transcript, $matches[1], $type == 'in');
            }
            // maybe name of speaker should appear after date in videoplayer footer.
            $post->post_content = str_replace($hasPlayer, $j, $post->post_content);
            // add javascript to go to the active tab
        }
		global $wp_query;
		$wp_query->is_404 = FALSE;
	}
    
    public function _generateEventListing($me_id) {
        global $post;
        // heading
        $post->post_title = $this->header->me_topic;
       
        $output = '';
        // get everything in this event
        $group = $this->getRelatedContent(NULL, $me_id, TRUE);
        $group = array_reverse($group);
        foreach($group as $item) {
            $date = '&nbsp;';
            if (!empty($item['date'])) {
                $dt = strtotime("{$item['date']} 12:00Z");
                $date = gmdate('l, F j, Y', $dt);
            }
            $link = $this->generateAuth('in', $item['item'], TRUE);
            $safelink = htmlspecialchars($link);
			if (empty($item['thumb'])) {
				$thumb = $this->_getThumbnail_audio();
			} else {
				$thumb = $item['thumb'];
			}
            $output .= <<<BLOCK
<div class="searchlist-item" style="margin: 10px; flex-basis: calc(25% - 20px);">
    <div>$date</div>
    <div class="thumbnail" style="text-align: center; background-color: #d8d8d8; padding: 3px">
        <a href="{$safelink}">{$thumb}</a>
    </div>
    <div class="caption" style="text-align: center; font-weight: bold">
        <a href="{$safelink}">{$item['hint']}</a>
    </div>
</div>
BLOCK;
        }
        
        $admin = current_user_can('editor') || current_user_can('administrator');
        if ($admin) {
            $admin = admin_url( 'admin.php' );
            $admin = <<<BLOCK
<a href="{$admin}?page=talks-main-page&amp;action=event&amp;eid={$me_id}">(edit)</a>
BLOCK;
        } else {
            $admin = '';
        }
        // table
        return <<<BLOCK
<div class="searchlist-container" style="display: flex; flex-flow: row wrap">
$output
</div>
$admin
BLOCK;
    }
    
    public function _audioparam($list) {
        $list[] = 'info';
        $list[] = 'cc';
        return $list;
    }
    
    public function _audiowidget($html, $atts, $audio, $post_id, $library) {
        if (!empty($atts['info'])) {        // if specified, this is one of our audio tags
            // remove the stuff added by WordPress
            $html = preg_replace('~<source\s+type="".*?/>~ism', '', $html);
            $html = preg_replace('~<source\s+type="text/vtt".*?/>~ism', '', $html);

            // then add our stuff: captions
            if (!empty($atts['cc'])) {
                $p = strpos($html, '<a href=');
                $new = '<track id="trk" kind="subtitles" srclang="en" src="' . $atts['cc'] . '?_=1" default  />';
                $html = substr($html, 0, $p) . $new . substr($html, $p);
            }
        }
        return $html;
    }
    
    private function _getThumbnail_moorsetown($item) {
        $data = base64_encode(file_get_contents(__DIR__ . '/placeholder.png'));
        return "<img width=\"340\" height=\"340\" src=\"data:image/png;base64,$data\">";
    }

    private function _getThumbnail_audio() {
        $data = base64_encode(file_get_contents(__DIR__ . '/placeholder16.png'));
        return "<img width=\"340\" height=\"248\" src=\"data:image/png;base64,$data\">";
    }

    // load the VTT file - put in some basic formatting
    public function renderTranscript($type, $item, $asHTML = TRUE) {
        global $wpdb;
        $ditem = '"' . esc_sql($item) . '"';
        $row = $wpdb->get_row("select present_vtt from `mm_item` where mi_id={$ditem}");
        $text = apply_filters('mm_get_contents', '', $item, 'vtt', $row->present_vtt);
		
		$vtt = new vtt_class;
		$vtt->Content($text);
		$vttdata = $vtt->getEntries();
        if (!count($vttdata)) return '';    	   // no transcript available
        
		$output = $vtt->asTranscript($asHTML);
        if ($asHTML) {
            return <<<BLOCK
<div class="xp-transcript" id="trans_{$type}_{$item}">
    <h3>Transcript</h3>
    $output
</div>
BLOCK;
        } else {
            return <<<BLOCK
WEBVTT

$output
BLOCK;
        }

    }
    
    // set wide theme in Catalyst, but only if we are on our special page
    public function __setlayout( $layout ) {
        if ($this->slug) {
            return 'no-sidebar wide';
        }
        return $layout;
    }

    private function _checkIfvalid($basic, &$pagenum, $testmode = FALSE) {
        global $wpdb;
        if ($basic == '') return FALSE;
        $xbasic = '"' . esc_sql($basic) . '"';
        
        // first step - get the event this is part of
        $sql = "select me_id from `mm_event` as e where me_slug={$xbasic}";
        $event = $wpdb->get_row($sql);
		
        if (is_null($event)) return FALSE;
        $event = (int) $event->me_id;
        
        if (empty($pagenum)) {
            $sql = "select min(mixe_sequence) as mn from mm_item_x_event where mixe_event=$event";
            $data = $wpdb->get_row($sql);
            $pagenum = is_null($data) ? 1 : (int) $data->mn;
        } 

        $xseq   = '"' . esc_sql($pagenum) . '"';
        $where = [
            "mixe_event=$event", 
            "(mixe_sequence = 0 or mixe_sequence = $xseq)"
        ];
        if (!$testmode) $where[] = '((present_mp4 <> 0) or (present_mp3 <> 0))';
        
        $this->header = media_manager_class::Load($where);
		
        $ok = !is_null($this->header);
        if ($ok) $this->header->__event = $event;
        return $ok;
    }

    private function _loadThePage($pagenum) {
        global $wp_query;

        $args = [
            'p'         => static::SPECIAL_PAGE, // ID of a page, post, or custom type
            'post_type' => 'any'
        ];
        $wp_query = new WP_Query($args);
        $wp_query->post->post_title = $this->header->csh_title ?? '';
        $wp_query->post->post_name = "{$this->slug}/$pagenum";
        // the content of the special page is updated in $this->__wp_page().
    }
    
    public function __build_site_title( $title, $separator, $separator_location) {
        global $post;
        // this is only needed when catalyst is active
        if (function_exists('catalyst_get_core') && $post->ID == static::SPECIAL_PAGE) {
            $separator = catalyst_get_core( 'title_tag_separator' ) ? catalyst_get_core( 'title_tag_separator' ) : '|';
            $separator_location = catalyst_get_core( 'title_append_location' ) == 'Right' ? 'Right' : 'Left';

            $title = $post->post_title;     // revert back to original title - ignore catalyst seo stuff
            $title = $separator_location == 'Right'  
                        ? $title . " $separator " . get_bloginfo( 'name' ) 
                        : get_bloginfo( 'name' ) . " $separator " . $title;
            $title = esc_html( trim( $title ) ); 
        }
        return $title;
    }


    // remove Author section at top
    public function __author($meta) {
        if ($this->header) {
            return '';
        }
        return $meta;
    }

    // remove Categories: at bottom
    public function __categories($value) {
        if ($this->header) {
            return '';
        }
        return $value;
    }
    
    public function command_reindex($id) {
        global $wpdb;
        $did = '"' . esc_sql($id) . '"';
        $sql = "select mi_id,mi_date,mi_type,mi_title,present_vtt from mm_item where mi_id=$did";
        $row = $wpdb->get_row($sql);
        $text = apply_filters('mm_get_contents', '', $id, 'vtt', $row->present_vtt);
        $vtt = new class_xap;
        $vtt->FilenameDatabase($this->DATABASE)
            ->Open(FALSE);      // open for writing
        $vtt->DocumentType($row->mi_type)
            ->DocumentId($id)
            ->Content($text)
            ->Reindex();
    }
    
    public function remove_from_xapian($id) {
        $vtt = new class_xap;
        $vtt->FilenameDatabase($this->DATABASE)
            ->Open(FALSE);        // open for writing
        $vtt->DocumentId($id)
            ->Remove()
            ->Close();
    }

    function __edit_post_link($link, $post_id, $text) {
        $hash = $this->ActiveHash();
        if (!empty($hash)) {
            $link .= " | <a href=\"/wp-admin/admin.php?page=talks-single&amp;action=class&amp;cid={$hash}\" class=\"bind-editlink\">(details)</a>";
        }
        return $link;
    }

}

// prevent breadcrumbs from being displayed
// wp-content\themes\catalyst\lib\functions\catalyst-breadcrumbs.php
function breadcrumbs() {
	return '';
}

include_once dirname(__FILE__) . '/widget.php';
global $MEDIASEARCH;
$MEDIASEARCH = new media_search_class();

/*

SELECT count(cst_id), cst_date FROM `cd_seriest` WHERE 1 group by cst_date

	2 	2016-05-15
	2 	2016-09-18
	2 	2016-09-25
	2 	2017-03-19
	2 	2017-09-17
	2 	2017-09-24
	2 	2017-04-09
	2 	2018-03-04
	2 	2018-03-11
	2 	2018-03-25

Classes are in a mess; see below for example.  'ac' files need to be uploaded to the classes/year folder; data-entry to make sure slug name is unique

SELECT * FROM `cd_seriest` WHERE cst_date = '2020-06-24'

Last class should automagically appear on adult-class page

* thumbnail sizes for videos on the other tabs
* docid based on record id in detail file?

*/

Youez - 2016 - github.com/yon3zu
LinuXploit