Files

665 lines
18 KiB
PHP

<?php
/**
* query.php
*
* Script called by the viewer, provides search results for places, land sales end events
*
* Requires OpenSimulator Search module
* [OpenSimSearch](https://github.com/kcozens/OpenSimSearch)
* Events need to be fetched with a separate script, from an HYPEvents server
*
* @package magicoli/opensim-helpers
* @author Gudule Lapointe <gudule@speculoos.world>
* @link https://github.com/magicoli/opensim-helpers
* @license AGPLv3
*
* Includes portions of code from
* [OpenSimSearch](https://github.com/kcozens/OpenSimSearch)
*/
require_once 'includes/config.php';
require_once 'includes/search.php';
function ossearch_get_gatekeeperURL( $args = array() ) {
$gatekeeperURL = false;
if ( ! empty( $args['gatekeeper_url'] ) ) {
$gatekeeperURL = $args['gatekeeper_url'];
} elseif ( ! empty( $_REQUEST['gk'] ) ) {
$gatekeeperURL = $_REQUEST['gk'];
} else {
return false;
}
if ( ! empty( $gatekeeperURL ) ) {
$gatekeeperURL = preg_match( '#https?://#', $gatekeeperURL ) ? $gatekeeperURL : 'http://' . $gatekeeperURL;
}
return $gatekeeperURL;
}
//
// The XMLRPC server object
//
$xmlrpc_server = xmlrpc_server_create();
//
// Places Query
//
xmlrpc_server_register_method( $xmlrpc_server, 'dir_places_query', 'dir_places_query' );
function dir_places_query( $method_name, $params, $app_data ) {
global $SearchDB;
$req = $params[0];
$flags = $req['flags'];
$text = $req['text'];
$category = isset( $req['category'] ) ? $req['category'] : null;
$query_start = $req['query_start'];
if ( ! is_int( $query_start ) ) {
$query_start = 0;
}
$pieces = explode( ' ', $text );
array_filter( $pieces );
$text = join( '%', $pieces );
$text = "%$text%";
if ( empty( $text ) || $text == '%%%' ) {
osXmlDie( 'Invalid search terms' );
}
$terms = array();
$sqldata = array();
// order by traffic or by parcename
$order = ( $flags & 1024 ) ? 'dwell DESC, parcelname' : 'parcelname';
$terms[] = '(parcelname LIKE :text OR description LIKE :text)';
$type = ossearch_terms_build_rating( $flags );
if ( ! empty( $type ) ) {
$terms[] = "$type";
}
if ( $category > 0 ) {
$terms[] = 'searchcategory = :cat';
}
$values = array(
':text' => $text,
':order' => $order,
':cat' => $category,
);
$gatekeeperURL = ossearch_get_gatekeeperURL();
if ( $gatekeeperURL ) {
$terms[] = 'parcels.gatekeeperURL = :gatekeeperURL';
$values['gatekeeperURL'] = $gatekeeperURL;
}
$query = $SearchDB->prepareAndExecute(
'SELECT * FROM parcels
INNER JOIN ' . SEARCH_REGION_TABLE . ' AS r ON parcels.regionUUID = r.regionUUID
WHERE ' . join( ' AND ', $terms ) . " ORDER BY :order LIMIT $query_start,101",
$values
);
$data = array();
while ( $row = $query->fetch( PDO::FETCH_ASSOC ) ) {
$data[] = array_merge(
$row,
array(
'parcel_id' => $row['infouuid'],
'name' => $row['parcelname'],
'for_sale' => 'False',
'auction' => 'False',
'dwell' => $row['dwell'],
)
);
}
if ( empty( $data ) ) {
osXmlResponse( false, 'No results', $data );
// osXmlResponse( false );
} else {
osXmlResponse( true, '', $data );
}
die();
}
//
// Popular Places Query
//
xmlrpc_server_register_method( $xmlrpc_server, 'dir_popular_query', 'dir_popular_query' );
function dir_popular_query( $method_name, $params, $app_data ) {
global $SearchDB;
$req = $params[0];
$text = $req['text'];
$flags = $req['flags'];
$query_start = $req['query_start'];
$include_hypergrid = ( isset( $req['include_hypergrid'] ) && $req['include_hypergrid'] == 'false' ) ? false : true;
$include_landsales = ( isset( $req['include_landsales'] ) && $req['include_landsales'] == 'true' ) ? true : false;
$terms = array();
$sqldata = array();
if ( $flags & pow( 2, 12 ) ) {
$terms[] = 'has_picture = 1';
}
// if ($flags & pow(2,11)) $terms[] = "pop.mature = 0"; //PgSimsOnly (1 << 11)
$typeCondition = ossearch_terms_build_rating( $flags, 'pop' );
if ( ! empty( $typeCondition ) ) {
$terms[] = $typeCondition;
}
if ( $text != '' ) {
$terms[] = '(name LIKE :text)';
$sqldata['text'] = "%$text%";
}
$gatekeeperURL = ossearch_get_gatekeeperURL( $req );
if ( ! $include_hypergrid && ! empty( $gatekeeperURL ) ) {
$terms[] = 'pop.gatekeeperURL = :gatekeeperURL';
$sqldata['gatekeeperURL'] = $gatekeeperURL;
}
$left_join = null;
if ( ! $include_landsales ) {
$left_join = 'LEFT JOIN parcelsales AS sales ON sales.parcelUUID = pop.parcelUUID';
$terms[] = 'sales.regionUUID IS NULL';
}
if ( count( $terms ) > 0 ) {
$where = ' WHERE ' . join( ' AND ', $terms );
} else {
$where = '';
}
// Prevent SQL injection by checking that $query_start is a number
if ( ! is_int( $query_start ) ) {
$query_start = 0;
}
$sql = 'SELECT pop.infoUUID, pop.name, pop.dwell, pop.gatekeeperURL, r.regionname, r.regionUUID, par.landingpoint, par.imageUUID FROM popularplaces as pop
INNER JOIN parcels as par ON pop.parcelUUID = par.parcelUUID
INNER JOIN ' . SEARCH_REGION_TABLE . ' as r ON par.regionUUID = r.regionUUID
' . $left_join . '
' . $where . "
ORDER BY pop.dwell DESC, par.parcelname LIMIT $query_start,100";
$query = $SearchDB->prepare( $sql );
$result = $query->execute( $sqldata );
$data = array();
while ( $row = $query->fetch( PDO::FETCH_ASSOC ) ) {
$data[] = array(
'parcel_id' => $row['infoUUID'],
'name' => $row['name'],
'dwell' => $row['dwell'],
'gatekeeperURL' => $row['gatekeeperURL'],
'regionname' => $row['regionname'],
'regionUUID' => $row['regionUUID'],
'landingpoint' => $row['landingpoint'],
'imageUUID' => $row['imageUUID'],
);
}
osXmlResponse( true, '', $data );
}
//
// Land Query
//
xmlrpc_server_register_method( $xmlrpc_server, 'dir_land_query', 'dir_land_query' );
function dir_land_query( $method_name, $params, $app_data ) {
global $SearchDB;
$req = $params[0];
$flags = $req['flags'];
$type = $req['type'];
$price = $req['price'];
$area = $req['area'];
$query_start = $req['query_start'];
$terms = array();
$sqldata = array();
if ( $type != 4294967295 ) {
// Do this check first so we can bail out quickly on Auction search
if ( ( $type & 26 ) == 2 ) {
osXmlDie( 'No auctions listed' ); // Auction (from SearchTypeFlags enum)
}
if ( ( $type & 24 ) == 8 ) {
$terms[] = 'parentestate = 1'; // Mainland (24=0x18 [bits 3 & 4])
}
if ( ( $type & 24 ) == 16 ) {
$terms[] = 'parentestate <> 1'; // Estate (24=0x18 [bits 3 & 4])
}
}
$typeCondition = ossearch_terms_build_rating( $flags );
if ( ! empty( $typeCondition ) ) {
$terms[] = $typeCondition;
}
if ( $flags & pow( 2, 20 ) ) {
$terms[] = 'saleprice <= :price';
$sqldata['price'] = $price;
}
if ( $flags & pow( 2, 21 ) ) {
$terms[] = 'area >= :area';
$sqldata['area'] = $area;
}
$gatekeeperURL = ossearch_get_gatekeeperURL();
if ( $gatekeeperURL ) {
$terms[] = 'gatekeeperURL = :gatekeeperURL';
$sqldata['gatekeeperURL'] = $gatekeeperURL;
}
// The PerMeterSort flag is always passed from a map item query.
// It doesn't hurt to have this as the default search order.
$order = 'lsq'; // PerMeterSort (1 << 17)
if ( $flags & pow( 2, 19 ) ) {
$order = 'parcelname';
}
if ( $flags & pow( 2, 16 ) ) {
$order = 'saleprice';
}
if ( $flags & pow( 2, 18 ) ) {
$order = 'area';
}
if ( ! ( $flags & pow( 2, 15 ) ) ) {
$order .= ' DESC';
}
if ( count( $terms ) > 0 ) {
$where = ' WHERE ' . join( ' AND ', $terms );
} else {
$where = '';
}
// Prevent SQL injection by checking that $query_start is a number
if ( ! is_int( $query_start ) ) {
$query_start = 0;
}
$sql = "SELECT *,saleprice/area AS lsq FROM parcelsales $where ORDER BY " . $order . " LIMIT $query_start,101";
$query = $SearchDB->prepare( $sql );
$result = $query->execute( $sqldata );
$data = array();
while ( $row = $query->fetch( PDO::FETCH_ASSOC ) ) {
$data[] = array(
'parcel_id' => $row['infoUUID'],
'name' => $row['parcelname'],
'auction' => 'false',
'for_sale' => 'true',
'sale_price' => $row['saleprice'],
'landing_point' => $row['landingpoint'],
'region_UUID' => $row['regionUUID'],
'area' => $row['area'],
);
}
osXmlResponse( true, '', $data );
}
//
// Events Query
//
xmlrpc_server_register_method( $xmlrpc_server, 'dir_events_query', 'dir_events_query' );
function dir_events_query( $method_name, $params, $app_data ) {
global $SearchDB;
$req = $params[0];
$text = $req['text'];
$flags = $req['flags'];
$query_start = $req['query_start'];
if ( $text == '%%%' ) {
$response_xml = xmlrpc_encode(
array(
'success' => false,
'errorMessage' => 'Invalid search terms',
)
);
print $response_xml;
return;
}
$pieces = explode( '|', $text );
$day = $pieces[0];
$category = $pieces[1];
if ( count( $pieces ) < 3 ) {
$search_text = '';
} else {
$search_text = $pieces[2];
}
$terms = array();
$sqldata = array();
// Event times are in UTC so we need to get the current time in UTC.
$now = time();
if ( $day == 'u' ) {
// This condition will include upcoming and in-progress events
$terms[] = 'dateUTC+duration*60 >= ' . $now;
} else {
// For events in a given day we need to determine the days start time
$now -= idate( 'Z' ); // Adjust for timezone
$now -= ( $now % 86400 ); // Adjust to start of day
// Is $day a number of days before or after current date?
if ( $day != 0 ) {
$now += $day * 86400;
}
$then = $now + 86400; // Time for end of day
// This condition will include any in-progress events
$terms[] = "(dateUTC+duration*60 >= $now AND dateUTC < $then)";
}
if ( $category > 0 ) {
$terms[] = 'category = :category';
$sqldata['category'] = $category;
}
$type = array();
if ( $flags & pow( 2, 24 ) ) {
$type[] = 'eventflags = 0'; // IncludePG (1 << 24)
}
if ( $flags & pow( 2, 25 ) ) {
$type[] = 'eventflags = 1'; // IncludeMature (1 << 25)
}
if ( $flags & pow( 2, 26 ) ) {
$type[] = 'eventflags = 2'; // IncludeAdult (1 << 26)
}
if ( count( $type ) > 0 ) {
$terms[] = ossearch_terms_join( ' OR ', $type );
}
if ( $search_text != '' ) {
$terms[] = '(name LIKE :text1 OR ' .
'description LIKE :text2)';
$search_text = "%$search_text%";
$sqldata['text1'] = $search_text;
$sqldata['text2'] = $search_text;
}
if ( count( $terms ) > 0 ) {
$where = ' WHERE ' . join( ' AND ', $terms );
} else {
$where = '';
}
// Prevent SQL injection by checking that $query_start is a number
if ( ! is_int( $query_start ) ) {
$query_start = 0;
}
$sql = 'SELECT owneruuid,name,eventid,dateUTC,eventflags,globalPos' .
' FROM events' . $where . " LIMIT $query_start,101";
$query = $SearchDB->prepare( $sql );
$result = $query->execute( $sqldata );
$data = array();
while ( $row = $query->fetch( PDO::FETCH_ASSOC ) ) {
$date = new DateTime( '@' . $row['dateUTC'] );
$date->setTimezone( new DateTimeZone( 'America/Los_Angeles' ) );
$LSTDate = $date->format( 'm/d h:i A' );
// The landing point is only needed when this event query is
// called to allow placement of event markers on the world map.
$data[] = array(
'owner_id' => $row['owneruuid'],
'name' => $row['name'],
'event_id' => $row['eventid'],
'date' => $LSTDate,
'unix_time' => $row['dateUTC'],
'event_flags' => $row['eventflags'],
'landing_point' => $row['globalPos'],
);
}
osXmlResponse( true, '', $data );
}
//
// Classifieds Query
//
xmlrpc_server_register_method( $xmlrpc_server, 'dir_classified_query', 'dir_classified_query' );
function dir_classified_query( $method_name, $params, $app_data ) {
global $OpenSimDB;
if ( ! tableExists( $OpenSimDB, array( 'classifieds' ) ) ) {
osXmlResponse( false );
die();
}
$req = $params[0];
$text = $req['text'];
$flags = $req['flags'];
$category = $req['category'];
$query_start = $req['query_start'];
if ( $text == '%%%' ) {
osXmlResponse( false, 'Invalid search terms', array() );
return;
}
$terms = array();
$sqldata = array();
// Renew Weekly flag is bit 5 (32) in $flags.
$f = array();
if ( $flags & 4 ) {
$f[] = 'classifiedflags & 4'; // PG (1 << 2)
}
if ( $flags & 8 ) {
$f[] = 'classifiedflags & 8'; // Mature (1 << 3)
}
if ( $flags & 64 ) {
$f[] = 'classifiedflags & 64'; // Adult (1 << 6)
}
if ( count( $f ) > 0 ) {
$terms[] = ossearch_terms_join( ' OR ', $f );
}
// Only restrict results based on category if it is not 0 (Any Category)
if ( $category > 0 ) {
$terms[] = "category = $category";
}
if ( $text != '' ) {
$terms[] = '(name LIKE :text OR description LIKE :text)';
$sqldata['text'] = "%$text%";
}
// Was there at least condition for the search?
if ( count( $terms ) > 0 ) {
$where = ' WHERE ' . join( ' AND ', $terms );
} else {
$where = '';
}
// Prevent SQL injection by checking that $query_start is a number
if ( ! is_int( $query_start ) ) {
$query_start = 0;
}
$sql = "SELECT * FROM classifieds $where ORDER BY priceforlisting DESC LIMIT $query_start,101";
$query = $OpenSimDB->prepareAndExecute( $sql, $sqldata );
$data = array();
while ( $row = $query->fetch( PDO::FETCH_ASSOC ) ) {
$data[] = array(
'classifiedid' => $row['classifieduuid'],
'name' => $row['name'],
'classifiedflags' => $row['classifiedflags'],
'creation_date' => $row['creationdate'],
'expiration_date' => $row['expirationdate'],
'priceforlisting' => $row['priceforlisting'],
);
}
osXmlResponse( true, '', $data );
}
//
// Events Info Query
//
xmlrpc_server_register_method( $xmlrpc_server, 'event_info_query', 'event_info_query' );
function event_info_query( $method_name, $params, $app_data ) {
global $SearchDB;
$req = $params[0];
$eventID = $req['eventID'];
$query = $SearchDB->prepare( 'SELECT * FROM events WHERE eventID = ?' );
$result = $query->execute( array( $eventID ) );
$data = array();
while ( $row = $query->fetch( PDO::FETCH_ASSOC ) ) {
$date = strftime( '%G-%m-%d %H:%M:%S', $row['dateUTC'] );
$category = '*Unspecified*';
if ( $row['category'] == 18 ) {
$category = 'Discussion';
}
if ( $row['category'] == 19 ) {
$category = 'Sports';
}
if ( $row['category'] == 20 ) {
$category = 'Live Music';
}
if ( $row['category'] == 22 ) {
$category = 'Commercial';
}
if ( $row['category'] == 23 ) {
$category = 'Nightlife/Entertainment';
}
if ( $row['category'] == 24 ) {
$category = 'Games/Contests';
}
if ( $row['category'] == 25 ) {
$category = 'Pageants';
}
if ( $row['category'] == 26 ) {
$category = 'Education';
}
if ( $row['category'] == 27 ) {
$category = 'Arts and Culture';
}
if ( $row['category'] == 28 ) {
$category = 'Charity/Support Groups';
}
if ( $row['category'] == 29 ) {
$category = 'Miscellaneous';
}
// debug($row);
if ( isset( $_REQUEST['gk'] ) & ! empty( $_REQUEST['gk'] ) && $_REQUEST['gk'] == $row['gatekeeperURL'] ) {
$simname = preg_replace( '/^[^ ]*/', '', $row['simname'] );
} else {
$simname = $row['simname'];
}
$data[] = array(
'event_id' => $row['eventid'],
'creator' => $row['creatoruuid'],
'name' => $row['name'],
'category' => $category,
'description' => $row['description'],
'date' => $date,
'dateUTC' => $row['dateUTC'],
'duration' => $row['duration'],
'covercharge' => $row['covercharge'],
'coveramount' => $row['coveramount'],
'simname' => $simname,
'globalposition' => $row['globalPos'],
'eventflags' => $row['eventflags'],
);
}
osXmlResponse( true, '', $data );
}
//
// Classifieds Info Query
//
xmlrpc_server_register_method( $xmlrpc_server, 'classifieds_info_query', 'classifieds_info_query' );
function classifieds_info_query( $method_name, $params, $app_data ) {
global $OpenSimDB;
if ( ! tableExists( $OpenSimDB, array( 'classifieds' ) ) ) {
osXmlResponse( false );
die();
}
$req = $params[0];
$classifiedID = $req['classifiedID'];
$query = $OpenSimDB->prepare( 'SELECT * FROM classifieds WHERE classifieduuid = ?' );
$result = $query->execute( array( $classifiedID ) );
$data = array();
while ( $row = $query->fetch( PDO::FETCH_ASSOC ) ) {
$data[] = array(
'classifieduuid' => $row['classifieduuid'],
'creatoruuid' => $row['creatoruuid'],
'creationdate' => $row['creationdate'],
'expirationdate' => $row['expirationdate'],
'category' => $row['category'],
'name' => $row['name'],
'description' => $row['description'],
'parceluuid' => $row['parceluuid'],
'parentestate' => $row['parentestate'],
'snapshotuuid' => $row['snapshotuuid'],
'simname' => $row['simname'],
'posglobal' => $row['posglobal'],
'parcelname' => $row['parcelname'],
'classifiedflags' => $row['classifiedflags'],
'priceforlisting' => $row['priceforlisting'],
);
}
osXmlResponse( true, '', $data );
}
$tmp_dir = get_writable_tmp_dir();
//
// Process the request
//
$request_xml = file_get_contents( 'php://input' );
// error_log( __FILE__ . ':' . __LINE__ . ' request xml ' . $request_xml );
if( ! $request_xml ) {
// Wrong request, probably called directly instead of by sending xml request,
// or no data sent, issue proper http response and error message, then die
header( 'HTTP/1.1 400 Bad Request' );
header( 'Content-Type: text/plain' );
die( '400 Bad Request' . PHP_EOL );
}
$request_array = array(
$_SERVER['REMOTE_ADDR'],
$_SERVER['REQUEST_URI'],
$request_xml
);
$request_key = md5( serialize( $request_array ) );
// error_log( __FILE__ . ':' . __LINE__
// . PHP_EOL . 'request xml ' . $request_xml
// . PHP_EOL . 'client ip ' . $_SERVER['REMOTE_ADDR']
// . PHP_EOL . 'request uri ' . $_SERVER['REQUEST_URI'] );
xmlrpc_server_call_method( $xmlrpc_server, $request_xml, '' );
xmlrpc_server_destroy( $xmlrpc_server );
die();