/dev/null#m", "$interval * * * * $root/../bin/refresh_youtube.pl &2> /dev/null", $crontab); } else { $crontab .= "$interval * * * * $root/../bin/refresh_youtube.pl &2> /dev/null\n"; } file_put_contents("$root/.crontab.tmp", $crontab); error(shell_exec(escapeshellcmd("/usr/bin/crontab $root/.crontab.tmp"))); error(shell_exec(escapeshellcmd("/bin/rm $root/.crontab.tmp"))); } # Refresh feeds function refresh() { global $root; return shell_exec(escapeshellcmd("$root/../bin/refresh_youtube.pl")); } # Fix quotes for SQL input # Convert ip4 to a long int for comparison in permissions function ip4_2_long($ip) { $ips = preg_split('/\./',$ip); return($ips[3] | $ips[2] << 8 | $ips[1] << 16 | $ips[0] << 24); } # Convert ip6 to a long int for comparison in permissions function ip6_2_long($ip) { if (!function_exists('gmp_init') && !function_exists('bcadd')) { error('

403: Permission Denied

Requires either GMP or BCMATH extension

'); } $ip_n = inet_pton($ip); $bin = ''; for ($bit = strlen($ip_n) - 1; $bit >= 0; $bit--) { $bin = sprintf('%08b', ord($ip_n[$bit])) . $bin; } if (function_exists('gmp_init')) { return gmp_strval(gmp_init($bin, 2), 10); } elseif (function_exists('bcadd')) { $dec = '0'; for ($i = 0; $i < strlen($bin); $i++) { $dec = bcmul($dec, '2', 0); $dec = bcadd($dec, $bin[$i], 0); } return $dec; } else { exit(); trigger_error('GMP or BCMATH extension not installed!', E_USER_ERROR); } } # Check if given IP is in a ip4 CIDR range function in_range4($allowed,$cidr,$ip) { if ( ( ( ip4_2_long($ip) >= ip4_2_long($allowed) ) && ( ip4_2_long($ip) <= (ip4_2_long($allowed)+2**(32-$cidr)) ) ) || ($ip == $_SERVER['SERVER_ADDR'])) { return 1; } else { return 0; } } # Check if write is outside of read range function write_outside_read4($read4ip,$read4cidr,$write4ip,$write4cidr) { $readtop = ip4_2_long($read4ip)+2**(32-$read4cidr); $writetop = ip4_2_long($write4ip)+2**(32-$write4cidr); if (!in_range4($read4ip,$read4cidr,$write4ip) || ($writetop > $readtop)) { error("

write range ($write4ip/$write4cidr) must be contained within read range ($read4ip/$read4cidr)

"); return 1; } return 0; } # Check if given IP is in a ip6 CIDR range function in_range6($allowed,$cidr,$ip) { if ( ( ( ip6_2_long($ip) >= ip6_2_long($allowed) ) && ( ip6_2_long($ip) <= (ip6_2_long($allowed)+2**(32-$cidr)) ) ) || ($ip == $_SERVER['SERVER_ADDR'])) { return 1; } else { return 0; } } # Strip argument key and value from current target URL and return valid updated URL function remove_arg($target,$arg) { global $debug_text; if (preg_match("/\?$arg/",$target)) { # First argument, leave ? and remove trailing & if it exists $target = preg_replace("/$arg=[^&]*&?/",'',$target); if (preg_match("/\?$/",$target)) { $target = rtrim($target,"?"); } } else { # Secondary argument, remove leading & $target = preg_replace("/&$arg=[^&]*/",'',$target); } $debug_text .= "

Just removed $arg

Target is now: $target

