RegAPI for PHP5

From Second Life Wiki
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

Hey y'all. I rewrote the RegAPI libraries to work with PHP5 without the compatibility library. It's not a drop-in replacement, but should be pretty easy to use. The documentation is in the code, and see the index.php example for some usage. I tried to make it fairly robust, but I pretty much suck at PHP, so please lemme know if y'all find anything untoward. >_>

It consists of two files...

A configuration file, config.php:

<?php

// Get your CAPS from https://secure-web2.secondlife.com/developers/third_party_reg/#what_are_cap
//	and fill them in here.

define('URI_CREATE_USER', 'FILL_ME_IN_WITH_YOUR_CAPS_URL');
define('URI_GET_LAST_NAMES', 'FILL_ME_IN_WITH_YOUR_CAPS_URL');
define('URI_CHECK_NAME', 'FILL_ME_IN_WITH_YOUR_CAPS_URL');
define('URI_GET_ERROR_CODES', 'FILL_ME_IN_WITH_YOUR_CAPS_URL');

?>

And the library, RegAPI.php:

<?php

////////////////////////////////////////////////////////////////////////////////
//
// Hacked up by Liandra Ceawlin, Version 1, Tue Jul 21 11:53:34 EDT 2009
//
//	I'm not really much of a PHP coder, so if you find any bugs or see things
// in this code that are utterly atrocious, please IM me in-world or email
// support@ceawlin.com.  Good luck with it!
//
////////////////////////////////////////////////////////////////////////////////
//
// Yadda yadda so I don't get sued when it breaks.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
////////////////////////////////////////////////////////////////////////////////



require( 'config.php' );


// Check to see if <username> (first name) and <last_name_id> (an integer code
//	as returned from regapiGetLastNames below) are a valid pair to create a
//	new account with.  If they are, the function returns true.  If not, it
//	returns false.  It also returns false if there was a query error, but you
//	can check to see if regapiGetCurrentErrors() returns anything to see if
//	there was an error or not.
function	regapiCheckName( $username, $last_name_id )
{
	// Yea, maybe this is kinda lame, but seriously easier than constructing
	//	a DOM object and ->saveXML()ing. >_>
	
	// Build the XML.
	$str = "<llsd><map><key>username</key><string>".$username.
			"</string><key>last_name_id</key><integer>".$last_name_id.
			"</integer></map></llsd>";
	
	// POST it and get a DOM object back.
	$dom = regapiHttpPostDOM( URI_CHECK_NAME, $str );
	if( regapiProcessDOMForErrors($dom) )
		return false;
	
	// Parse out the return value and return boolean.
	$nodelist = $dom->getElementsByTagName( "boolean" );
	if( $nodelist != NULL && $nodelist->length )
		return $nodelist->item(0)->textContent == 'true';
	regapiSetUnknownError();
	return false;
}

// Create a new user, using the required fields <username> (first name),
//	<last_name_id> (an integer code as returned from regapiGetLastNames below),
//	<email>, and <password>.  <options> is an associative array of optional
//	parameters (see https://wiki.secondlife.com/wiki/Registration_API_Reference#Parameters_2),
//	where the key is the option name and the value is the, uh, value of that
//	option.  The function returns the agent's UUID.
// Returns NULL on error, then you can access the error codes with
//	regapiGetCurrentErrors().
function	regapiCreateUser( $username, $last_name_id, $email, $password, $dob, $options )
{
	// A mapping of options and their types, used in verification and XML
	//	creation.
	$valid_options = array(
			"username"			=> "string",
			"last_name_id"		=> "integer",
			"email"				=> "string",
			"password"			=> "string",
			"dob"				=> "string",
			"limited_to_estate"	=> "integer",
			"start_region_name"	=> "string",
			"start_local_x"		=> "float",
			"start_local_y"		=> "float",
			"start_local_z"		=> "float",
			"start_look_at_x"	=> "float",
			"start_look_at_y"	=> "float"
		);
	
	// Remove any invalid optional, uh, options.
	if( !is_array($options) )
		$options = array();
	foreach( $options as $opt => $val )
	{
		if( !array_key_exists($opt,$valid_options) )
		{
			?><b>Warning</b>:  Unrecognized option <b><?php echo $opt; ?></b> removed from regapiCreateUser list.<br /><?php
			unset( $options[$opt] );
		}
	}
	
	// Add the required options to the option list.
	$options["username"]		= $username;
	$options["last_name_id"]	= $last_name_id;
	$options["email"]			= $email;
	$options["password"]		= $password;
	$options["dob"]				= $dob;
	
	// Build the XML.
	// Yea, maybe this is kinda lame, but seriously easier than constructing
	//	a DOM object and ->saveXML()ing. >_>
	$str = "<llsd><map>";
	foreach( $options as $opt => $val )
	{
		$type = $valid_options[$opt];
		$str = $str."<key>".$opt."</key><".$type.">".$val."</".$type.">";
	}
	$str = $str."</map></llsd>";
	
	// POST it and get a DOM object back.
	$dom = regapiHttpPostDOM( URI_CREATE_USER, $str );
	if( regapiProcessDOMForErrors($dom) )
		return NULL;
		
	// Parse out the return value and return agent uuid string.
	$nodelist = $dom->getElementsByTagName( "string" );
	if( $nodelist != NULL && $nodelist->length )
		return $nodelist->item(0)->textContent;
	regapiSetUnknownError();
	return NULL;
}

