* @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();