"; return $target; } # Return whether the client IP is ip4 or ip6 function ip_version($ip) { if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { return 4; } elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { return 6; } else { return 0; } } # Return read and write permissions given client IP and preferences function get_permissions($settings) { $permissions = array(); if ($settings['ipv'] == 4) { $permissions['rip'] = preg_replace("/([\d\.]+)\/\d+/","$1",$settings['read4']); $permissions['wip'] = preg_replace("/([\d\.]+)\/\d+/","$1",$settings['write4']); $permissions['rcidr'] = preg_replace("/[\d\.]+\/(\d+)/","$1",$settings['read4']); $permissions['wcidr'] = preg_replace("/[\d\.]+\/(\d+)/","$1",$settings['write4']); if ($settings['enable4']) { $permissions['readable'] = in_range4($permissions['rip'],$permissions['rcidr'],$_SERVER['REMOTE_ADDR']); $permissions['writable'] = in_range4($permissions['wip'],$permissions['wcidr'],$_SERVER['REMOTE_ADDR']); } else { $permissions['readable'] = 0; $permissions['writable'] = 0; } } else { $permissions['rip'] = preg_replace("/([\d:]+)\/\d+/","$1",$settings['read6']); $permissions['wip'] = preg_replace("/([\d:]+)\/\d+/","$1",$settings['write6']); $permissions['rcidr'] = preg_replace("/[\d:]+\/(\d+)/","$1",$settings['read6']); $permissions['wcidr'] = preg_replace("/[\d:]+\/(\d+)/","$1",$settings['write6']); if ($settings['enable6']) { $permissions['readable'] = 1; //$permissions['readable'] = in_range6($permissions['rip'],$permissions['rcidr'],$_SERVER['REMOTE_ADDR']); $permissions['writable'] = 0; //$permissions['writable'] = in_range6($permissions['wip'],$permissions['wcidr'],$_SERVER['REMOTE_ADDR']); } else { $permissions['readable'] = 0; $permissions['writable'] = 0; } } return $permissions; } # Print one row given the video details function print_video($video,$settings,$mobile,$show_seen) { $html = '
'; if ($settings['player'] == 'embed') { if (isset($settings['embed_type']) && $settings['embed_type'] == 'nocookie') { $default .= ' '; } elseif (isset($settings['embed_type']) && $settings['embed_type'] == 'proxy') { $default .= ' '; } else { $default .= ' '; } } elseif ($settings['player'] == 'clip') { $default .= ' '; } else { $default .= ' '; } $html .= " $default" . '

' . $video['lengthText'] . '

'; if ($mobile) { $html .= '
'; if ($settings['embed_type'] == 'proxy') { $html .= '
'; } elseif ($settings['embed_type'] == 'nocookie') { $html .= '
'; } else { $html .= '
'; } $html .= '
'; if (isset($show_seen) && $show_seen && isset($video['seen']) && $video['seen']) { $html .= '

*

'; } else { $html .= '

X

'; } $html .= '
' . $default .'

' . $video['title'] . '

' . $video['shortViewCountText'] . '

' . $video['publishedTimeText'] . '

'; } else { $html .= '

' . $video['title'] . '

' . $video['shortViewCountText'] . '

' . $video['publishedTimeText'] . '

'; if ($settings['embed_type'] == 'proxy') { $html .= '
'; } elseif ($settings['embed_type'] == 'nocookie') { $html .= '
'; } else { $html .= '
'; } $html .= '
'; if (isset($show_seen) && $show_seen && isset($video['seen']) && $video['seen']) { $html .= '

*

'; } else { $html .= '

X

'; } $html .= ' '; } return $html; } # Add a new subscription. function add_channel($chan) { global $root; return shell_exec(escapeshellcmd("$root/../bin/add_youtube_subscription.pl $chan")); } # Fix quotes for SQL input function sql_escape($text) { return preg_replace("/'/","''",$text); } # Open database and fetch all settings $no_db = 1; if (!file_exists($database)) { $permissions = array("readable"=>"1","writable"=>"1"); } else { $no_db = 0; $handle = new SQLite3($database); $settings_row = $handle->query('SELECT * FROM settings'); $settings = $settings_row->fetchArray(); } # If default player is 'embed' we need to select the right 'embed_type' if (isset($settings['player']) && $settings['player'] == 'embed' && isset($settings['embed_type'])) { $settings['player'] == $settings['embed_type']; } # Fetch client IP version, then check permissions for that IP if (!isset($permissions)) { $settings['ipv'] = ip_version($_SERVER['REMOTE_ADDR']); if (isset($settings['ipv'])) { $permissions = get_permissions($settings); # Reject connection if PHP does not support ip6 } elseif (!defined('AF_INET6')) { error('

403: Permission Denied

PHP does not support IPv6 and your address is: ' . $_SERVER['REMOTE_ADDR'] . '

'); # Reject connection if not a valid ip4 or ip6 address (what else is it?) } else { error('

403: Permission Denied

Invalid remote IP address: ' . $_SERVER['REMOTE_ADDR'] . '

