Skip to content

Instantly share code, notes, and snippets.

@misterhon
Last active November 25, 2020 03:38
Show Gist options
  • Save misterhon/ff06b4ffff0c7815bb86 to your computer and use it in GitHub Desktop.
Save misterhon/ff06b4ffff0c7815bb86 to your computer and use it in GitHub Desktop.
YourMembership API Requests in PHP
<?php
/**
* Authenticate user via the API
*
* Documentation:
* http://www.yourmembership.com/company/api-reference.aspx
*/
define( 'API_ENDPOINT', 'https://api.yourmembership.com' );
define( 'API_VERSION', '2.02' );
define( 'API_KEY_PUBLIC', '...' );
define( 'API_KEY_PRIVATE', '...' );
define( 'API_PASSCODE', '...' );
/**
* Pre-define an error code that we know cannot come from the API:
* https://api.yourmembership.com/reference/2_02/Error_Codes.htm
*/
$api_response = new stdClass();
$api_response->ErrCode = 9001;
/**
* Helper method - Sanitize User Input
*
* Wrapper for htmlspecialchars function: http://php.net/htmlspecialchars
*
* @param string $in User-submitted form input
*
* @return string Sanitized form input
*/
function sanitize_form_field( $in = '' ) {
return htmlspecialchars( $in, ENT_QUOTES | ENT_SUBSTITUTE | ENT_DISALLOWED | ENT_XML1, 'UTF-8' );
}
/**
* Helper method - Create XML
*
* Creates the YourMembership XML request object.
* Utilizes DOMDocument: http://php.net/manual/en/class.domdocument.php
*
* @param string $ym_call API call.
* @param string $ym_callID Value for the CallID element.
* @param array $ym_callParams Parameters to be included in the API call.
*
* @return string String representation of the YourMembership XML request object
*/
function create_xml( $ym_call = '', $ym_callID = '000', $ym_callArgs = array() ) {
// If user didn't make a call, no need to create an XML object
if ( empty( $ym_call ) ) {
return '';
}
$doc = new DOMDocument( '1.0', 'UTF-8' );
$doc->formatOutput = true; // Human-readable XML. Good for debugging.
$xml = $doc->createElement('YourMembership');
$ver = $doc->createElement( 'Version', API_VERSION );
$apiKey = $doc->createElement( 'ApiKey', API_KEY_PUBLIC );
$callID = $doc->createElement( 'CallID', $ym_callID );
$call = $doc->createElement('Call');
$callAttr = $doc->createAttribute('Method');
$callAttr->value = $ym_call;
$call->appendChild( $callAttr );
if ( count( $ym_callArgs ) ) {
foreach ( $ym_callArgs as $key => $val ) {
// Make sure all call argument data are sanitized
$el = $doc->createElement( $key, sanitize_form_field( $val ) );
$call->appendChild( $el );
}
}
$xml->appendChild( $ver );
$xml->appendChild( $apiKey );
$xml->appendChild( $callID );
// If we aren't creating the session in this request, include the SessionID
if ( 'Session.Create' !== $ym_call ) {
$sessionID = $doc->createElement( 'SessionID', $_SESSION['ID'] );
$xml->appendChild( $sessionID );
}
$xml->appendChild( $call );
$doc->appendChild( $xml );
return $doc->saveXML();
}
/**
* Helper method - Get API Response
*
* Makes a HTTP POST request to the API endpoint. Refer to PHP documentation:
* http://php.net/manual/en/function.simplexml-load-string.php
*
* @param string $xml String representation of the XML request
*
* @return object Type SimpleXMLElement
*/
function get_api_response( $xml = '' ) {
$options = array(
'http' => array(
'header' => "Content-type: application/x-www-form-urlencoded; charset=utf-8",
'method' => 'POST',
'content' => $xml
));
return simplexml_load_string(file_get_contents(
API_ENDPOINT,
false,
stream_context_create( $options )
));
}
/**
* Get SessionID
*
* Calls Session.Create: https://api.yourmembership.com/reference/2_02/Session_Create.htm
*
* @return string SessionID if API returns no error; otherwise empty string.
*/
function get_sessionID() {
// Create session if no previous session exists.
if ( 9001 === $api_response->ErrCode ) {
$api_response = get_api_response( create_xml( 'Session.Create', '001' ) );
}
// Return sessionID if API call did not return error code; else return empty string.
return '' . ( intval( $api_response->ErrCode ) ) ? '' : $api_response->{"Session.Create"}->SessionID;
}
/**
* Is User Logged In?
*
* Checks whether a user is logged in. Calls Auth.Authenticate:
* https://api.yourmembership.com/reference/2_02/Auth_Authenticate.htm
*
* @param string $usr Username
* @param string $pwd Password
* @param string $sessionID SessionID
*
* @return boolean True if API authenticates the user credentials
*/
function is_user_logged_in( $usr = '', $pwd = '' ) {
// Skip the API call if the user was already authenticated
if ( strlen( $_SESSION['userID'] ) ) {
return true;
}
$_SESSION['ID'] = get_sessionID();
// Authenticate submitted user credentials if the API created a session without error.
if ( ! intval( $api_response->ErrCode ) ) {
$api_response = get_api_response( create_xml(
'Auth.Authenticate',
'002',
array(
'Username' => $usr,
'Password' => $pwd
)) );
// Assign user ID returned from the API to the session variable.
$_SESSION['userID'] = '' . $api_response->{"Auth.Authenticate"}->ID;
// Return true if the API response came with a user ID.
return (bool) strlen( $_SESSION['userID'] );
}
// The API returned errors. The user couldn't be logged in.
return false;
}
$usr = '';
$pwd = '';
if ( !empty( $_POST['user'] ) ) {
// User input are always assumed unsafe. Sanitize at the lastest possible.
$usr = $_POST['user'];
$pwd = $_POST['pass'];
}
session_start();
$isUserLoggedIn = is_user_logged_in( $usr, $pwd );
?>
@ericandrewlewis
Copy link

I'll disclaim all my comments with the note that you should experiment and end up doing what works for you.

I tend to keep in alignment with the PHPDoc standard. The WordPress project has a succinct intro for all the ways PHPDoc should be expressed.

With that in mind, I would take out all unnecessary presentational stuff (e.g. lines of hyphens). The Table of Contents / sections may not be needed. Document what constants are for with a multi-line block for each.

No extra line break between multi-line comment blocks and the control structure being described (e.g. functions).

In general your inline comments seem great and you're already well on the way to providing context for other developers on your stack 😄

Unrelated: don't pass raw POST content into business logic without sanitizing data 😉

@misterhon
Copy link
Author

Thank you so much for the feedback. I will follow the PHPDoc standard more closely.

Regarding data sanitization, luckily, this code is still being tested in a local dev environment. Will definitely sanitize POST content before this is deployed live!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment