aurjson.class.php: Style fixes

* Fix braces, indentation and comment style.
* Remove some superfluous comments.
* Reword some comments.

Signed-off-by: Lukas Fleischer <archlinux@cryptocrack.de>
This commit is contained in:
Lukas Fleischer 2014-04-28 11:00:39 +02:00
parent e50f352643
commit 11a565936e

View file

@ -1,361 +1,363 @@
<?php <?php
/**
* AurJSON
*
* This file contains the AurRPC remote handling class
**/
include_once("aur.inc.php"); include_once("aur.inc.php");
/** /*
* This class defines a remote interface for fetching data * This class defines a remote interface for fetching data from the AUR using
* from the AUR using JSON formatted elements. * JSON formatted elements.
*
* @package rpc * @package rpc
* @subpackage classes * @subpackage classes
**/ */
class AurJSON { class AurJSON {
private $dbh = false; private $dbh = false;
private static $exposed_methods = array( private static $exposed_methods = array(
'search', 'info', 'multiinfo', 'msearch', 'suggest' 'search', 'info', 'multiinfo', 'msearch', 'suggest'
); );
private static $fields = array( private static $fields = array(
'Packages.ID', 'Packages.Name', 'PackageBases.Name AS PackageBase', 'Packages.ID', 'Packages.Name',
'Version', 'CategoryID', 'Description', 'URL', 'NumVotes', 'PackageBases.Name AS PackageBase', 'Version', 'CategoryID',
'OutOfDateTS AS OutOfDate', 'Users.UserName AS Maintainer', 'Description', 'URL', 'NumVotes', 'OutOfDateTS AS OutOfDate',
'SubmittedTS AS FirstSubmitted', 'ModifiedTS AS LastModified' 'Users.UserName AS Maintainer',
); 'SubmittedTS AS FirstSubmitted', 'ModifiedTS AS LastModified'
private static $numeric_fields = array( );
'ID', 'CategoryID', 'NumVotes', 'OutOfDate', 'FirstSubmitted', private static $numeric_fields = array(
'LastModified' 'ID', 'CategoryID', 'NumVotes', 'OutOfDate', 'FirstSubmitted',
); 'LastModified'
);
/** /*
* Handles post data, and routes the request. * Handles post data, and routes the request.
* @param string $post_data The post data to parse and handle. *
* @return string The JSON formatted response data. * @param string $post_data The post data to parse and handle.
**/ *
public function handle($http_data) { * @return string The JSON formatted response data.
// unset global aur headers from aur.inc */
// leave expires header to enforce validation public function handle($http_data) {
// header_remove('Expires'); /*
// unset global aur.inc pragma header. We want to allow caching of data * Unset global aur.inc.php Pragma header. We want to allow
// in proxies, but require validation of data (if-none-match) if * caching of data in proxies, but require validation of data
// possible * (if-none-match) if possible.
*/
header_remove('Pragma'); header_remove('Pragma');
// overwrite cache-control header set in aur.inc to allow caching, but /*
// require validation * Overwrite cache-control header set in aur.inc.php to allow
* caching, but require validation.
*/
header('Cache-Control: public, must-revalidate, max-age=0'); header('Cache-Control: public, must-revalidate, max-age=0');
header('Content-Type: application/json, charset=utf-8'); header('Content-Type: application/json, charset=utf-8');
// handle error states if (!isset($http_data['type']) || !isset($http_data['arg'])) {
if ( !isset($http_data['type']) || !isset($http_data['arg']) ) { return $this->json_error('No request type/data specified.');
return $this->json_error('No request type/data specified.'); }
} if (!in_array($http_data['type'], self::$exposed_methods)) {
return $this->json_error('Incorrect request type specified.');
}
// do the routing $this->dbh = DB::connect();
if ( in_array($http_data['type'], self::$exposed_methods) ) {
// set up db connection.
$this->dbh = DB::connect();
// ugh. this works. I hate you php. $json = call_user_func(array(&$this, $http_data['type']), $http_data['arg']);
$json = call_user_func(array(&$this, $http_data['type']),
$http_data['arg']);
// calculate etag as an md5 based on the json result $etag = md5($json);
// this could be optimized by calculating the etag on the header("Etag: \"$etag\"");
// query result object before converting to json (step into /*
// the above function call) and adding the 'type' to the response, * Make sure to strip a few things off the
// but having all this code here is cleaner and 'good enough' * if-none-match header. Stripping whitespace may not
$etag = md5($json); * be required, but removing the quote on the incoming
header("Etag: \"$etag\""); * header is required to make the equality test.
// make sure to strip a few things off the if-none-match */
// header. stripping whitespace may not be required, but $if_none_match = isset($_SERVER['HTTP_IF_NONE_MATCH']) ?
// removing the quote on the incoming header is required trim($_SERVER['HTTP_IF_NONE_MATCH'], "\t\n\r\" ") : false;
// to make the equality test if ($if_none_match && $if_none_match == $etag) {
$if_none_match = isset($_SERVER['HTTP_IF_NONE_MATCH']) ? header('HTTP/1.1 304 Not Modified');
trim($_SERVER['HTTP_IF_NONE_MATCH'], "\t\n\r\" ") : false; return;
if ($if_none_match && $if_none_match == $etag) { }
header('HTTP/1.1 304 Not Modified');
return; if (isset($http_data['callback'])) {
header('content-type: text/javascript');
return $http_data['callback'] . "({$json})";
} else {
header('content-type: application/json');
return $json;
}
}
/*
* Returns a JSON formatted error string.
*
* @param $msg The error string to return
*
* @return mixed A json formatted error response.
*/
private function json_error($msg) {
header('content-type: application/json');
return $this->json_results('error', 0, $msg);
}
/*
* Returns a JSON formatted result data.
*
* @param $type The response method type.
* @param $data The result data to return
*
* @return mixed A json formatted result response.
*/
private function json_results($type, $count, $data) {
return json_encode(array(
'version' => 2,
'type' => $type,
'resultcount' => $count,
'results' => $data
));
}
private function get_extended_fields($pkgid) {
$query = "SELECT DependencyTypes.Name AS Type, " .
"PackageDepends.DepName AS Name, " .
"PackageDepends.DepCondition AS Cond " .
"FROM PackageDepends " .
"LEFT JOIN DependencyTypes " .
"ON DependencyTypes.ID = PackageDepends.DepTypeID " .
"WHERE PackageDepends.PackageID = " . $pkgid . " " .
"UNION SELECT RelationTypes.Name AS Type, " .
"PackageRelations.RelName AS Name, " .
"PackageRelations.RelCondition AS Cond " .
"FROM PackageRelations " .
"LEFT JOIN RelationTypes " .
"ON RelationTypes.ID = PackageRelations.RelTypeID " .
"WHERE PackageRelations.PackageID = " . $pkgid . " " .
"UNION SELECT 'groups' AS Type, Groups.Name, '' AS Cond " .
"FROM Groups INNER JOIN PackageGroups " .
"ON PackageGroups.PackageID = " . $pkgid . " " .
"AND PackageGroups.GroupID = Groups.ID " .
"UNION SELECT 'license' AS Type, Licenses.Name, '' AS Cond " .
"FROM Licenses INNER JOIN PackageLicenses " .
"ON PackageLicenses.PackageID = " . $pkgid . " " .
"AND PackageLicenses.LicenseID = Licenses.ID";
$result = $this->dbh->query($query);
if (!$result) {
return null;
}
$type_map = array(
'depends' => 'Depends',
'makedepends' => 'MakeDepends',
'checkdepends' => 'CheckDepends',
'optdepends' => 'OptDepends',
'conflicts' => 'Conflicts',
'provides' => 'Provides',
'replaces' => 'Replaces',
'groups' => 'Groups',
'license' => 'License',
);
$data = array();
while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
$type = $type_map[$row['Type']];
$data[$type][] = $row['Name'] . $row['Cond'];
}
return $data;
}
private function process_query($type, $where_condition) {
global $MAX_RPC_RESULTS;
$fields = implode(',', self::$fields);
$query = "SELECT {$fields} " .
"FROM Packages LEFT JOIN PackageBases " .
"ON PackageBases.ID = Packages.PackageBaseID " .
"LEFT JOIN Users ON PackageBases.MaintainerUID = Users.ID " .
"WHERE ${where_condition}";
$result = $this->dbh->query($query);
if ($result) {
$resultcount = 0;
$search_data = array();
while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
$resultcount++;
$pkgbase_name = $row['PackageBase'];
$row['URLPath'] = URL_DIR . substr($pkgbase_name, 0, 2) . "/" . $pkgbase_name . "/" . $pkgbase_name . ".tar.gz";
/*
* Unfortunately, mysql_fetch_assoc() returns
* all fields as strings. We need to coerce
* numeric values into integers to provide
* proper data types in the JSON response.
*/
foreach (self::$numeric_fields as $field) {
$row[$field] = intval($row[$field]);
}
if ($type == 'info' || $type == 'multiinfo') {
$row = array_merge($row, $this->get_extended_fields($row['ID']));
}
if ($type == 'info') {
$search_data = $row;
break;
} else {
array_push($search_data, $row);
}
} }
// allow rpc callback for XDomainAjax if ($resultcount === $MAX_RPC_RESULTS) {
if ( isset($http_data['callback']) ) { return $this->json_error('Too many package results.');
// it is more correct to send text/javascript }
// content-type for jsonp-callback
header('content-type: text/javascript');
return $http_data['callback'] . "({$json})";
}
else {
// set content type header to app/json
header('content-type: application/json');
return $json;
}
}
else {
return $this->json_error('Incorrect request type specified.');
}
}
/** return $this->json_results($type, $resultcount, $search_data);
* Returns a JSON formatted error string. } else {
* return $this->json_results($type, 0, array());
* @param $msg The error string to return }
* @return mixed A json formatted error response. }
**/
private function json_error($msg) {
// set content type header to app/json
header('content-type: application/json');
return $this->json_results('error', 0, $msg);
}
/** /*
* Returns a JSON formatted result data. * Parse the args to the multiinfo function. We may have a string or an
* @param $type The response method type. * array, so do the appropriate thing. Within the elements, both * package
* @param $data The result data to return * IDs and package names are valid; sort them into the relevant arrays and
* @return mixed A json formatted result response. * escape/quote the names.
**/ *
private function json_results($type, $count, $data) { * @param $args the arg string or array to parse.
return json_encode(array( *
'version' => 2, * @return mixed An array containing 'ids' and 'names'.
'type' => $type, */
'resultcount' => $count, private function parse_multiinfo_args($args) {
'results' => $data if (!is_array($args)) {
)); $args = array($args);
} }
private function get_extended_fields($pkgid) { $id_args = array();
$query = "SELECT DependencyTypes.Name AS Type, " . $name_args = array();
"PackageDepends.DepName AS Name, " . foreach ($args as $arg) {
"PackageDepends.DepCondition AS Cond " . if (!$arg) {
"FROM PackageDepends " . continue;
"LEFT JOIN DependencyTypes " . }
"ON DependencyTypes.ID = PackageDepends.DepTypeID " . if (is_numeric($arg)) {
"WHERE PackageDepends.PackageID = " . $pkgid . " " . $id_args[] = intval($arg);
"UNION SELECT RelationTypes.Name AS Type, " . } else {
"PackageRelations.RelName AS Name, " . $name_args[] = $this->dbh->quote($arg);
"PackageRelations.RelCondition AS Cond " . }
"FROM PackageRelations " . }
"LEFT JOIN RelationTypes " .
"ON RelationTypes.ID = PackageRelations.RelTypeID " .
"WHERE PackageRelations.PackageID = " . $pkgid . " " .
"UNION SELECT 'groups' AS Type, Groups.Name, '' AS Cond " .
"FROM Groups INNER JOIN PackageGroups " .
"ON PackageGroups.PackageID = " . $pkgid . " " .
"AND PackageGroups.GroupID = Groups.ID " .
"UNION SELECT 'license' AS Type, Licenses.Name, '' AS Cond " .
"FROM Licenses INNER JOIN PackageLicenses " .
"ON PackageLicenses.PackageID = " . $pkgid . " " .
"AND PackageLicenses.LicenseID = Licenses.ID";
$result = $this->dbh->query($query);
if (!$result) { return array('ids' => $id_args, 'names' => $name_args);
return null; }
}
$type_map = array( /*
'depends' => 'Depends', * Performs a fulltext mysql search of the package database.
'makedepends' => 'MakeDepends', *
'checkdepends' => 'CheckDepends', * @param $keyword_string A string of keywords to search with.
'optdepends' => 'OptDepends', *
'conflicts' => 'Conflicts', * @return mixed Returns an array of package matches.
'provides' => 'Provides', */
'replaces' => 'Replaces', private function search($keyword_string) {
'groups' => 'Groups', global $MAX_RPC_RESULTS;
'license' => 'License',
);
$data = array();
while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
$type = $type_map[$row['Type']];
$data[$type][] = $row['Name'] . $row['Cond'];
}
return $data; if (strlen($keyword_string) < 2) {
} return $this->json_error('Query arg too small');
}
private function process_query($type, $where_condition) { $keyword_string = $this->dbh->quote("%" . addcslashes($keyword_string, '%_') . "%");
global $MAX_RPC_RESULTS;
$fields = implode(',', self::$fields);
$query = "SELECT {$fields} " .
"FROM Packages LEFT JOIN PackageBases " .
"ON PackageBases.ID = Packages.PackageBaseID " .
"LEFT JOIN Users ON PackageBases.MaintainerUID = Users.ID " .
"WHERE ${where_condition}";
$result = $this->dbh->query($query);
if ($result) { $where_condition = "(Packages.Name LIKE $keyword_string OR ";
$resultcount = 0; $where_condition .= "Description LIKE $keyword_string) ";
$search_data = array(); $where_condition .= "LIMIT $MAX_RPC_RESULTS";
while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
$resultcount++;
$pkgbase_name = $row['PackageBase'];
$row['URLPath'] = URL_DIR . substr($pkgbase_name, 0, 2) . "/" . $pkgbase_name . "/" . $pkgbase_name . ".tar.gz";
/* Unfortunately, mysql_fetch_assoc() returns all fields as return $this->process_query('search', $where_condition);
* strings. We need to coerce numeric values into integers to }
* provide proper data types in the JSON response.
*/
foreach (self::$numeric_fields as $field) {
$row[$field] = intval($row[$field]);
}
if ($type == 'info' || $type == 'multiinfo') { /*
$row = array_merge($row, $this->get_extended_fields($row['ID'])); * Returns the info on a specific package.
} *
* @param $pqdata The ID or name of the package. Package Query Data.
*
* @return mixed Returns an array of value data containing the package data
*/
private function info($pqdata) {
if (is_numeric($pqdata)) {
$where_condition = "Packages.ID = $pqdata";
} else {
$where_condition = "Packages.Name = " . $this->dbh->quote($pqdata);
}
if ($type == 'info') { return $this->process_query('info', $where_condition);
$search_data = $row; }
break;
}
else {
array_push($search_data, $row);
}
}
if ($resultcount === $MAX_RPC_RESULTS) { /*
return $this->json_error('Too many package results.'); * Returns the info on multiple packages.
} *
* @param $pqdata A comma-separated list of IDs or names of the packages.
*
* @return mixed Returns an array of results containing the package data
*/
private function multiinfo($pqdata) {
global $MAX_RPC_RESULTS;
return $this->json_results($type, $resultcount, $search_data); $args = $this->parse_multiinfo_args($pqdata);
} $ids = $args['ids'];
else { $names = $args['names'];
return $this->json_results($type, 0, array());
}
}
/** if (!$ids && !$names) {
* Parse the args to the multiinfo function. We may have a string or an return $this->json_error('Invalid query arguments');
* array, so do the appropriate thing. Within the elements, both * package }
* IDs and package names are valid; sort them into the relevant arrays and
* escape/quote the names.
* @param $args the arg string or array to parse.
* @return mixed An array containing 'ids' and 'names'.
**/
private function parse_multiinfo_args($args) {
if (!is_array($args)) {
$args = array($args);
}
$id_args = array(); $where_condition = "";
$name_args = array(); if ($ids) {
foreach ($args as $arg) { $ids_value = implode(',', $args['ids']);
if (!$arg) { $where_condition .= "ID IN ($ids_value) ";
continue; }
} if ($ids && $names) {
if (is_numeric($arg)) { $where_condition .= "OR ";
$id_args[] = intval($arg); }
} else { if ($names) {
$name_args[] = $this->dbh->quote($arg); /*
} * Individual names were quoted in
} * parse_multiinfo_args().
*/
$names_value = implode(',', $args['names']);
$where_condition .= "Packages.Name IN ($names_value) ";
}
$where_condition .= "LIMIT $MAX_RPC_RESULTS";
return array('ids' => $id_args, 'names' => $name_args); return $this->process_query('multiinfo', $where_condition);
} }
/** /*
* Performs a fulltext mysql search of the package database. * Returns all the packages for a specific maintainer.
* @param $keyword_string A string of keywords to search with. *
* @return mixed Returns an array of package matches. * @param $maintainer The name of the maintainer.
**/ *
private function search($keyword_string) { * @return mixed Returns an array of value data containing the package data
global $MAX_RPC_RESULTS; */
if (strlen($keyword_string) < 2) { private function msearch($maintainer) {
return $this->json_error('Query arg too small'); global $MAX_RPC_RESULTS;
}
$keyword_string = $this->dbh->quote("%" . addcslashes($keyword_string, '%_') . "%"); $maintainer = $this->dbh->quote($maintainer);
$where_condition = "(Packages.Name LIKE {$keyword_string} OR "; $where_condition = "Users.Username = $maintainer ";
$where_condition.= "Description LIKE {$keyword_string}) "; $where_condition .= "LIMIT $MAX_RPC_RESULTS";
$where_condition.= "LIMIT {$MAX_RPC_RESULTS}";
return $this->process_query('search', $where_condition); return $this->process_query('msearch', $where_condition);
} }
/** /*
* Returns the info on a specific package. * Get all package names that start with $search.
* @param $pqdata The ID or name of the package. Package Query Data. *
* @return mixed Returns an array of value data containing the package data * @param string $search Search string.
**/ *
private function info($pqdata) { * @return string The JSON formatted response data.
if ( is_numeric($pqdata) ) { */
// just using sprintf to coerce the pqd to an int private function suggest($search) {
// should handle sql injection issues, since sprintf will $query = 'SELECT Name FROM Packages WHERE Name LIKE ' .
// bork if not an int, or convert the string to a number 0 $this->dbh->quote(addcslashes($search, '%_') . '%') .
$where_condition = "Packages.ID={$pqdata}"; ' ORDER BY Name ASC LIMIT 20';
}
else {
$where_condition = sprintf("Packages.Name=%s", $this->dbh->quote($pqdata));
}
return $this->process_query('info', $where_condition);
}
/** $result = $this->dbh->query($query);
* Returns the info on multiple packages. $result_array = array();
* @param $pqdata A comma-separated list of IDs or names of the packages.
* @return mixed Returns an array of results containing the package data
**/
private function multiinfo($pqdata) {
global $MAX_RPC_RESULTS;
$args = $this->parse_multiinfo_args($pqdata);
$ids = $args['ids'];
$names = $args['names'];
if (!$ids && !$names) { if ($result) {
return $this->json_error('Invalid query arguments'); $result_array = $result->fetchAll(PDO::FETCH_COLUMN, 0);
} }
$where_condition = ""; return json_encode($result_array);
if ($ids) { }
$ids_value = implode(',', $args['ids']);
$where_condition .= "ID IN ({$ids_value}) ";
}
if ($ids && $names) {
$where_condition .= "OR ";
}
if ($names) {
// individual names were quoted in parse_multiinfo_args()
$names_value = implode(',', $args['names']);
$where_condition .= "Packages.Name IN ({$names_value}) ";
}
$where_condition .= "LIMIT {$MAX_RPC_RESULTS}";
return $this->process_query('multiinfo', $where_condition);
}
/**
* Returns all the packages for a specific maintainer.
* @param $maintainer The name of the maintainer.
* @return mixed Returns an array of value data containing the package data
**/
private function msearch($maintainer) {
global $MAX_RPC_RESULTS;
$maintainer = $this->dbh->quote($maintainer);
$where_condition = "Users.Username = {$maintainer} ";
$where_condition .= "LIMIT {$MAX_RPC_RESULTS}";
return $this->process_query('msearch', $where_condition);
}
/**
* Get all package names that start with $search.
* @param string $search Search string.
* @return string The JSON formatted response data.
**/
private function suggest($search) {
$query = 'SELECT Name FROM Packages WHERE Name LIKE ' .
$this->dbh->quote(addcslashes($search, '%_') . '%') .
' ORDER BY Name ASC LIMIT 20';
$result = $this->dbh->query($query);
$result_array = array();
if ($result) {
$result_array = $result->fetchAll(PDO::FETCH_COLUMN, 0);
}
return json_encode($result_array);
}
} }