Thursday 25 July 2013

Dynamics CRM 2011 - PHP and SOAP Calls

I put the first little section regarding CRM 2011, PHP, and SOAP here ->  http://crmtroubleshoot.blogspot.com.au/2013/07/dynamics-crm-2011-php-and-soap-using.html

I went into some basic information about authentication so in this second part I thought I would give people the SOAP calls for talking to CRM 2011.

NOTE: I am not suggesting the following are the only ways to connect using SOAP to CRM and I know its not a complete list (Delete for example) however I hope this assists you in your investigation it is not intended to do all the work for you! ;-)

SOAP CALLS


CREATE
Creates a single CRM record


    
      
  
      
      
      
      00000000-0000-0000-0000-000000000000
      
      
    



RETRIEVE
Returns a single CRM record based on the GUID.


  
  
  
 false
 
   
 
  



RETRIEVE ALL ENTITIES
This returns all all entities. I don't really recommend this because it is incredibly slow!


 
   
    
  EntityFilters
  Entity
    
    
  RetrieveAsIfPublished
  true
    
   
   
   RetrieveAllEntities
 



RETRIEVE MULTIPLE
Returns a collection of CRM Records based on the Criteria list passed


 
  
    false
    
   
    
  
  
  false
  
  
  
  
    
    
    
    false
  
 



UPDATE
Updates a single record based on the GUID


 
  
   
  
  
  
  
  
  
 



WHO AM I
Returns the User Id, Business Unit Id, and Organization Id for the logged in user


 
  
  
  WhoAmI
 



CHILD ITEMS


COLUMN
This should be repeated for each column required for the Retrieve Requests


 



ATTRIBUTES
This should be repeated for each attribute required for a Create or Update Request. There is various types for attributes.

EntityReference

 
 
  
  
  
  


OptionSetValue

 
 
  
 


Money

 
 
  
 


String

        
 


Boolean

        
 


Integer

        
 


Date Time

        
 




CRITERIA
Used to filter the query results


 
 
  
  
   
   
   
    
    
   
  
 



ORDER
Used to define the sort order of the result set


 
 
  
  
 



Tuesday 16 July 2013

Dynamics CRM 2011 - PHP and SOAP using Office 365