// This function returns an associative 2d array of error codes, with the first
//	key being the integer code, and the second key being either "desc" for the
//	short error description, or "message" for the verbose error message. See
//	the example index.php file for example of usage.
// Returns NULL on error, then you can access the error codes with
//	regapiGetCurrentErrors(), although you won't be able to look up the error
//	code text, lol.
function regapiGetErrorCodes()
{
	global $regAPI_errorcodecache;
	
	// We cache the first query for error codes so that subsequent calls to not
	//	cause more queries.
	if( $regAPI_errorcodecache != NULL )
		return $regAPI_errorcodecache;
	
	$errors = array();
	
	$dom = regapiHttpGetDOM( URI_GET_ERROR_CODES );
	if( regapiProcessDOMForErrors($dom) )
		return NULL;
	
	// Get <llsd>.
	$node = $dom->documentElement;
	// Move to enclosing <array>.
	if( $node != NULL )
		$node = $node->firstChild;
	// move to first sub-<array>.
	if( $node != NULL && $node->nodeName == "array" )
		$node = $node->firstChild;
	else
	{
		regapiSetUnknownError();
		return NULL;
	}
	// Move through sub-<array>s and build the error list.
	while( $node != NULL && $node->nodeName == "array" )
	{
		// Build the array from the sub-<array> children.
		$child = $node->firstChild;
		// Get code.
		if( $child != NULL )
		{
			$code = $child->textContent;
			$child = $child->nextSibling;
		}
		else
		{
			regapiSetUnknownError();
			return NULL;
		}
		// Get short text.
		if( $child != NULL )
		{
			$desc = $child->textContent;
			$child = $child->nextSibling;
		}
		else
		{
			regapiSetUnknownError();
			return NULL;
		}
		// Get verbose error message.
		if( $child != NULL )
			$message = $child->textContent;
		else
		{
			regapiSetUnknownError();
			return NULL;
		}
		$errors[$code]["desc"] = $desc;
		$errors[$code]["message"] = $message;
		$node = $node->nextSibling;
	}
	
	$regAPI_errorcodecache = $errors;
	return $errors;
}

