inited=false; $this->site_path=getenv('DOCUMENT_ROOT'); wp_register_sidebar_widget('fdroid_latest', 'FDroid Latest', 'widget_fdroidlatest'); } // Register additional query variables. (Handler for the 'query_vars' filter) function queryvars($qvars) { $qvars[]='fdfilter'; $qvars[]='fdcategory'; $qvars[]='fdid'; $qvars[]='fdpage'; $qvars[]='fdstyle'; return $qvars; } // Lazy initialise. All non-trivial members should call this before doing anything else. function lazyinit() { if(!$this->inited) { load_plugin_textdomain($this->textdom, PLUGINDIR.'/'.dirname(plugin_basename(__FILE__)), dirname(plugin_basename(__FILE__))); $this->inited=true; } } // Gets a required query parameter by name. function getrequiredparam($name) { global $wp_query; if(!isset($wp_query->query_vars[$name])) wp_die("Missing parameter ".$name,"Error"); return $wp_query->query_vars[$name]; } // Handler for the 'fdroidrepo' shortcode. // $attribs - shortcode attributes // $content - optional content enclosed between the starting and // ending shortcode // Returns the generated content. function do_shortcode($attribs,$content=null) { global $wp_query,$wp_rewrite; $this->lazyinit(); // Init local query vars foreach($this->queryvars(array()) as $qv) { if(array_key_exists($qv,$wp_query->query_vars)) { $query_vars[$qv] = $wp_query->query_vars[$qv]; } else { $query_vars[$qv] = null; } } // Sanity check and standardise all query variables... if(!isset($query_vars['fdpage']) || !is_numeric($query_vars['fdpage']) || $query_vars['fdpage'] <= 0) { $query_vars['fdpage'] = 1; } else { $query_vars['fdpage'] = strval(intval($query_vars['fdpage'])); } if(isset($query_vars['fdstyle']) && ($query_vars['fdstyle'] != 'list' && $query_vars['fdstyle'] != 'grid')) { $query_vars['fdstyle'] = 'list'; } if(isset($query_vars['fdcategory'])) { if($query_vars['fdcategory'] == 'All categories') { unset($query_vars['fdcategory']); } else { $query_vars['fdcategory'] = sanitize_text_field($query_vars['fdcategory']); } } if(isset($query_vars['fdfilter'])) { $query_vars['fdfilter'] = sanitize_text_field($query_vars['fdfilter']); } else { if(isset($attribs['search'])) { $query_vars['fdfilter'] = ''; } } if(isset($query_vars['fdid'])) { $query_vars['fdid'] = sanitize_text_field($query_vars['fdid']); } $out = ''; if($query_vars['fdid']!==null) { $out.=$this->get_app($query_vars); } else { $out.='
'; $out.='

'; $out.=''; $out.='

