Author: Troy Wolf (troy@troywolf.com)
Modified Date: 2006-04-07
Does your organization use Microsoft Exchange?
Do you have a need to incorporate your Exchange data with your PHP web applications?
Do you NOT want to struggle for 2 days figuring this stuff out like I did?!
.....then these examples are for you. Learn from my pain, grasshopper.
If you are like me, you start this journey with a Google search for something like '"Exchange Server" PHP' or 'PHP "Microsoft Exchange" iterate contacts'. You quickly learn there are several different technologies--all rather cryptic-- to programmatically access your Exchange data. Let me shorten your reading time--WebDAV is the technology you want to use to allow your PHP applications to query Exchange.
Once I learned this technique, I made small tweaks to both my http and xml classes to support WebDAV queries. That's right, these examples incorporate two of my most popular classes. You can learn more about these PHP classes, but you don't need to in order to use these examples.
By far, the most difficult part of querying your Exchange Server will be how exactly to construct the WebDAV request to get the information you want. You'll no doubt end up spending some time looking at the documentation and examples at http://msdn.microsoft.com/library/default.asp?url=/library/en-us/e2k3/e2k3/wss_references_webdav.asp.
You'll also need the reference material that documents all the Exchange object properties and what each one's urn is. http://msdn.microsoft.com/library/default.asp?url=/library/en-us/e2k3/e2k3/_exch2k_urn_content-classes_person.asp
The code examples below will help you see how these are used.
Whenever I personally use this class to accomplish a unique task, I update this page with a generic example of the technique. Currently, these examples are available:
- Iterate the folders in a user's inbox
- Iterate the email items in a user's inbox
- Search for contacts that match a certain criteria
- Grab all properties for an item
- Create a new contact
- Create a new email message
The Code Examples
I'm not going to explain these examples too much--I'll just give a short description of what the code is querying, then show the full code example. Unfortunately, I don't have any publicly-accessible Exchange Servers so I am unable to give you any live, working examples. In all the examples below, I make the assumption that you have at least some programming experience and you'll understand to modify the parts you need to work in your environment. For example, obviously the name of your Exchange Server, the paths to your folders, your username and password will be different than what I show in these examples.Example 1 - Iterate the folders in a user's inbox
This first example will include a lot of comments and examples of how to work with the objects produced after parsing the result XML. Later examples will be less complete.<?php
// Modify the paths to these class files as needed.
require_once("class_http.php");
require_once("class_xml.php");
// Change these values for your Exchange Server.
$exchange_server = "http://NameOfYourExchangeServer";
$exchange_username = "YourExchangeUsername";
$exchange_password = "YourExchangePassword";
// We use Troy's http class object to send the XML-formatted WebDAV request
// to the Exchange Server and to receive the response from the Exchange Server.
// The response is also XML-formatted.
$h = new http();
$h->headers["Content-Type"] = 'text/xml; charset="UTF-8"';
// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/e2k3/e2k3/_webdav_depth_header.asp
$h->headers["Depth"] = "0";
$h->headers["Translate"] = "f";
// The trickiest part is forming your WebDAV query. This example shows how to
// find all the folders in the inbox for a user named 'twolf'.
$h->xmlrequest = '<?xml version="1.0"?>';
$h->xmlrequest .= <<<END
<a:searchrequest xmlns:a="DAV:" xmlns:s="http://schemas.microsoft.com/exchange/security/">
<a:sql>
SELECT "DAV:displayname"
FROM SCOPE('hierarchical traversal of "$exchange_server/Exchange/twolf/inbox"')
</a:sql>
</a:searchrequest>
END;
// IMPORTANT -- The END line above must be completely left-aligned. No white-space.
// The 'fetch' method does the work of sending and receiving the request.
// NOTICE the last parameter passed--'SEARCH' in this example. That is the
// HTTP verb that you must correctly set according to the type of WebDAV request
// you are making. The examples on this page use either 'PROPFIND' or 'SEARCH'.
if (!$h->fetch($exchange_server."/Exchange/twolf/inbox", 0, null, $exchange_username, $exchange_password, "SEARCH")) {
echo "<h2>There is a problem with the http request!</h2>";
echo $h->log;
exit();
}
// Note: The following lines can be uncommented to aid in debugging.
#echo "<pre>".$h->log."</pre><hr />\n";
#echo "<pre>".$h->header."</pre><hr />\n";
#echo "<pre>".$h->body."</pre><hr />\n";
#exit();
// Or, these next lines will display the result as an XML doc in the browser.
#header('Content-type: text/xml');
#echo $h->body;
#exit();
// The assumption now is that we've got an XML result back from the Exchange
// Server, so let's parse the XML into an object we can more easily access.
// For this task, we'll use Troy's xml class object.
$x = new xml();
if (!$x->fetch($h->body)) {
echo "<h2>There was a problem parsing your XML!</h2>";
echo "<pre>".$h->log."</pre><hr />\n";
echo "<pre>".$h->header."</pre><hr />\n";
echo "<pre>".$h->body."</pre><hr />\n";
echo "<pre>".$x->log."</pre><hr />\n";
exit();
}
// You should now have an object that is an array of objects and arrays that
// makes it easy to access the parts you need. These next lines can be
// uncommented to make a raw display of the data object.
#echo "<pre>\n";
#print_r($x->data);
#echo "</pre>\n";
#exit();
// And finally, an example of iterating the inbox folder names and url's to
// display in the browser. I also show you 2 methods to link to the folders.
// One uses the href provided in the response which opens the folder using OWA.
// The other is an Outlook style link to open the folder in the Outlook desktop
// client.
echo '<table border="1">';
foreach($x->data->A_MULTISTATUS[0]->A_RESPONSE as $idx=>$item) {
echo '<tr>'
.'<td>'.$item->A_PROPSTAT[0]->A_PROP[0]->A_DISPLAYNAME[0]->_text.'</td>'
.'<td><a href="'.$item->A_HREF[0]->_text.'">Click to open via OWA</a></td>'
.'<td><a href="Outlook:Inbox/'.$item->A_PROPSTAT[0]->A_PROP[0]->A_DISPLAYNAME[0]->_text.'">Click to open via Outlook</a></td>'
."</tr>\n";
}
echo "<table>\n";
?>
Some notes regarding Outlook URL's
By "Outlook URL", I mean the special "outlook:" URLs you can create that open the Outlook Client app to view folders and items. If anyone finds definitive documentation for all the options, please let me know. I found this link helpful. It's really old, but still applicable. http://support.microsoft.com/?kbid=225007If the 'twolf' inbox has a 'Company Picnic' subfolder, the code above would create a link to "Outlook:Inbox/Company Picnic". This link is really only good for twolf unless other users happen to have an Inbox/Company Picnic folder. You can make the link user-specific, but since it is unlikely any users have access to other users' mail folders, it is probably not that useful. But here is how you make that link: "Outlook://Mailbox - Troy Wolf/Inbox/Company Picnic".
I have not completely figured out what characters should be escaped and what should not. For example, it does not matter if you leave spaces as spaces or replace with %20, but you can not replace with a '+'. Other characters seem to break the URL if you urlencode them. Other characters seem to break the URL regardless of whether they are urlencoded or not! (The OWA hrefs seem to always work.)
Notes: You can run web pages in a normal browser or directly within Outlook. There are some caveats to be aware of.
- Links and new windows - When using Outlook: style links in a browser external to Outlook, the Outlook content will always open a new Outlook window regardless of the fact you may already have Outlook open. I see the argument for either way, but would prefer a method to indicate whether to open a new window or navigate the existing Outlook window. I think by default, folders should open in the existing Outlook window and specific items should open a new window--just like they do in Outlook normally. You taking notes, Bill? :) When using links on a web page running directly within Outlook, the content opens as expected. That is, folders navigate the current window to the folder and items pop in a new Outlook window.
- Authorization required content - When accessing content that requires basic authentication, your users will have to login when accessing the content inside Outlook. Then, if you pages pop new IE windows, they'll have to login again in the IE window. My assumption is that IE--the stand-alone browser--and IE running within Outlook use seperate memory space. If somebody has more technical details or workaround options, please send them to me at troy@troywolf.com.
Example 2 - Iterate the email items in a user's inbox
<?php
// Modify the paths to these class files as needed.
require_once("class_http.php");
require_once("class_xml.php");
// Change these values for your Exchange Server.
$exchange_server = "http://NameOfYourExchangeServer";
$exchange_username = "YourExchangeUsername";
$exchange_password = "YourExchangePassword";
// We use Troy's http class object to send the XML-formatted WebDAV request
// to the Exchange Server and to receive the response from the Exchange Server.
// The response is also XML-formatted.
$h = new http();
$h->headers["Content-Type"] = 'text/xml; charset="UTF-8"';
$h->headers["Depth"] = "0";
$h->headers["Translate"] = "f";
// Find all the email items in the inbox for a user named 'twolf'.
$h->xmlrequest = '<?xml version="1.0"?>';
$h->xmlrequest .= <<<END
<a:searchrequest xmlns:a="DAV:" xmlns:s="http://schemas.microsoft.com/exchange/security/">
<a:sql>
SELECT "DAV:displayname"
,"urn:schemas:httpmail:subject"
FROM "$exchange_server/Exchange/twolf/inbox"
</a:sql>
</a:searchrequest>
END;
// IMPORTANT -- The END line above must be completely left-aligned. No white-space.
// The 'fetch' method does the work of sending and receiving the request.
// NOTICE the last parameter passed--'SEARCH' in this example. That is the
// HTTP verb that you must correctly set according to the type of WebDAV request
// you are making. The examples on this page use either 'PROPFIND' or 'SEARCH'.
if (!$h->fetch($exchange_server."/Exchange/twolf/inbox", 0, null, $exchange_username, $exchange_password, "SEARCH")) {
echo "<h2>There is a problem with the http request!</h2>";
echo $h->log;
exit();
}
// The assumption now is that we've got an XML result back from the Exchange
// Server, so let's parse the XML into an object we can more easily access.
// For this task, we'll use Troy's xml class object.
$x = new xml();
if (!$x->fetch($h->body)) {
echo "<h2>There was a problem parsing your XML!</h2>";
echo "<pre>".$h->log."</pre><hr />\n";
echo "<pre>".$h->header."</pre><hr />\n";
echo "<pre>".$h->body."</pre><hr />\n";
echo "<pre>".$x->log."</pre><hr />\n";
exit();
}
// And finally, an example of iterating the email items to display in the
// browser. I also show you 2 methods to link to the items. One uses the href
// provided in the response which opens the folder using OWA. The other is an
// Outlook style link to open the folder in the Outlook desktop client.
echo '<table border="1">';
foreach($x->data->A_MULTISTATUS[0]->A_RESPONSE as $idx=>$item) {
echo '<tr>'
.'<td>'.$item->A_PROPSTAT[0]->A_PROP[0]->D_SUBJECT[0]->_text.'</td>'
.'<td><a href="'.$item->A_HREF[0]->_text.'">Click to open via OWA</a></td>'
.'<td><a href="Outlook:Inbox/~'.$item->A_PROPSTAT[0]->A_PROP[0]->D_SUBJECT[0]->_text.'">Click to open via Outlook</a></td>'
."</tr>\n";
}
echo "<table>\n";
?>
Example 3 - Search for contacts that match a certain criteria
This example can easily be modified to simply iterate all contacts in a folder by simply deleting the WHERE clause in the WebDAV request.<?php
// Modify the paths to these class files as needed.
require_once("class_http.php");
require_once("class_xml.php");
// Change these values for your Exchange Server.
$exchange_server = "http://NameOfYourExchangeServer";
$exchange_username = "YourExchangeUsername";
$exchange_password = "YourExchangePassword";
// We use Troy's http class object to send the XML-formatted WebDAV request
// to the Exchange Server and to receive the response from the Exchange Server.
// The response is also XML-formatted.
$h = new http();
$h->headers["Content-Type"] = 'text/xml; charset="UTF-8"';
$h->headers["Depth"] = "0";
$h->headers["Translate"] = "f";
// Find all the contacts for a specific company in a specific folder.
// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/e2k3/e2k3/_exch2k_urn_content-classes_person.asp
$h->xmlrequest = '<?xml version="1.0"?>';
$h->xmlrequest .= <<<END
<a:searchrequest xmlns:a="DAV:">
<a:sql>
SELECT "a:href"
,"urn:schemas:contacts:o"
,"urn:schemas:contacts:cn"
,"urn:schemas:contacts:fileas"
,"urn:schemas:contacts:title"
,"urn:schemas:contacts:email1"
,"urn:schemas:contacts:telephoneNumber"
FROM "$exchange_server/public/Customer%20Contacts/"
WHERE "urn:schemas:contacts:o" = 'XYZ Industries, Inc.'
ORDER BY "urn:schemas:contacts:cn"
</a:sql>
</a:searchrequest>
END;
// IMPORTANT -- The END line above must be completely left-aligned. No white-space.
// The 'fetch' method does the work of sending and receiving the request.
// NOTICE the last parameter passed--'SEARCH' in this example. That is the
// HTTP verb that you must correctly set according to the type of WebDAV request
// you are making. The examples on this page use either 'PROPFIND' or 'SEARCH'.
if (!$h->fetch($exchange_server."/public/Customer%20Contacts", 0, null, $exchange_username, $exchange_password, "SEARCH")) {
echo "<h2>There is a problem with the http request!</h2>";
echo $h->log;
exit();
}
// The assumption now is that we've got an XML result back from the Exchange
// Server, so let's parse the XML into an object we can more easily access.
// For this task, we'll use Troy's xml class object.
$x = new xml();
if (!$x->fetch($h->body)) {
echo "<h2>There was a problem parsing your XML!</h2>";
echo "<pre>".$h->log."</pre><hr />\n";
echo "<pre>".$h->header."</pre><hr />\n";
echo "<pre>".$h->body."</pre><hr />\n";
echo "<pre>".$x->log."</pre><hr />\n";
exit();
}
// And finally, an example of iterating the contact items to display in the browser.
echo '<table border="1">';
echo "<tr><th>Company</th><th>Name</th><th>Title</th><th>Email</th><th>Phone</th></tr>\n";
foreach($x->data->A_MULTISTATUS[0]->A_RESPONSE as $idx=>$contact) {
echo '<tr>'
.'<td>'.$contact->A_PROPSTAT[0]->A_PROP[0]->E_O[0]->_text.'</td>'
.'<td><a href="outlook://Public%20Folders/All%20Public%20Folders/Account_Contacts/~'.str_replace(" ","%20",$contact->A_PROPSTAT[0]->A_PROP[0]->E_CN[0]->_text).'">'
.$contact->A_PROPSTAT[0]->A_PROP[0]->E_CN[0]->_text.'</a></td>'
.'<td>'.$contact->A_PROPSTAT[0]->A_PROP[0]->E_TITLE[0]->_text.'</td>'
.'<td>'.$contact->A_PROPSTAT[0]->A_PROP[0]->E_EMAIL1[0]->_text.'</td>'
.'<td>'.$contact->A_PROPSTAT[0]->A_PROP[0]->E_TELEPHONENUMBER[0]->_text.'</td>'
."</tr>\n";
}
echo "<table>\n";
?>
Example 4 - Grab all properties for an item
This example demonstrates returning all properties for a specific message item. You can use the allprop property to return all properties. This is very useful when you don't know what the property names are.<?php
// Modify the paths to these class files as needed.
require_once("class_http.php");
require_once("class_xml.php");
// Change these values for your Exchange Server.
$exchange_server = "http://NameOfYourExchangeServer";
$exchange_username = "YourExchangeUsername";
$exchange_password = "YourExchangePassword";
// We use Troy's http class object to send the XML-formatted WebDAV request
// to the Exchange Server and to receive the response from the Exchange Server.
// The response is also XML-formatted.
$h = new http();
$h->headers["Content-Type"] = 'text/xml; charset="UTF-8"';
$h->headers["Depth"] = "0";
$h->headers["Translate"] = "f";
// Find all the properties for a specific item.
$h->xmlrequest = '<?xml version="1.0"?>';
$h->xmlrequest .= <<<END
<a:propfind xmlns:a="DAV:">
<a:allprop/>
</a:propfind>
END;
// IMPORTANT -- The END line above must be completely left-aligned. No white-space.
// The 'fetch' method does the work of sending and receiving the request.
// NOTICE the last parameter passed--'PROPFIND' in this example. That is the
// HTTP verb that you must correctly set according to the type of WebDAV request
// you are making. The examples on this page use either 'PROPFIND' or 'SEARCH'.
if (!$h->fetch($exchange_server."/public/Email%20Log/Some%20Message-668992879.EML", 0, null, $exchange_username, $exchange_password, "PROPFIND")) {
echo "<h2>There is a problem with the http request!</h2>";
echo $h->log;
exit();
}
// The assumption now is that we've got an XML result back from the Exchange
// Server, so let's parse the XML into an object we can more easily access.
// For this task, we'll use Troy's xml class object.
$x = new xml();
if (!$x->fetch($h->body)) {
echo "<h2>There was a problem parsing your XML!</h2>";
echo "<pre>".$h->log."</pre><hr />\n";
echo "<pre>".$h->header."</pre><hr />\n";
echo "<pre>".$h->body."</pre><hr />\n";
echo "<pre>".$x->log."</pre><hr />\n";
exit();
}
echo "<pre>";
print_r($x->data);
echo "</pre>";
?>
Example 5 - Create a new contact
This example demonstrates how to programmatically create a new contact in a public contacts folder. We'll use the PROPPATCH WebDAV method. This method will create a new contact if the URL does not exist or update an existing contact. Notice the last 2 properties. Those are custom fields that must be pre-defined for the folder. I show an example where a custom field is a boolean YES/NO. I struggled with how to set this property for a few hours. You're welcome. There are many, many more contact fields available. Use the allprop example #4 above to list an existing contact to see all the fields and schemas available.<?php
// Modify the path to this class file as needed.
require_once("class_http.php");
// Change these values for your Exchange Server.
$exchange_server = "http://NameOfYourExchangeServer";
$exchange_username = "YourExchangeUsername";
$exchange_password = "YourExchangePassword";
// We use Troy's http class object to send the XML-formatted WebDAV request
// to the Exchange Server and to receive the response from the Exchange Server.
// The response is also XML-formatted.
$h = new http();
$h->headers["Content-Type"] = 'text/xml; charset="UTF-8"';
$h->headers["Depth"] = "0";
$h->headers["Translate"] = "f";
// Build the XML request.
// This section must be against the left margin.
$h->xmlrequest = '<?xml version="1.0"?>';
$h->xmlrequest .= <<<END
<g:propertyupdate xmlns:g="DAV:"
xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/"
xmlns:c="urn:schemas:contacts:"
xmlns:e="http://schemas.microsoft.com/exchange/"
xmlns:mapi="http://schemas.microsoft.com/mapi/"
xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:cust="urn:schemas:customproperty"
xmlns:ed="urn:schemas-microsoft-com:exch-data:"
xmlns:repl="http://schemas.microsoft.com/repl/"
xmlns:x="xml:"
xmlns:cal="urn:schemas:calendar:"
xmlns:mail="urn:schemas:httpmail:"
xmlns:ec="urn:schemas-microsoft-com:exch-data:expected-content-class"
xmlns:j="urn:content-classes:propertydef"
xmlns:mailheader="urn:schemas:mailheader:">
<g:set>
<g:prop>
<g:contentclass>urn:content-classes:person</g:contentclass>
<e:outlookmessageclass>IPM.Contact</e:outlookmessageclass>
<e:keywords-utf8>
<x:v>Buddies</x:v><x:v>Engineers</x:v>
</e:keywords-utf8>
<c:language>US English</c:language>
<c:o>Innotech, Inc.</c:o>
<c:givenName>John</c:givenName>
<c:sn>Doe</c:sn>
<c:cn>John Doe</c:cn>
<c:fileas>Doe, John</c:fileas>
<c:street>100 N. Main</c:street>
<c:postofficebox>PO Box 555</c:postofficebox>
<c:l>Kansas City</c:l>
<c:st>MO</c:st>
<c:postalcode>64118</c:postalcode>
<c:co>USA</c:co>
<c:telephoneNumber>425-555-1110</c:telephoneNumber>
<c:facsimiletelephonenumber>425-555-1112</c:facsimiletelephonenumber>
<c:homePhone>425-555-1113</c:homePhone>
<c:mobile>425-555-1117</c:mobile>
<mapi:email1addrtype>SMTP</mapi:email1addrtype>
<mapi:email1emailaddress>john.doe@hotmail.com</mapi:email1emailaddress>
<mapi:email1originaldisplayname>John Doe</mapi:email1originaldisplayname>
<favoritecolor>Blue</favoritecolor>
<newsletter b:dt="boolean">1</newsletter>
</g:prop>
</g:set>
</g:propertyupdate>
END;
// IMPORTANT -- The END line above must be completely left-aligned. No white-space.
// The http object's 'fetch' method does the work of sending and receiving the
// request. We use the WebDAV PROPPATCH method to create or update Exchange items.
$url = $exchange_server."/public/Company%20Contacts/john%20doe.EML";
if (!$h->fetch($url, 0, null, $exchange_username, $exchange_password, "PROPPATCH")) {
echo "<h2>There is a problem with the http request!</h2>";
echo $h->log;
exit();
}
// You can print out the response to help troubleshoot.
echo "<pre>".$h->header."</pre><hr />\n";
echo "<pre>".$h->body."</pre><hr />\n";
?>
Example 6 - Create a new email message
This example demonstrates how to programmatically create a new email message. In this example, we'll create the example in the user's Drafts folder. The exchange username and password will need privileges to create items in the user's personal folders.<?php
// Modify the path to this class file as needed.
require_once("class_http.php");
// Change these values for your Exchange Server.
$exchange_server = "http://NameOfYourExchangeServer";
$exchange_username = "YourExchangeUsername";
$exchange_password = "YourExchangePassword";
// We use Troy's http class object to send the XML-formatted WebDAV request
// to the Exchange Server and to receive the response from the Exchange Server.
// The response is also XML-formatted.
$h = new http();
$h->headers["Content-Type"] = 'text/xml; charset="UTF-8"';
$h->headers["Depth"] = "0";
$h->headers["Translate"] = "f";
$subject = "Ci@lis CHEAP!";
// Build the XML request.
// This section must be against the left margin.
$h->xmlrequest = '<?xml version="1.0"?>';
$h->xmlrequest .= <<<END
<a:propertyupdate xmlns:a="DAV:"
xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/"
xmlns:g="http://schemas.microsoft.com/mapi/"
xmlns:e="urn:schemas:httpmail:"
xmlns:d="urn:schemas:mailheader:"
xmlns:c="xml:"
xmlns:f="http://schemas.microsoft.com/mapi/proptag/"
xmlns:h="http://schemas.microsoft.com/exchange/"
xmlns:i="urn:schemas-microsoft-com:office:office"
xmlns:k="http://schemas.microsoft.com/repl/"
xmlns:j="urn:schemas:calendar:"
xmlns:l="urn:schemas-microsoft-com:exch-data:">
<a:set>
<a:prop>
<a:contentclass>urn:content-classes:message</a:contentclass>
<h:outlookmessageclass>IPM.Note</h:outlookmessageclass>
<d:to>foo@foobar.com</d:to>
<d:cc>bar@foobar.com</d:cc>
<d:bcc>bob@aol.com</d:bcc>
<g:subject>$subject</g:subject>
<e:htmldescription>This is spam. Please delete this email.</e:htmldescription>
</a:prop>
</a:set>
</a:propertyupdate>
END;
// IMPORTANT -- The END line above must be completely left-aligned. No white-space.
// The http object's 'fetch' method does the work of sending and receiving the
// request. We use the WebDAV PROPPATCH method to create or update Exchange items.
$url = $exchange_server."/Exchange/twolf/Drafts/".urlencode($subject).".EML";
if (!$h->fetch($url, 0, null, $exchange_username, $exchange_password, "PROPPATCH")) {
echo "<h2>There is a problem with the http request!</h2>";
echo $h->log;
exit();
}
// You can print out the response to help troubleshoot.
echo "<pre>".$h->header."</pre><hr />\n";
echo "<pre>".$h->body."</pre><hr />\n";
// Bonus tip! You can automatically open this new draft message for your user by
// formulating an outlook URL. Then either redirect to the URL by uncommenting the
// header line below, or pop the URL in client-side javascript using window.open.
#header("Location: outlook:drafts/~".urlencode($subject));
?>
沒有留言:
張貼留言