'); } } # Reject connection if not readable if (!$permissions['readable']) { error('

403: Permission Denied

You do not have readable access to this page

'); if ($settings['ipv'] == 4) { if ($settings['enable4']) { error('

Your IP (' . $_SERVER['REMOTE_ADDR'] . ') falls outside of readable range (' . $settings['read4'] . ')

'); } else { error('

IPv4 Interface is disabled

'); } } else { if ($settings['enable6']) { error('

Your IP (' . $_SERVER['REMOTE_ADDR'] . ') falls outside of readable range (' . $settings['read6'] . ')

'); } else { error('

IPv6 Interface is disabled

'); } } } # Collect URL arguments $target = $_SERVER['REQUEST_URI']; $args = array(); parse_str($_SERVER['QUERY_STRING'], $args); # Some arguments will require the page to be reloaded after processing. # They will set this flag which will be checked after all arguements are done. $reload = 0; ## STARTREAD: Read options included prior to the ENDREAD line if (isset($args['mobile'])) { if ($args['mobile'] != '' && $args['mobile'] != '0') { $mobile = 1; } else { $mobile = 0; } $target = remove_arg($target,'mobile'); unset($args['mobile']); } # Retrieve passed error message if (isset($args['msg'])) { if ($args['msg'] != '') { error(urldecode($args['msg'])); } $target = remove_arg($target,'msg'); unset($args['msg']); } # Add parameters to SQL query to restrict by search results if (isset($args['search'])) { if (isset($args['search_clear']) || $args['search'] == '') { unset($args['search_clear']); $target = remove_arg($target,'search_clear'); $target = remove_arg($target,'search'); } else { $search = urldecode($args['search']); # If search is quoted with '/', treat it as a regex if (preg_match('#^/.*/$#',$search)) { $sql_search = preg_replace('#^/(.*)/$#', '$1', $search); $regex_search = $handle->loadExtension('pcre.so'); if ($regex_search) { $sql_search = " videos.title REGEXP '" . $sql_search . "'"; } else { # emulate regex if module is unavailable $new_search = ''; if (preg_match('/^\^/', $sql_search)) { $start = preg_replace('/^\^([^\.]*).*/','$1',$sql_search); $sql_search = preg_replace('/^\^[^\.]*(.*)$/','$1',$sql_search); $new_search .= " AND videos.title LIKE '" . $start . "%'"; } if (preg_match('/\$$/', $sql_search)) { $tail = preg_replace('/^.*?([^\*]*)\$$/','$1',$sql_search); $sql_search = preg_replace('/^(.*?)[^\*]*\$$/','$1',$sql_search); $new_search .= " AND videos.title LIKE '%" . $tail . "'"; } $sql_search = preg_replace('/(\.\*)?(.*)(\.\*)?/','$2',$sql_search); $searches = preg_split('/\.\*/', $sql_search); foreach ($searches as $s) { if ($s != '') { $new_search .= " AND videos.title LIKE '%" . $s . "%'"; } } $sql_search = preg_replace('/ AND(.*)/', '$1', $new_search); error("You do not have the pcre.so extension for SQLite3."); if ($permissions['writable']) { error("

You will need to add the path to your extensions directory with an argument in your php.ini file like:

sqlite3.extension_dir = \"/usr/lib/sqlite3/\"

"); } error ("

Simulating basic wildcard (.*) and anchor (^$) expression:

$sql_search

"); } } else { $sql_search = " videos.title LIKE '%" . sql_escape($search) . "%'"; } } unset($args['search']); $target = remove_arg($target,'search'); } # Set flag to not hide videos marked as seen from SQL query results if (isset($args['show_seen'])) { $show_seen = $args['show_seen']; unset($args['show_seen']); $target = remove_arg($target,'show_seen'); } # Remove previous filter arguments to revert to regular query results if (isset($args['filter_clear'])) { unset($args['filter_clear']); $target = remove_arg($target,'filter_clear'); unset($args['filter']); $target = remove_arg($target,'filter'); unset($args['filter[]']); $target = remove_arg($target,urlencode('filter[]')); } # Add claus(es) to SQL query to limit by channel or category if (isset($args['filter'])) { $categories = array(); if (!is_array($args['filter'])) { if (preg_match('/^UC[0-9a-zA-Z\_\-]{22}$/', $args['filter'])) { $chan_filter = $args['filter']; } elseif ($args['filter'] != '-') { $categories[0] = $args['filter']; } $target = remove_arg($target,'filter'); $target = remove_arg($target,urlencode('filter[]')); } else { foreach ($args['filter'] as $filter) { $categories[] = urldecode($filter); $target = remove_arg($target,urlencode('filter[]')); } $target = remove_arg($target,'filter'); } unset($args['filter']); unset($args['filter[]']); } ## ENDREAD # If there are remaining arguments, ensure that the client has write permissions if ($permissions['writable']) { if (sizeof($args) > 0) { $reload = 1; } } elseif ($permissions['readable']) { if (sizeof($args) > 0) { $reload = 1; error("