'; $out.=$this->makeformdata($query_vars); $out.='
'."\n"; $out.=$this->get_apps($query_vars); } return $out; } // Get a URL for a full description of a license, as given by one of our // pre-defined license abbreviations. This is a temporary function, as this // needs to be data-driven so the same information can be used by the client, // the web site and the documentation. function getlicenseurl($license) { switch($license) { case 'MIT': return 'https://www.gnu.org/licenses/license-list.html#X11License'; case 'NewBSD': return 'https://www.gnu.org/licenses/license-list.html#ModifiedBSD'; case 'BSD': return 'https://www.gnu.org/licenses/license-list.html#OriginalBSD'; case 'GPLv3': case 'GPLv3+': return 'https://www.gnu.org/licenses/license-list.html#GNUGPLv3'; case 'GPLv2': case 'GPLv2+': return 'https://www.gnu.org/licenses/license-list.html#GPLv2'; case 'AGPLv3': case 'AGPLv3+': return 'https://www.gnu.org/licenses/license-list.html#AGPLv3.0'; case 'LGPL': return 'https://www.gnu.org/licenses/license-list.html#LGPL'; case 'LGPL': case 'LGPLv3': return 'https://www.gnu.org/licenses/license-list.html#LGPLv3'; case 'LGPLv2.1': return 'https://www.gnu.org/licenses/license-list.html#LGPLv2.1'; case 'Apache2': return 'https://www.gnu.org/licenses/license-list.html#apache2'; case 'WTFPL': return 'https://www.gnu.org/licenses/license-list.html#WTFPL'; default: return null; } } function androidversion($sdkLevel) { if ($sdkLevel < 1) return null; switch ($sdkLevel) { case 19: return "4.4"; case 18: return "4.3"; case 17: return "4.2"; case 16: return "4.1"; case 15: return "4.0.3"; case 14: return "4.0"; case 13: return "3.2"; case 12: return "3.1"; case 11: return "3.0"; case 10: return "2.3.3"; case 9: return "2.3"; case 8: return "2.2"; case 7: return "2.1"; case 6: return "2.0.1"; case 5: return "2.0"; case 4: return "1.6"; case 3: return "1.5"; case 2: return "1.1"; case 1: return "1.0"; default: return "?"; } } function get_app($query_vars) { global $permissions_data; $permissions_object = new AndroidPermissions($this->site_path.'/wp-content/plugins/wp-fdroid/AndroidManifest.xml', $this->site_path.'/wp-content/plugins/wp-fdroid/strings.xml', sys_get_temp_dir().'/android-permissions.cache'); $permissions_data = $permissions_object->get_permissions_array(); // Get app data $xml = simplexml_load_file($this->site_path.'/repo/index.xml'); foreach($xml->children() as $app) { $attrs=$app->attributes(); if($attrs['id']==$query_vars['fdid']) { $apks=array();; foreach($app->children() as $el) { switch($el->getName()) { case "name": $name=$el; break; case "icon": $icon=$el; break; case "summary": $summary=$el; break; case "desc": $desc=$el; break; case "license": $license=$el; break; case "source": $source=$el; break; case "tracker": $issues=$el; break; case "donate": $donate=$el; break; case "flattr": $flattr=$el; break; case "web": $web=$el; break; case "antifeatures": $antifeatures=$el; break; case "requirements": $requirements=$el; break; case "package": $thisapk=array(); foreach($el->children() as $pel) { switch($pel->getName()) { case "version": $thisapk['version']=$pel; break; case "vercode": $thisapk['vercode']=$pel; break; case "added": $thisapk['added']=$pel; break; case "apkname": $thisapk['apkname']=$pel; break; case "srcname": $thisapk['srcname']=$pel; break; case "hash": $thisapk['hash']=$pel; break; case "size": $thisapk['size']=$pel; break; case "sdkver": $thisapk['sdkver']=$pel; break; case "maxsdkver": $thisapk['maxsdkver']=$pel; break; case "nativecode": $thisapk['nativecode']=$pel; break; case "permissions": $thisapk['permissions']=$pel; break; } } $apks[]=$thisapk; } } // Generate app diff data foreach(array_reverse($apks, true) as $key=>$apk) { if(isset($previous)) { // Apk size $apks[$key]['diff']['size'] = $apk['size']-$previous['size']; } // Permissions $permissions = explode(',',$apk['permissions']); $permissionsPrevious = isset($previous['permissions'])?explode(',',$previous['permissions']):array(); $apks[$key]['diff']['permissions']['added'] = array_diff($permissions, $permissionsPrevious); $apks[$key]['diff']['permissions']['removed'] = array_diff($permissionsPrevious, $permissions); $previous = $apk; } // Output app information $out='
'; $out.='
'; $out.='

'.$name.""; $out.="
".$summary."

"; $out.="
"; $out.=str_replace('href="fdroid.app:', 'href="/repository/browse/?fdid=', $desc); if(isset($antifeatures)) { $antifeaturesArray = explode(',',$antifeatures); foreach($antifeaturesArray as $antifeature) { $antifeatureDescription = $this->get_antifeature_description($antifeature); $out.='

'.$antifeatureDescription['name'].'
'; $out.=$antifeatureDescription['description'].' more...

'; } } $out.="

"; $licenseurl=$this->getlicenseurl($license); $out.="License: "; if($licenseurl) $out.=''; $out.=$license; if($licenseurl) $out.=''; if(isset($requirements)) { $out.='
Additional requirements: '.$requirements; } $out.="

"; $out.="

"; if(strlen($web)>0) $out.='Website: '.$web.'
'; if(strlen($issues)>0) $out.='Issue Tracker: '.$issues.'
'; if(strlen($source)>0) $out.='Source Code: '.$source.'
'; if($donate && strlen($donate)>0) $out.='Donate: '.$donate.'
'; if($flattr && strlen($flattr)>0) $out.='Flattr:
'; $out.="

"; $out.="

For full details and additional technical information, see "; $out.="this application's page on the F-Droid wiki.

"; $out.=''; $out.="

Packages

"; $out.='
Download F-Droid
'; $out.="

Although APK downloads are available below to give "; $out.="you the choice, you should be aware that by installing that way you "; $out.="will not receive update notifications, and it's a less secure way "; $out.="to download. "; $out.="We recommend that you install the F-Droid client and use that.

"; $i=0; foreach($apks as $apk) { $first = $i+1==count($apks); $out.="

