2. Conventions

This chapter will describe the PHP-MAPI API and how to use it. It will describe also some tricks that can be applied to make programming easier.

2.1. Properties

Every object in MAPI has several properties set. The properties contains information about the object like the name, the unique id, etc... A property tag consists of a type and a unique id, the property type and the property id. A property tag is made as in the following example:

Property definitions

define ('PR_SUBJECT', mapi_prop_tag(PT_TSTRING, 0x0037);
// This will result in a constant PR_SUBJECT with the value 0x001E0037

Within the PHP-code all of the properties are defined with a PR_ prefix to indicatie it is a property. All type codes are prefixed with PT_ (Property Type).

The function mapi_prop_tag accepts the property type and the property id and returns a unique code that indicates the property tag. This code is a simple long where the first 16 bits are the type and the last 16 bits are the ID.

Possible property types are:

Special properties:

  • PT_NULL
  • PT_ERROR

Single properties:

  • PT_I2
  • PT_LONG
  • PT_FLOAT
  • PT_DOUBLE
  • PT_APPTIME
  • PT_BOOLEAN
  • PT_STRING8
  • PT_SYSTIME
  • PT_BINARY
  • PT_CLSID

Multi value properties:

  • PT_MV_I2
  • PT_MV_LONG
  • PT_MV_R4
  • PT_MV_DOUBLE
  • PT_MV_APPTIME
  • PT_MV_SYSTIME
  • PT_MV_STRING8
  • PT_MV_BINARY
  • PT_MV_CLSID

Not supported:

  • PT_CURRENCY
  • PT_UNICODE
  • PT_OBJECT
  • PT_I8
  • PT_MV_CURRENCY
  • PT_MV_UNICODE
  • PT_MV_I8

Special properties for rules table:

  • PT_ACTIONS
  • PT_SRESTRICTION

2.2. Checking errors

When an array of properties is retrieved with the function mapi_getprops by example there could some problems with the properties. Because of optimalization MAPI has a restricition on the size of a property. When by example a PR_BODY is too big the error MAPI_E_NOT_ENOUGH_MEMORY will be returned instead of the actual data.

The type of this property will be set to PT_ERROR instead of PT_TSTRING. The value of the property will be the code of the error that occured. The numeric value of the property tag will be 0x000A0037 which is different from 0x001E0037. This requires a special way to check if there is an error and find out what the error code is.

The following program listing gives an example how to check the property on an error and how to get the error from the property:

Checking property errors in an object

// get properties
$msg_props = mapi_getprops($message);

if (array_key_exists($msg_props, PR_BODY) {
// there is a body with no error, display body here
} else {
        $pr_body_error = mapi_prop_tag(PT_ERROR, mapi_prop_id(PR_BODY));
        if (array_key_exists($msg_props, $pr_body_error)) {
                // found an error, now check which
                switch($msg_props[$pr_body_error]) {
                        case MAPI_E_NOT_ENOUGH_MEMORY:
                        // found but too big, use mapi_openproperty the get the data.
                        break;
                default:
                        // other error.
                        break;
                }
        } else {
        // No error found, the body doesn't exist in this object.
        }
}

Also, when checking error values (such as 0x80040107, MAPI_E_NOT_FOUND), you must use the mapi_is_error function to check whether the valus is actually an error. This is due to the fact that 0x800xxxxx numbers are not handed as unsigned numbers within PHP.

Checking for errors or warnings

if(mapi_is_error($msg_props[$pr_body_error])) {
        echo "error!\n";
}

When a function returns the value ‘false’, it means the function has failed. To retrieve the MAPI error, you can use the mapi_last_hresult() function, to get the last returned HRESULT value of a MAPI function. Example:

Checking last MAPI returned error code

$inbox = mapi_msgstore_getreceivefolder($store);
if ($inbox == false) {
        print "no inbox available!\n";
        printf("last result: %x\n",mapi_last_hresult());
}

2.3. Restrictions

Restrictions limit the rows queried using a set of conditions. This way messages can be selected on specific properties, such as the existance of a certain property, a property’s value larger than a certain value, etc..

A restriction is specified with an array with one value with the restriction type and one value with an array with the restriction specific properties.

Restriction types:

Restriction type Description
RES_AND Takes an array with restrictions that must all be valid.
RES_OR Expects an array with restrictions of which either one can be valid.
RES_NOT Wants an array with a single restriction that must not be valid.
RES_CONTENT Fuzzy search a property. The array must contain: FUZZYLEVEL, ULPROPTAG and VALUE. VALUE must be a string or binary. VALUE => array (tag => value)
RES_PROPERTY Takes an array with properties of a property which must be valid. This array must contain: RELOP, ULPROPTAG and VALUE. The property and VALUE can have different types. VALUE => array (tag => value)
RES_COMPAREPROPS Compares two props. Expects an array with the indexes: RELOP, ULPROPTAG1 and ULPROPTAG2.
RES_BITMASK Applies a bitmask. The given array must contain: ULPROPTAG, ULTYPE and ULMASK.
RES_SIZE Measures the size of a property. The array must have: ULPROPTAG, RELOP and CB.
RES_EXIST Checks whether a property exists. Takes only ULPROPTAG in its array.
RES_SUBRESTRICTION Places a restriction on a property. This is used on PR_MESSAGE_RECIPIENT properties with an RES_COMMENT restriction.
RES_COMMENT A comment restriction can be used to add comments to the restriction structure, or in a special case with the RES_SUBRESTRICTION. The RES_COMMENT structure holds a RES_PROPERTY restriction, which applies on the given properties list in the structure.

Retriction operations types:

Restriction operation Description
RELOP_LT Less than.
RELOP_LE Less than or equal to.
RELOP_GT Greater than.
RELOP_GE Greater than or equal to.
RELOP_EQ Equal to.
RELOP_NE Not equal to.
RELOP_RE The attachment has just been created.

FUZZYLEVEL can be one or bitwise more of these:

Fuzzy level Description
FL_FULLSTRING The property must be equal to this exact string.
FL_SUBSTRING Full text search for substring.
FL_PREFIX The value must start with this string.
FL_IGNORECASE Case should be ignored.
FL_IGNORENONSPACE Non-spaces should be ignored.
FL_LOOSE The string is allowed to match loosely.

Simple restriction example

// Example for restrictions
$testrestriction = Array(RES_OR,
                         Array(
                               Array(RES_PROPERTY,
                                     Array(RELOP => RELOP_LE, ULPROPTAG => PR_BODY,
                                           VALUE => array(PR_BODY =>"test"))),
                               Array(RES_PROPERTY,
                                     Array(RELOP => RELOP_LE, ULPROPTAG => PR_SUBJECT,
                                           VALUE => array(PR_SUBJECT => "bericht")))));

$contents = mapi_table_queryallrows($table, $props, $sizerestriction);

foreach ($contents as $row)
{
        echo $row[PR_SUBJECT];
        echo "\n";
}

RES_SUBRESTRICTION + RES_COMMENT example

$restriction =
array(RES_SUBRESTRICTION,
      array(ULPROPTAG => PR_MESSAGE_RECIPIENTS, // this is the subobject
         RESTRICTION => array(RES_COMMENT,
            array(PROPS => array(),  // probably need to add outlook data
               RESTRICTION => array(RES_PROPERTY,
                  array(RELOP => RELOP_EQ,
                     ULPROPTAG => PR_SEARCH_KEY,
                        VALUE => array(PR_SEARCH_KEY => "one-off entryid") )
               )
            )
         )
      )
);

2.4. Attachments

Attachments are used to send a binary file with a email message. There are a few different attachments types:

Property Description
ATTACH_BY_VALUE The attachment data is stored within the message object.
ATTACH_EMBEDDED_MSG The attachment is stored as a message object within the message object.
ATTACH_BY_REFERENCE These are regerences to a file by name. Since the PHP code runs on the server, these types are unsupported.
ATTACH_BY_REF_RESOLVE
ATTACH_BY_REF_ONLY

All of these types use different ways to store the name and to store the attachement data. with mapi_message_getattachmenttable() the table with all the attachments can be read from the message. Every row in this table has a property PR_ATTACH_METHOD which defines with which method the attachment was stored (see the values above). The property to read from an attachment or the function to call depends on the method an attachment has:

Method Description
NO_ATTACHMENT The attachment has just been created.
ATTACH_BY_VALUE The PR_ATTACH_FILENAME and PR_ATTACH_LONG_FILENAME contains the name of the attachment. When the attachment is opened with mapi_message_openattach() the data can be read with mapi_attach_openbin()
ATTACH_EMBEDDED_MSG The PR_DISPLAY_NAME contains the name of the attachment (This is mostly the PR_SUBJECT of the message that has been attached). With mapi_attach_openobj() the attachment can be read from the message. This function returns a message object.
ATTACH_BY_REFERENCE These types are unsupported and should never be used.
ATTACH_BY_REF_RESOLVE
ATTACH_BY_REF_ONLY

When created, all attachment objects have an initial PR_ATTACH_METHOD value of NO_ATTACHMENT. Client applications and service providers are only required to support the attachment method represented by the ATTACH_BY_VALUE value. The other attachment methods are optional.

The message store does not enforce any consistency between the value of PR_ATTACH_METHOD and the values of the other attachment properties.

2.5. Messages

The reading of the contents from a message can be done in a generic way. All messages contain all types of bodies. The different body types are kept in sync automatically, and the version which is written last wins, and updates the other versions. The following body types are available:

There are 3 ways in which the body of a message can be stored:

Type Description
RTF The content of the message is stored in the PR_RTF_COMPRESSED property.
Embedded HTML in RTF The content of the message is stored in the PR_RTF_COMPRESSED property. This is a special type of RTF that has an embedded HTML content. This version has been depricated by normal HTML.
HTML text The content of the message is stored in the PR_HTML property. is normal HTML formatted text.
Plain text The content of the body is stored in the PR_BODY property, and contains the plain text.

The PR_RTF_COMPRESSED property can hold RTF of HTML embedded text. If you use this property, you can convert it to HTML with the mapi_rtf2html function. Therefore, this property is only useful to detect if the original message was sent in TRF or HTML.

2.5.1. RTF

The RTF-text may be used when messages are stored within Outlook. RTF text can only be sent through the TNEF data in a winmail.dat attachment.

2.5.2. Embedded HTML in RTF

The messages that are sent in HTML will be stored as RTF in the PR_RTF_COMPRESSED property. The HTML is converted from plain HTML to Embedded HTML. With the function mapi_rtf2html this type of RTF can be converted back to HTML to be shown in a browser. The function will return false when the RTF is not of the correct type.

While this is still true, this method of reading the body is depricated. If you want the HTML version of the body, use the normal HTML property.

2.5.3. HTML text

The normal HTML property is stored in the PR_HTML property. This is the preferred property if you want to read the HTML body.

2.5.4. Plain text

The PR_BODY will contain the message body in Plain text.

2.6. Recipients

Recipients are used to specify the receiving entities for a specific message.

The structure of a recipient array in PHP is as follows. When recipients are set, you should use mapi_createoneoff() to create the right PR_ENTRYIDs:

Recipient table

// Create array of recipients usable by the PHP MAPI functions
$recipientarray = Array(Array(PR_ENTRYID => mapi_createoneoff("John Doe", "SMTP", "john@example.com"
                              PR_DISPLAY_NAME => "John Doe",
                              PR_ADDRTYPE => "SMTP",
                              PR_EMAIL_ADDRESS => "john@example.com"
                              PR_RECIPIENT_TYPE => MAPI_TO),
                        Array(PR_ENTRYID => mapi_createoneoff("Robert Roe", "SMTP", "robert@example.com"
                              PR_DISPLAY_NAME => "Robert Doe",
                              PR_ADDRTYPE => "SMTP",
                              PR_EMAIL_ADDRESS => "[email protected]"
                              PR_RECIPIENT_TYPE => MAPI_CC),
                        Array(PR_ENTRYID => mapi_createoneoff("Richard Miles", "SMTP", "richard@example.com"
                              PR_DISPLAY_NAME => "Richard Miles",
                              PR_ADDRTYPE => "SMTP",
                              PR_EMAIL_ADDRESS => "richard@example.com"
                              PR_RECIPIENT_TYPE => MAPI_BCC));

To set or get recipients, use the mapi_message_modifyrecipients and mapi_message_getrecipienttable methods.