You are allowed to read but not write. However, a write argument was passed.

"); unset($args); $target = preg_replace('/^([^\?]*)\?.*/','$1',$_SERVER['REQUEST_URI']); } } else { error('
' . $debug_text . '
'); return http_response_code(403); } ## IMPORTANT: Write operations to be included after this point only. # Initialize database if ($no_db && isset($args['initialize'])) { $debug_text .= "

Initializing database...

"; $debug_text .= "

" . initialize() . "

"; $reload = 1; $target = remove_arg($target,'initialize'); $handle = new SQLite3($database); $no_db = 0; $args['refresh'] = 15; } # Delete database if (isset($args['delete_db'])) { $debug_text .= "

Deleting database

"; $err = delete_db(); if ($err) { error("

Failed to delete db: $err

"); } else { $debug_text .= "

Database deleted

"; } } # Handle all change requests. if ( !$no_db && $reload && $permissions['writable'] ) { # Force feeds to refresh if (isset($args['force_refresh']) && $args['force_refresh'] == 1) { $debug_text .= "

Refreshing feeds...

"; # Must free database in order to insert $handle->close(); $debug_text .= "

" . refresh() . "

"; $handle = new SQLite3($database); $target = remove_arg($target,'force_refresh'); $reload = 1; } # Mark one or all videos as played. if (isset($args['rm'])) { if (preg_match("/^([^']{11})$/",urldecode($args['rm']))) { $debug_text .= "

Removing " . urldecode($args['rm']) . "

"; $rm = sql_escape(urldecode($args['rm'])); $handle->query("UPDATE videos SET seen = 1 WHERE videoId = '$rm'"); } elseif (urldecode($args['rm']) == '*') { $debug_text .= "

Removing All

"; $handle->query("UPDATE videos SET seen = 1 WHERE seen = 0"); } $target = remove_arg($target,'rm'); } # Mark one or all videos as played. if (isset($args['unrm'])) { if (preg_match("/^([^']{11})$/",urldecode($args['unrm']))) { $debug_text .= "

Restoring " . urldecode($args['unrm']) . "

"; $unrm = sql_escape(urldecode($args['unrm'])); $handle->query("UPDATE videos SET seen = 0 WHERE videoId = '$unrm'"); } $target = remove_arg($target,'unrm'); } # Update the refresh interval if (isset($args['refresh'])) { $interval = urldecode($args['refresh']); if (preg_match('/^[0-9]+$/',$interval) && $interval <= 60) { $handle->query("UPDATE settings SET refresh = '$interval'"); update_cron($interval); $debug_text .= "

Set update interval to $interval

"; } else { error("

Update interval must be an integer lower than or equal to 60, $interval is not

"); } $target = remove_arg($target,'refresh'); } # Update the default player if (isset($args['player'])) { if (preg_match("/^(clip|embed|web)$/",urldecode($args['player']))) { $player = sql_escape(urldecode($args['player'])); $handle->query("UPDATE settings SET player = '$player'"); $debug_text .= "

Set player $player

"; } else { error("

" . $args['player'] . " is not a valid player option

"); } $target = remove_arg($target,'player'); } # Update the embedded player type if (isset($args['embed_type'])) { if (preg_match("/^(embed|nocookie|proxy)$/",urldecode($args['embed_type']))) { $embed_type = sql_escape(urldecode($args['embed_type'])); $handle->query("UPDATE settings SET embed_type = '$embed_type'"); $debug_text .= "

Set embed_type $embed_type

"; } else { error("

" . $args['embed_type'] . " is not a valid embed_type option

"); } $target = remove_arg($target,'embed_type'); } # Update the theme if (isset($args['theme'])) { $found = 0; $theme = urldecode($args['theme']); foreach (glob($root.'/css/*.css') as $file) { $file = preg_replace('/.*\/([^\/]*)\.css$/','\1',$file); if ($args['theme'] == $file && $file != 'style') { $handle->query("UPDATE settings SET theme = '" . sql_escape($theme) . "'"); $debug_text .= "

Set theme $theme

"; $found = 1; } } if (!$found) { error("

" . $theme . " is not a valid theme option

"); } $target = remove_arg($target,'theme'); } # Subscribe to a new channel if (isset($args['sub'])) { $debug_text .= "

Adding " . urldecode($args['sub']) . "...

"; # Must free database in order to insert $handle->close(); $debug_text .= "

" . add_channel(urldecode($args['sub'])) . "

"; $handle = new SQLite3($database); $reload = 1; $target = remove_arg($target,'sub'); } # Unsubscribe and remove channel's videos. if (isset($args['unsub'])) { $unsub = sql_escape(urldecode($args['unsub'])); $channels = $handle->query("SELECT * FROM channels WHERE channelId = '$unsub'"); $chan = $channels->fetchArray(); $debug_text .= "

Removing " . $chan['channelName'] . "

"; $handle->query("DELETE FROM channels WHERE channelId = '$unsub'"); $handle->query("DELETE FROM videos WHERE channelId = '$unsub'"); $target = remove_arg($target,'unsub'); } # Update IPv4 writeable range if (isset($args['write4'])) { $write4ip = preg_replace("/([\d\.]+)\/\d+/","$1",urldecode($args['write4'])); $write4cidr = preg_replace("/[\d\.]+\/(\d+)/","$1",urldecode($args['write4'])); if (isset($args['read4'])) { $read4ip = preg_replace("/([\d\.]+)\/\d+/","$1",urldecode($args['read4'])); $read4cidr = preg_replace("/[\d\.]+\/(\d+)/","$1",urldecode($args['read4'])); } else { $read4ip = preg_replace("/([\d\.]+)\/\d+/","$1",$settings['read4']); $read4cidr = preg_replace("/[\d\.]+\/(\d+)/","$1",$settings['read4']); } if ( write_outside_read4($read4ip,$read4cidr,$write4ip,$write4cidr) ) { error("

Invalid read/write. Write must be completely contained within read.

"); $target = remove_arg($target,'write4'); $target = remove_arg($target,'read4'); unset($args['read4']); } elseif ( in_range4($write4ip,$write4cidr,$_SERVER['REMOTE_ADDR']) ) { $handle->query("UPDATE settings SET write4 = '$write4ip/$write4cidr'"); $debug_text .= "

Updated writable IPv4 to $write4ip/$write4cidr

"; $target = remove_arg($target,'write4'); } else { error("

You are not allowed to remove write access from yourself. " . $_SERVER['REMOTE_ADDR'] . " is not in range $write4ip/$write4cidr

"); $target = remove_arg($target,'write4'); } } # Update IPv4 readable range if (isset($args['read4'])) { # Update whether IPv4 is enabled if (isset($args['enable4']) && $args['enable4'] == 'on') { if (!$settings['enable4']) { $debug_text .= "

Enabling IPv4

"; $handle->query("UPDATE settings SET enable4 = 1"); } } else { if ($settings['ipv'] == 4) { error("

You are currently using the IPv4 interface (Your IP: " . $_SERVER['REMOTE_ADDR'] . "). You cannot remove access from yourself.

"); } else { $debug_text .= "

Disabling IPv4

"; #$handle->query("UPDATE settings SET enable4 = 0"); } } $target = remove_arg($target,'enable4'); $read4ip = preg_replace("/([\d\.]+)\/\d+/","$1",urldecode($args['read4'])); $read4cidr = preg_replace("/[\d\.]+\/(\d+)/","$1",urldecode($args['read4'])); if ( in_range4($read4ip,$read4cidr,$_SERVER['REMOTE_ADDR']) ) { $handle->query("UPDATE settings SET read4 = '$read4ip/$read4cidr'"); $debug_text .= "

Updated readable IPv4 to $read4ip/$read4cidr

"; $target = remove_arg($target,'read4'); } else { error("

You are not allowed to remove read access from yourself. " . $_SERVER['REMOTE_ADDR'] . " is not in range $read4ip/$read4cidr

"); $target = remove_arg($target,'read4'); } } # Update IPv6 readable range if (isset($args['read6'])) { # Update whether IPv6 is enabled if (isset($args['enable6']) && $args['enable6'] == 'on') { if (!$settings['enable6']) { $debug_text .= "

Enabling IPv6

"; $handle->query("UPDATE settings SET enable6 = 1"); } } else { if ($settings['ipv'] == 6) { error("

You are currently using the IPv6 interface (Your IP: " . $_SERVER['REMOTE_ADDR'] . "). You cannot remove access from yourself.

"); } else { $debug_text .= "

Disabling IPv6

"; $handle->query("UPDATE settings SET enable6 = 0"); } } $target = remove_arg($target,'enable6'); $handle->query("UPDATE settings SET read6 = '" . urldecode($args['read6']) . "'"); $debug_text .= "

Updated readable IPv6 to " . $args['read6'] . "

"; $target = remove_arg($target,'read6'); } if (isset($args['write6'])) { $handle->query("UPDATE settings SET write6 = '" . urldecode($args['write6']) . "'"); $debug_text .= "

Updated writable IPv6 to " . $args['write6'] . "

"; $target = remove_arg($target,'write6'); } # Update the Category for channel if (isset($args['category'])) { # URL arg is formatted as 'channelId category,other,third' $category_array = preg_split('/\ /',urldecode($args['category'])); foreach ($category_array as $item) { # First item is the channelId if (!isset($category_chan)) { $category_chan = $item; # All others are categories } elseif (!isset($category_name)) { $category_name = $item; # Restore spaces removed by split } else { $category_name .= ' ' . $item; } } $category_chan = sql_escape($category_chan); $category_name = sql_escape($category_name); if ($category_name != '') { $category_name = ','.$category_name.','; } $debug_text .= '

Add Category - Channel: ' . $category_chan . ' Category: ' . $category_name . '

'; $handle->query("UPDATE channels SET category = '$category_name' WHERE channelId = '$category_chan'"); $debug_text .= '

Category updated

'; $target = remove_arg($target,'category'); } # Update the Title Filter RegExp for a channel if (isset($args['regex'])) { $regex_array = preg_split('/\ /',urldecode($args['regex'])); foreach ($regex_array as $item) { if (!isset($regex_chan)) { $regex_chan = $item; } elseif (!isset($regex_exp)) { $regex_exp = $item; } else { $regex_exp .= ' ' . $item; } } $regex_chan = sql_escape($regex_chan); $regex_exp = sql_escape($regex_exp); $debug_text .= '

Add Regex - Channel: ' . $regex_chan . ' Exp: ' . $regex_exp . '

'; $handle->query("UPDATE channels SET regex = '$regex_exp' WHERE channelId = '$regex_chan'"); $debug_text .= '

Regex updated

'; $target = remove_arg($target,'regex'); } # If there are remaining arguments, they are invalid if (preg_match("/[^\?]*\?(.*)/",$target)) { $remaining = preg_replace("/^(.)*\?(.*)$/",'$2',$target); $debug_text .= "

Remaining: $remaining

"; $bad_args = preg_split("/\&/",$remaining); foreach ($bad_args as $arg) { $value = preg_replace("/[^=]*=([^=]*)/","$1",$arg); $arg = preg_replace("/([^=]*)=[^=]*/","$1",$arg); error('

Ignoring invalid argument: ' . urldecode($arg) . ' with value ' . urldecode($value) . '

'); $target = remove_arg($target,$arg); } } } # Clean up target URL given intact arguments $target = preg_replace('/[\?&]$/', '', $target); # Close DB then refresh the page with what should be a clean URL if ($reload) { if ($debug && isset($debug_text) && $debug_text != '') { $debug_text .= '
Load ' . $target . '
'; $debug_text = '

DEBUG MODE

' . $debug_text . '
'; } else { if (isset($handle)) { $handle->close(); } header("Refresh:0; url=$target" . ((isset($error_text) && $error_text != '') ? "?msg=".urlencode($error_text) : '')); exit(); } } $head = ' YouTube Subscriptions '; if ($mobile) { $head .= ' '; } $head .= ' '; if (isset($settings['theme']) && $settings['theme'] != 'default') { $head .= ' '; } $head .= ' '; if (isset($permissions['readable']) && $permissions['readable']) { # HEADER $header = '