Version ".$apk['version'].""; $out.=" - Added on ".$apk['added']."
"; $hasminsdk = isset($apk['sdkver']); $hasmaxsdk = isset($apk['maxsdkver']); if($hasminsdk && $hasmaxsdk) { $out.="

This version requires Android ".$this->androidversion($apk['sdkver'])." up to ".$this->androidversion($apk['maxsdkver'])."

"; } elseif($hasminsdk) { $out.="

This version requires Android ".$this->androidversion($apk['sdkver'])." or newer.

"; } elseif($hasmaxsdk) { $out.="

This version requires Android ".$this->androidversion($apk['maxsdkver'])." or old.

"; } $hasabis = isset($apk['nativecode']); if($hasabis) { $abis = str_replace(',', ' ', $apk['nativecode']); $out.="

This version uses native code and will only run on: ".$abis."

"; } // Is this source or binary? $srcbuild = isset($apk['srcname']) && file_exists($this->site_path.'/repo/'.$apk['srcname']); $out.="

This version is built and signed by "; if($srcbuild) { $out.="F-Droid, and guaranteed to correspond to the source tarball below.

"; } else { $out.="the original developer.

"; } $out.='download apk '; $out.=$this->human_readable_size($apk['size']); $diffSize = $apk['diff']['size']; if(abs($diffSize) > 500) { $out.=' ('; $out.=$diffSize>0?'+':''; $out.=$this->human_readable_size($diffSize, 1).')'; } if($srcbuild) { $out.='
source tarball '; $out.=$this->human_readable_size(filesize($this->site_path.'/repo/'.$apk['srcname'])); } if(isset($apk['permissions'])) { // Permissions diff link if($first == false) { $permissionsAddedCount = count($apk['diff']['permissions']['added']); $permissionsRemovedCount = count($apk['diff']['permissions']['removed']); $divIdDiff='permissionsDiff'.$i; if($permissionsAddedCount || $permissionsRemovedCount) { $out.='
permissions diff'; $out.=' ('; if($permissionsAddedCount) $out.='+'.$permissionsAddedCount; if($permissionsAddedCount && $permissionsRemovedCount) $out.='/'; if($permissionsRemovedCount) $out.='-'.$permissionsRemovedCount; $out.=')'; } else { $out.='
no permission changes'; } } // Permissions list link $permissionsListString = $this->get_permission_list_string(explode(',',$apk['permissions']), $permissions_data, $summary); /*if($i==0) $divStyleDisplay='block'; else*/ $divStyleDisplay='none'; $divId='permissions'.$i; $out.='
view permissions'; $out.=' ['.$summary.']'; $out.='
'; // Permissions list $out.='
'; $out.=$permissionsListString; $out.='
'; // Permissions diff { $out.='
'; $permissionsRemoved = $apk['diff']['permissions']['removed']; usort($permissionsRemoved, "permissions_cmp"); // Added permissions if($permissionsAddedCount) { $out.='
ADDED

'; $out.=$this->get_permission_list_string($apk['diff']['permissions']['added'], $permissions_data, $summary); } // Removed permissions if($permissionsRemovedCount) { $out.='
REMOVED

'; $out.=$this->get_permission_list_string($apk['diff']['permissions']['removed'], $permissions_data, $summary); } $out.='
'; } } else { $out.='
no extra permissions needed
'; } $out.='

'; $i++; } $out.='

Index

'; return $out; } } return "

Application not found