// Returns an associative array of available last names, in alphabetical
//	order, where key is the name string and value is the numeric id
//	corresponding to that name.  If you pass <num> of 1 or more, it will
//	randomly choose that many names from the list of available names and return
//	them.  If <num> is negative, it will return all available names.  If you
//	pass something into the <username> parameter, only last names that are
//	available with the specified username (first name) will be returned.
//	WARNING: Passing something into <username> is currently very slow, and gets
//	even slower the larger <num> is...
// Returns NULL on error, then you can access the error codes with
//	regapiGetCurrentErrors().
function	regapiGetLastNames( $username, $num )
{
	$dom = regapiHttpGetDOM( URI_GET_LAST_NAMES );
	if( regapiProcessDOMForErrors($dom) )
		return NULL;

	$names = array();
		
	$nodelist = $dom->getElementsByTagName( "map" );
	if( $nodelist != NULL && $nodelist->length )
	{
		// This will only return the names in the first map, but there is
		//	only one map anyway.
		$node = $nodelist->item(0)->firstChild;
		while( $node != NULL )
		{
			$k = $node->textContent;
			$node = $node->nextSibling;
			if( $node == NULL_KEY )
			{
				// This should never happen.
				return rval;
			}
			$s = $node->textContent;
			$node = $node->nextSibling;
			$names[$s] = $k;
		}
	}
	$rval = array();
	while( ($num>=0&&count($rval)<$num) || ($num<0&&count($names)) )
	{
		if( $num < 0 )
		{
			$rval = $names;
			$names = array();
		}
		else
		{
			$entries = array_rand( $names, $num-count($rval) );
			foreach( $entries as $k )
			{
				if( $username=="" || $username==NULL || regapiCheckName($username,$names[$k]) )
					$rval[$k] = $names[$k];
				unset( $names[$k] );
			}
		}
		if( !count($names) )
			break;
	}
	ksort( $rval );
	return $rval;
}

function	regapiGetCurrentErrors()
{
	global $regAPI_lasterrors;
	if( !count($regAPI_lasterrors) )
		return NULL;
	return $regAPI_lasterrors;
}

////////////////////////////////////////////////////////////////////////////////
// Internal things below here. You probably don't need to worry about them.
////////////////////////////////////////////////////////////////////////////////

$regAPI_lasterrors		= array();
$regAPI_errorcodecache	= NULL;

// Utility function for random errors not reported via XML returns from queries.
//	We just set a 0 (undefined) error.
function	regapiSetUnknownError()
{
	global $regAPI_lasterrors;
	$regAPI_lasterrors = array();
	$regAPI_lasterrors[] = 0;
}

// This function re-sets the lasterrors array, and returns true if the XML
//	looks like an error message, false otherwise.
function	regapiProcessDOMForErrors( $dom )
{
	global $regAPI_lasterrors;
	
	$regAPI_lasterrors = array();
	
	if( $dom == NULL )
		return true;
	// Get <llsd>.
	$node = $dom->documentElement;
	// Move to enclosing <array>.
	if( $node != NULL )
		$node = $node->firstChild;
	// Move to first <integer> node, if we're really in an <array>.
	if( $node != NULL && $node->nodeName == "array" )
		$node = $node->firstChild;
	else
		return false;
	$startnode = $node;
	// Scan over child nodes to see if they are all integers.
	while( $node != NULL )
	{
		if( $node->nodeName != "integer" )
			return false;
		$node = $node->nextSibling;
	}
	// If we get here, then the array is all integers, and it looks like an
	//	error message. So we add the error codes to the array.
	$node = $startnode;
	while( $node != NULL )
	{
		$regAPI_lasterrors[] = $node->textContent;
		$node = $node->nextSibling;
	}
	return true;
}

// Send a GET request to <url> and return a DOM object, or NULL on error.
function	regapiHttpGetDOM( $url )
{
	$ch = curl_init( $url );
	curl_setopt( $ch, CURLOPT_RETURNTRANSFER, TRUE );
    curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, FALSE );
	$doc = curl_exec( $ch );
	$status = curl_getinfo( $ch, CURLINFO_HTTP_CODE );
	curl_close( $ch );
	if( $status >= 400 )
		return NULL;
    
	$dom = new DOMDocument();
	if( !(@$dom->loadXML($doc)) )
		return NULL;
	return $dom;
}

// Send a POST request to <url> and return a DOM object, or NULL on error.
function	regapiHttpPostDOM( $url, $str )
{
	$ch = curl_init( $url );
    curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, FALSE );
	curl_setopt( $ch, CURLOPT_RETURNTRANSFER, TRUE );
	curl_setopt( $ch, CURLOPT_POST, TRUE );
	curl_setopt( $ch, CURLOPT_FAILONERROR, 1 );
	curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, 1 );
	curl_setopt( $ch, CURLOPT_HTTPHEADER, Array("Content-Type: application/xml") );
	curl_setopt( $ch, CURLOPT_POSTFIELDS, $str );
	$doc = curl_exec( $ch );
	$status = curl_getinfo( $ch, CURLINFO_HTTP_CODE );
	curl_close( $ch );
	if( $status >= 400 )
		return NULL;
	
	$dom = new DOMDocument();
	if( !(@$dom->loadXML($doc)) )
		return NULL;
	return $dom;
}

