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:
<?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";
?>
If 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.
<?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";
?>
<?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";
?>
<?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>";
?>
<?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";
?>
<?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));
?>