"; } private function get_permission_list_string($permissions, $permissions_data, &$summary) { $out=''; usort($permissions, "permissions_cmp"); $permission_group_last = ''; foreach($permissions as $permission) { $permission_group = $permissions_data['permission'][$permission]['permissionGroup']; if($permission_group != $permission_group_last) { $permission_group_label = $permissions_data['permission-group'][$permission_group]['label']; if($permission_group_label=='') $permission_group_label = 'Extra/Custom'; $out.=''.strtoupper($permission_group_label).'
'; $permission_group_last = $permission_group; } $out.=$this->get_permission_protection_level_icon($permissions_data['permission'][$permission]['protectionLevel']).' '; $out.=''.$permissions_data['permission'][$permission]['label'].' ['.$permission.']
'; if($permissions_data['permission'][$permission]['description']) $out.=$permissions_data['permission'][$permission]['description'].'
'; //$out.=$permissions_data['permission'][$permission]['comment'].'
'; $out.='
'; if(!isset($summaryCount[$permissions_data['permission'][$permission]['protectionLevel']])) $summaryCount[$permissions_data['permission'][$permission]['protectionLevel']] = 0; $summaryCount[$permissions_data['permission'][$permission]['protectionLevel']]++; } $summary = ''; if(isset($summaryCount)) { foreach($summaryCount as $protectionLevel => $count) { $summary .= $this->get_permission_protection_level_icon($protectionLevel, 'regular').' '.$count; $summary .= ', '; } } $summary = substr($summary,0,-2); return $out; } private function get_permission_protection_level_icon($protection_level, $size='adjusted') { $iconString = ''; if($protection_level=='dangerous') { $iconString .= ''; // WARNING SIGN } elseif($protection_level=='normal') { $iconString .= ''; // CIRCLED LATIN SMALL LETTER I } elseif($protection_level=='signature') { $iconString .= ''; // HEAVY TEARDROP-SPOKED ASTERISK } elseif($protection_level=='signatureOrSystem') { $iconString .= ''; // ATOM SYMBOL } else { $iconString .= ''; // GEAR } return $iconString; } private function human_readable_size($size, $minDiv=0) { $si_prefix = array('bytes','kB','MB'); $div = 1024; for($i=0;(abs($size) > $div && $i < count($si_prefix)) || $i<$minDiv;$i++) { $size /= $div; } return round($size,max(0,$i-1)).' '.$si_prefix[$i]; } private function get_antifeature_description($antifeature) { // Anti feature names and descriptions $antifeatureDescription['Ads']['name'] = 'Advertising'; $antifeatureDescription['Ads']['description'] = 'This application contains advertising.'; $antifeatureDescription['Tracking']['name'] = 'Tracks You'; $antifeatureDescription['Tracking']['description'] = 'This application tracks and reports your activity to somewhere.'; $antifeatureDescription['NonFreeNet']['name'] = 'Non-Free Network Services'; $antifeatureDescription['NonFreeNet']['description'] = 'This application promotes a non-Free network service.'; $antifeatureDescription['NonFreeAdd']['name'] = 'Non-Free Addons'; $antifeatureDescription['NonFreeAdd']['description'] = 'This application promotes non-Free add-ons.'; $antifeatureDescription['NonFreeDep']['name'] = 'Non-Free Dependencies'; $antifeatureDescription['NonFreeDep']['description'] = 'This application depends on another non-Free application.'; $antifeatureDescription['UpstreamNonFree']['name'] = 'Upstream Non-Free'; $antifeatureDescription['UpstreamNonFree']['description'] = 'The upstream source code is non-free.'; if(isset($antifeatureDescription[$antifeature])) { return $antifeatureDescription[$antifeature]; } return array('name'=>$antifeature); } function get_apps($query_vars) { $xml = simplexml_load_file($this->site_path."/repo/index.xml"); $matches = $this->show_apps($xml,$query_vars,$numpages); $out=''; if(($query_vars['fdfilter']===null || $query_vars['fdfilter']!='') && $numpages>0) { $out.='
'; if($query_vars['fdfilter']===null) { $categories = array('All categories'); $handle = fopen(getenv('DOCUMENT_ROOT').'/repo/categories.txt', 'r'); if ($handle) { while (($buffer = fgets($handle, 4096)) !== false) { $categories[] = rtrim($buffer); } fclose($handle); } $out.='
'; $out.=$this->makeformdata($query_vars); $out.=''; $out.='
'."\n"; } else { $out.='Applications matching "'.$query_vars['fdfilter'].'"'; } $out.="
"; $out.='
'; $out.='List | '; $out.='Grid'; $out.='
'; $out.='
'; } if($numpages>0) { $out.=$matches; $out.='

'; $out.='

'; $out.=' Page '.$query_vars['fdpage'].' of '.$numpages.' '; $out.='
'; $out.='
'; if($numpages>1) { for($i=1;$i<=$numpages;$i++) { if($i == $query_vars['fdpage']) { $out.=''.$i.''; } else { $out.=''; $out.=$i; $out.=''; } $out.=' '; } $out.=' '; } $out.='
'; $out.='
'; if($query_vars['fdpage']!=$numpages) { $out.='next> '; } $out.='
'; $out.='

'; } else if($query_vars['fdfilter']!='') { $out.='

No matches