?>

And here's a modified version of the example index.php that uses the new library:

<?php

require( 'RegAPI.php' );

function get_months()
{
    $months = array();
    for( $i=1; $i<=12; $i++ )
    {
        $key = date( 'n', mktime(0, 0, 0, $i, 1, 2000) );
        $value = date( 'M.', mktime(0, 0, 0, $i, 1, 2000) );
        $months[sprintf("%02d", $key)] = $value;
    }
    return $months;
}
 
function get_years()
{
    $today = getdate();
    $max_year = $today['year'] - 90;
    $min_year = $today['year'] - 13;
 
    $years = array();
    for( $i=$min_year; $i>=$max_year; $i-- )
        $years[$i] = $i;
    return $years;
}
 
function get_days()
{
    $days = array();
    for( $i=1; $i<=31; $i++ )
        $days[sprintf("%02d",$i)] = sprintf("%02d",$i);
    return $days;
}



if ($_SERVER['REQUEST_METHOD'] == 'POST')
{
	$username = $_POST['username'];
	$last_name_id = (int)$_POST['last_name_id'];
	$email = $_POST['email'];
	$password = $_POST['password'];
	$dob = $_POST['dob_year'].'-'.$_POST['dob_month'].'-'.$_POST['dob_day'];
		
	if( regapiCheckName($username,$last_name_id) )
	{
		$uuid = regapiCreateUser( $username, $last_name_id, $email, $password, $dob, NULL );
		if( $uuid )
		{
			echo "Created ".$uuid."!<br/>";
		}
		else
		{
			if( $errors=regapiGetCurrentErrors() )
			{
				$errorcodes = regapiGetErrorCodes();
				foreach( $errors as $e )
				{
					if( array_key_exists($e,$errorcodes) )
						echo $errorcodes[$e]["message"]."<br/>";
					else
						echo "Unknown error.<br/>";
				}
			}
		}
	}
	else
	{
		echo "That name is not available.<br/>";
	}
}
?>


<h3>Create Second Life Account</h3>
 
<form action="<?php print $_SERVER['PHP_SELF']; ?>" method="post">
 
<table border="0" cellpadding="3" cellspacing="0">
<tr>
  <td>First name:</td>
  <td><input type="text" name="username" size="25" maxlength="31" value="" /></td>
</tr>
<tr>
  <td>Last name:</td>
  <td>
  <select name="last_name_id">
  <?php
	  $last_names = regapiGetLastNames( NULL, -1 );
	  foreach( $last_names as $name => $last_name_id )
	      print '<option value="'.$last_name_id.'">'.$name.'</option>';
  ?>
  </select>
  </td>
</tr>
<tr>
    <td>Password:</td>
    <td><input type="password" name="password" size="20" value="" /></td>
</tr>
<tr>
    <td>Email:</td>
    <td><input type="text" name="email" size="35" value="" /></td>
</tr>
<tr>
    <td>Date of brith:</td>
    <td>
    <select name="dob_day">
    <?php
    $days = get_days();
    foreach ($days as $key => $value) { print '<option value="'.$key.'" '.$selected.'>'.$value.'</option>'; }
    ?>
    </select>
 
    <select name="dob_month">
    <?php
    $months = get_months();
    foreach ($months as $key => $value) { print '<option value="'.$key.'" '.$selected.'>'.$value.'</option>'; }
    ?>
    </select>
 
    <select name="dob_year">
    <?php
    $years = get_years();
    foreach ($years as $key => $value) { print '<option value="'.$key.'" '.$selected.'>'.$value.'</option>'; } 
    ?>
    </select>
    </td>
</tr>
<tr>
    <td></td>
    <td><input type="submit" value="Create SL Account" /></td>
</table>
 
</form>