NOTE: If you are using this code I found a small issue!!
Microsoft appears to have changed the SSL version they are using. Due to this the following function needs to be modified!
 public static function GetSOAPResponse($url, $request) {
    // Set up headers.
    $headers = array(
      "POST " . "/Organization.svc" . " HTTP/1.1",
      "Host: yourorganisation.api.crm5.dynamics.com",
      'Connection: Keep-Alive',
      "Content-type: application/soap+xml; charset=UTF-8",
      "Content-length: " . strlen($request),
    );

    $cURLHandle = curl_init();
    curl_setopt($cURLHandle, CURLOPT_URL, $url);
    curl_setopt($cURLHandle, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($cURLHandle, CURLOPT_TIMEOUT, 60);
    curl_setopt($cURLHandle, CURLOPT_SSL_VERIFYPEER, FALSE);
    curl_setopt($cURLHandle, CURLOPT_FOLLOWLOCATION, TRUE);
    //COMMENT OUT THIS LINE!!curl_setopt($cURLHandle, CURLOPT_SSLVERSION, 3);
    curl_setopt($cURLHandle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
    curl_setopt($cURLHandle, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($cURLHandle, CURLOPT_POST, 1);
    curl_setopt($cURLHandle, CURLOPT_POSTFIELDS, $request);
    //COMMENT OUT THIS LINE!!$response = curl_exec($cURLHandle);
    // ADD THE FOLLOWING FOUR LINES INSTEAD (and a try/catch wouldn't hurt!)
    if( ! $response = curl_exec($cURLHandle)) 
    { 
        trigger_error(curl_error($cURLHandle)); 
    }
    curl_close($cURLHandle);

    return $response;
  }


Now onto the post!!
I recently found myself in a position where I needed to connect to CRM Online from PHP.  As I didn't have anywhere to put my code except for the client side creating my own wrapper for the CRM Web services didn't seem like a great option.

I found a few examples of CRM, PHP, and SOAP however many seemed to have some issue along the way (In particular around Office 365 authentication).

So firstly we will look at Office 365 authentication. To connect you need to request so tokens from the CRM Web Service as follows:

class Authentication
{
 public $username;
 public $password;
 public $keyIdentifier;
    public $securityToken0;
    public $securityToken1;

 public function Authentication($_username, $_password)
 {
  $this->username = $_username;
  $this->password = $_password;
  $this->AuthenticateUser();
 }
 
 function BuildOCPSoap()
 {
  /*
  Select the right region for your CRM
  crmna:dynamics.com - North America
  crmemea:dynamics.com - Europe, the Middle East and Africa
  crmapac:dynamics.com - Asia Pacific
  */
  $region = 'crmapac:dynamics.com';

  $OCPRequest = '<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
       <s:Header>
      <a:Action s:mustUnderstand="1">http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue</a:Action>
      <a:MessageID>urn:uuid:%s</a:MessageID>
      <a:ReplyTo>
        <a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
      </a:ReplyTo>
      <a:To s:mustUnderstand="1">%s</a:To>
      <o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
        <u:Timestamp u:Id="_0">
       <u:Created>%sZ</u:Created>
       <u:Expires>%sZ</u:Expires>
        </u:Timestamp>
        <o:UsernameToken u:Id="uuid-cdb639e6-f9b0-4c01-b454-0fe244de73af-1">
       <o:Username>%s</o:Username>
       <o:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">%s</o:Password>
        </o:UsernameToken>
      </o:Security>
       </s:Header>
       <s:Body>
      <t:RequestSecurityToken xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust">
        <wsp:AppliesTo xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
       <a:EndpointReference>
         <a:Address>'. $region .'</a:Address>
       </a:EndpointReference>
        </wsp:AppliesTo>
        <t:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</t:RequestType>
      </t:RequestSecurityToken>
       </s:Body>
     </s:Envelope>';
  
  $OCPRequest = sprintf($OCPRequest, gen_uuid(), 'https://login.microsoftonline.com/RST2.srf',  getCurrentTime(), getNextDayTime(), $this->username, $this->password);
  return $OCPRequest;
 }
 
 public static function GetSOAPResponse($url, $request) {
    // Set up headers.
    $headers = array(
      "POST " . "/Organization.svc" . " HTTP/1.1",
      "Host: yourorganisation.api.crm5.dynamics.com",
      'Connection: Keep-Alive',
      "Content-type: application/soap+xml; charset=UTF-8",
      "Content-length: " . strlen($request),
    );

    $cURLHandle = curl_init();
    curl_setopt($cURLHandle, CURLOPT_URL, $url);
    curl_setopt($cURLHandle, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($cURLHandle, CURLOPT_TIMEOUT, 60);
    curl_setopt($cURLHandle, CURLOPT_SSL_VERIFYPEER, FALSE);
    curl_setopt($cURLHandle, CURLOPT_FOLLOWLOCATION, TRUE);
    curl_setopt($cURLHandle, CURLOPT_SSLVERSION, 3);
    curl_setopt($cURLHandle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
    curl_setopt($cURLHandle, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($cURLHandle, CURLOPT_POST, 1);
    curl_setopt($cURLHandle, CURLOPT_POSTFIELDS, $request);
    $response = curl_exec($cURLHandle);
    curl_close($cURLHandle);

    return $response;
  }
  
  public function AuthenticateUser()
 {
  $SOAPresult = GetSOAPResponse('https://login.microsoftonline.com/RST2.srf', $this->BuildOCPSoap());
  $responsedom = new DomDocument();
        $responsedom->loadXML($SOAPresult);
  
        $cipherValues = $responsedom->getElementsbyTagName("CipherValue");
       
        if( isset ($cipherValues) && $cipherValues->length>0){
            $this->securityToken0 =  $cipherValues->item(0)->textContent;
   $this->securityToken1 =  $cipherValues->item(1)->textContent;
   $this->keyIdentifier = $responsedom->getElementsbyTagName("KeyIdentifier")->item(0)->textContent;
        }else{
            return null;
        }
 }
      


gen_uuid returns a random guid, getCurrentTime gets the current date/time, getNextDayTime gets tomorrow (or any expiry date/time you wish)

We then send this to the authentication srf to get the required tokens

You should now have  the required tokens which you then place in your header for any requests as shown below:


public static function getHeader($_action, $_authentication) {

  $header = '
  <s:Header>
   <a:Action s:mustUnderstand="1">http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/'.$_action .'</a:Action>
   <a:MessageID>
    urn:uuid:'.self::gen_uuid().'
   </a:MessageID>
   <a:ReplyTo><a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address></a:ReplyTo>
   <a:To s:mustUnderstand="1">
    https://yourorganisation.api.crm5.dynamics.com/XRMServices/2011/Organization.svc
   </a:To>
   <o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
   <u:Timestamp u:Id="_0">
    <u:Created>'.self::getCurrentTime().'Z</u:Created>
    <u:Expires>'.self::getNextDayTime().'Z</u:Expires>
   </u:Timestamp>
   <EncryptedData Id="Assertion0" Type="http://www.w3.org/2001/04/xmlenc#Element" xmlns="http://www.w3.org/2001/04/xmlenc#">
    <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#tripledes-cbc"></EncryptionMethod>
    <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
     <EncryptedKey>
      <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"></EncryptionMethod>
      <ds:KeyInfo Id="keyinfo">
       <wsse:SecurityTokenReference xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
        <wsse:KeyIdentifier EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509SubjectKeyIdentifier">
         '.$_authentication->keyIdentifier.'
        </wsse:KeyIdentifier>
       </wsse:SecurityTokenReference>
      </ds:KeyInfo>
      <CipherData>
       <CipherValue>
        '.$_authentication->securityToken0.'
       </CipherValue>
      </CipherData>
     </EncryptedKey>
    </ds:KeyInfo>
    <CipherData>
     <CipherValue>
      '.$_authentication->securityToken1.'
     </CipherValue>
    </CipherData>
   </EncryptedData>
   </o:Security>
  </s:Header>';

  return $header;

 }

public static function WhoAmIRequest($request){

 $authentication = new Authentication('Username', 'Password');
 $xml = '
 <s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
  'getHeader('Execute', $authentication).'
  <s:Body>
   <Execute xmlns="http://schemas.microsoft.com/xrm/2011/Contracts/Services" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
    <request i:type="b:WhoAmIRequest" xmlns:a="http://schemas.microsoft.com/xrm/2011/Contracts" xmlns:b="http://schemas.microsoft.com/crm/2011/Contracts">
     <a:Parameters xmlns:c="http://schemas.datacontract.org/2004/07/System.Collections.Generic" />
     <a:RequestId i:nil="true" />
     <a:RequestName>WhoAmI</a:RequestName>
    </request>
   </Execute>
  </s:Body>
 </s:Envelope>'; 

 return self::GetSOAPResponse('https://yourorganisation.api.crm5.dynamics.com/XRMServices/2011/Organization.svc', $xml);
}

This code is slightly modified obviously from what I am using however I have the above working. The above is not intended to be the entire solution it is just expected that it might assist you in your own work.

Thursday 11 July 2013

CRM2011 - "A currency is required if a value exists in a money field. Select a currency and try again."

I was writing a javascript function and during testing kept getting "A currency is required if a value exists in a money field. Select a currency and try again." while attempting to enter data into the field.

Basically what had happened was a currency field was added to an existing CRM entity.  CRM doesn't like this for some weird reason.

After googling alot of people suggested that you write javascript to set the currency onload.  This to me seemed like a bit of a hack which i'd prefer to avoid.  Instead do the following:

Step 1 - Go to File -> Options
Then in the first tab General go down to "Select a default currency". Set it to the appropriate value.
NOTE: This is a user setting so this needs to be repeated for all users however HOPEFULLY this is already set.

Step 2 - Add the "Currency" field to the form for the entity you are having issues with. Save and Publish

Step 3 - Select all existing records for the entity and "Edit"

Step 4 - Click on the currency lookup and select the required currency. Save the changes

You will now find the required currency symbol appears in the field and you can happily enter data into the field!