'; } return $out; } function makeformdata($query_vars) { $out=''; $out.=''; foreach($query_vars as $name => $value) { if($value !== null && $name != 'fdfilter' && $name != 'fdpage') $out.=''; } return $out; } function show_apps($xml,$query_vars,&$numpages) { $skipped=0; $got=0; $total=0; if($query_vars['fdstyle']=='grid') { $outputter = new FDOutGrid(); } else { $outputter = new FDOutList(); } $out = ""; $out.=$outputter->outputStart(); foreach($xml->children() as $app) { if($app->getName() == 'repo') continue; $appinfo['attrs']=$app->attributes(); $appinfo['id']=$appinfo['attrs']['id']; foreach($app->children() as $el) { switch($el->getName()) { case "name": $appinfo['name']=$el; break; case "icon": $appinfo['icon']=$el; break; case "summary": $appinfo['summary']=$el; break; case "desc": $appinfo['description']=$el; break; case "license": $appinfo['license']=$el; break; case "category": $appinfo['category']=$el; break; } } if(($query_vars['fdfilter']===null || $query_vars['fdfilter']!='' && (stristr($appinfo['name'],$query_vars['fdfilter']) || stristr($appinfo['id'],$query_vars['fdfilter']) || stristr($appinfo['summary'],$query_vars['fdfilter']) || stristr($appinfo['description'],$query_vars['fdfilter']))) && (!isset($query_vars['fdcategory']) || $query_vars['fdcategory'] && $query_vars['fdcategory']==$appinfo['category'])) { if($skipped<($query_vars['fdpage']-1)*$outputter->perpage) { $skipped++; } else if($got<$outputter->perpage) { $out.=$outputter->outputEntry($query_vars, $appinfo); $got++; } $total++; } } $out.=$outputter->outputEnd(); $numpages = ceil((float)$total/$outputter->perpage); return $out; } } // Class to output app entries in a detailed list format class FDOutList { var $perpage=30; function FDOutList() { } function outputStart() { return ''; } function outputEntry($query_vars, $appinfo) { $out=""; $out.='
'."\n"; $out.=''; $out.='
'; $out.='
'; $out.='
'; $out.='

Details...

'; $out.="
\n"; $out.='

'.$appinfo['name'].""; $out.="
".$appinfo['summary']."

\n"; $out.="
\n"; $out.='
'; return $out; } function outputEnd() { return ''; } } // Class to output app entries in a compact grid format class FDOutGrid { var $perpage=80; var $itemCount = 0; function FDOutGrid() { } function outputStart() { return "\n".''."\n"; } function outputEntry($query_vars, $appinfo) { $link=makelink($query_vars, array('fdid'=>$appinfo['id'])); $out=''; if($this->itemCount%4 == 0 && $this->itemCount > 0) { $out.=''."\n"; } $out.='\n"; $this->itemCount++; return $out; } function outputEnd() { return '
'; $out.='

'; $out.='

"; $out.='

'; $out.="
'."\n"; } } function permissions_cmp($a, $b) { global $permissions_data; $aProtectionLevel = $permissions_data['permission'][$a]['protectionLevel']; $bProtectionLevel = $permissions_data['permission'][$b]['protectionLevel']; if($aProtectionLevel != $bProtectionLevel) { if(strlen($aProtectionLevel)==0) return 1; if(strlen($bProtectionLevel)==0) return -1; return strcmp($aProtectionLevel, $bProtectionLevel); } $aGroup = $permissions_data['permission'][$a]['permissionGroup']; $bGroup = $permissions_data['permission'][$b]['permissionGroup']; if($aGroup != $bGroup) { return strcmp($aGroup, $bGroup); } return strcmp($a, $b); } // Make a link to this page, with the current query vars attached and desired params added/modified function makelink($query_vars, $params=array()) { $link=get_permalink(); $p = array_merge($query_vars, $params); // Page 1 is the default, don't clutter urls with it... if($p['fdpage'] == 1) unset($p['fdpage']); // Likewise for list style... if($p['fdstyle'] == 'list') unset($p['fdstyle']); $vars=linkify($p); if(strlen($vars)==0) return $link; if(strpos($link,'?')===false) $link.='?'; else $link.='&'; return $link.$vars; } // Return the key value pairs in http-get-parameter format as a string function linkify($vars) { $retvar = ''; foreach($vars as $k => $v) { if($k!==null && $v!==null && $v!='') $retvar .= $k.'='.$v.'&'; } return substr($retvar,0,-1); } function widget_fdroidlatest($args) { extract($args); echo $before_widget; echo $before_title . 'Latest Apps' . $after_title; $handle = fopen(getenv('DOCUMENT_ROOT').'/repo/latestapps.dat', 'r'); if ($handle) { while (($buffer = fgets($handle, 4096)) !== false) { $app = explode("\t", $buffer); echo ''; if(isset($app[2]) && trim($app[2])) { echo ''; } echo $app[1].'
'; if(isset($app[3]) && trim($app[3])) { echo ''.$app[3].''; } echo '

'; } fclose($handle); } echo $after_widget; } $wp_fdroid = new FDroid(); ?>