Adding library for StreamSend
authorSteve Sutton <steve@gaslightmedia.com>
Tue, 7 Feb 2017 20:20:39 +0000 (15:20 -0500)
committerSteve Sutton <steve@gaslightmedia.com>
Tue, 7 Feb 2017 20:20:39 +0000 (15:20 -0500)
Files from the app.gaslightmedia.com

12 files changed:
lib/StreamSend/DB/AbstractTable.php [new file with mode: 0644]
lib/StreamSend/DB/IMapper.php [new file with mode: 0644]
lib/StreamSend/DB/Member.php [new file with mode: 0644]
lib/StreamSend/DB/MemberContact.php [new file with mode: 0644]
lib/StreamSend/DB/MemberContactMapper.php [new file with mode: 0644]
lib/StreamSend/DB/MemberMapper.php [new file with mode: 0644]
lib/StreamSend/Exception.php [new file with mode: 0644]
lib/StreamSend/Member.php [new file with mode: 0644]
lib/StreamSend/StreamSend.php [new file with mode: 0644]
lib/StreamSend/StreamSendAbstract.php [new file with mode: 0644]
lib/StreamSend/class_streamsend_api.inc [new file with mode: 0644]
models/admin/member/memberInfo.php

diff --git a/lib/StreamSend/DB/AbstractTable.php b/lib/StreamSend/DB/AbstractTable.php
new file mode 100644 (file)
index 0000000..1ba0db9
--- /dev/null
@@ -0,0 +1,105 @@
+<?php
+
+abstract class StreamSend_DB_AbstractTable {
+    protected $email_address;
+    protected $first_name;
+    protected $last_name;
+    protected $company;
+    protected $address1;
+    protected $address2;
+    protected $city;
+    protected $stateprovince;
+    protected $postal_code;
+    protected $phone_number;
+
+    public function getEmail_address() {
+        return $this->email_address;
+    }
+
+    public function setEmail_address($email_address) {
+        $this->email_address = $email_address;
+        return $this;
+    }
+
+    public function getFirst_name() {
+        return $this->first_name;
+    }
+
+    public function setFirst_name($first_name) {
+        $this->first_name = $first_name;
+        return $this;
+    }
+
+    public function getLast_name() {
+        return $this->last_name;
+    }
+
+    public function setLast_name($last_name) {
+        $this->last_name = $last_name;
+        return $this;
+    }
+    
+    public function getCompany() {
+        return $this->company;
+    }
+
+    public function setCompany($company) {
+        $this->company = $company;
+        return $this;
+    }
+
+    public function getAddress1() {
+        return $this->address1;
+    }
+
+    public function setAddress1($address1) {
+        $this->address1 = $address1;
+        return $this;
+    }
+
+    public function getAddress2() {
+        return $this->address2;
+    }
+
+    public function setAddress2($address2) {
+        $this->address2 = $address2;
+        return $this;
+    }
+
+    public function getCity() {
+        return $this->city;
+    }
+
+    public function setCity($city) {
+        $this->city = $city;
+        return $this;
+    }
+
+    public function getStateprovince() {
+        return $this->stateprovince;
+    }
+
+    public function setStateprovince($stateprovince) {
+        $this->stateprovince = $stateprovince;
+        return $this;
+    }
+
+    public function getPostal_code() {
+        return $this->postal_code;
+    }
+
+    public function setPostal_code($postal_code) {
+        $this->postal_code = $postal_code;
+        return $this;
+    }
+
+    public function getPhone_number() {
+        return $this->phone_number;
+    }
+
+    public function setPhone_number($phone_number) {
+        $this->phone_number = $phone_number;
+        return $this;
+    }
+}
+
diff --git a/lib/StreamSend/DB/IMapper.php b/lib/StreamSend/DB/IMapper.php
new file mode 100644 (file)
index 0000000..da1f8c6
--- /dev/null
@@ -0,0 +1,7 @@
+<?php
+
+interface IMapper {
+    public function findById(PDO $dbh, $id);
+    public function findByEmail(PDO $dbh, $email);
+    public function createFromArray($contact);
+}
diff --git a/lib/StreamSend/DB/Member.php b/lib/StreamSend/DB/Member.php
new file mode 100644 (file)
index 0000000..013e5b9
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+
+class StreamSend_DB_Member extends StreamSend_DB_AbstractTable
+{
+    protected $member_category;
+    protected $member_type;
+    protected $active;
+
+    public function getMember_category() {
+        return $this->member_category;
+    }
+
+    public function setMember_category($member_category) {
+        $this->member_category = $member_category;
+        return $this;
+    }
+
+    public function getMember_type() {
+        return $this->member_type;
+    }
+
+    public function setMember_type($member_type) {
+        $this->member_type = $member_type;
+        return $this;
+    }
+
+    public function getActive()
+    {
+        return $this->active;
+    }
+
+    public function setActive($active)
+    {
+        $this->active = $active;
+        return $this;
+    }
+
+
+}
+
diff --git a/lib/StreamSend/DB/MemberContact.php b/lib/StreamSend/DB/MemberContact.php
new file mode 100644 (file)
index 0000000..f06cbf7
--- /dev/null
@@ -0,0 +1,39 @@
+<?php
+
+class StreamSend_DB_MemberContact
+    extends StreamSend_DB_AbstractTable
+{
+    protected $member_category;
+    protected $member_type;
+    protected $active;
+
+    public function getMember_category() {
+        return $this->member_category;
+    }
+
+    public function setMember_category($member_category) {
+        $this->member_category = $member_category;
+        return $this;
+    }
+
+    public function getMember_type() {
+        return $this->member_type;
+    }
+
+    public function setMember_type($member_type) {
+        $this->member_type = $member_type;
+        return $this;
+    }
+
+    public function getActive()
+    {
+        return $this->active;
+    }
+
+    public function setActive($active)
+    {
+        $this->active = $active;
+        return $this;
+    }
+}
+
diff --git a/lib/StreamSend/DB/MemberContactMapper.php b/lib/StreamSend/DB/MemberContactMapper.php
new file mode 100644 (file)
index 0000000..a5e0c7a
--- /dev/null
@@ -0,0 +1,159 @@
+<?php
+
+/**
+ * Map the Member object with functions for creating and finding a member
+ *
+ * @author steve
+ */
+class StreamSend_DB_MemberContactMapper
+    implements IMapper
+{
+    protected $memberCategories = array();
+    public function findById(PDO $dbh, $id)
+    {
+        $memberContacts = array();
+        try {
+            $sql = "
+            SELECT category_id
+              FROM member_category
+             WHERE member_id = :member_id";
+            $memberCategories = $dbh->prepare($sql);
+            $sql = "
+            SELECT m.member_id,
+                   case
+                    when mc.send_mail <> true then false
+                    when m.active <> true then false
+                    when m.active = true and mc.send_mail = true then true
+                    else false end as active,
+                   m.member_name as company,
+                   mc.email as email_address,
+                   mc.fname as first_name,
+                   mc.lname as last_name,
+                   '' as address1,
+                   '' as city,
+                   '' as stateprovince,
+                   '' as postal_code,
+                   '' as phone_number,
+                   'Member Contact' as member_type
+              FROM member m
+                   LEFT OUTER JOIN member_contacts mc
+                   ON (m.member_id = mc.member_id)
+             WHERE mc.email != ''
+               AND mc.email is not null
+              -- AND mc.send_mail is true
+               AND m.member_id = :member_id";
+            $stmt = $dbh->prepare($sql);
+            $stmt->bindParam(
+                ':member_id',
+                $id,
+                PDO::PARAM_INT
+            );
+            $stmt->execute();
+            while ($member = $stmt->fetch(PDO::FETCH_ASSOC)) {
+                // get member categories
+                // this will be used also for its member contacts
+                $memberCatsArray = array();
+                $memberCategories->bindParam(
+                    ':member_id',
+                    $member['member_id'],
+                    PDO::PARAM_INT
+                );
+                $memberCategories->execute();
+                while ($mcdRow = $memberCategories->fetch(PDO::FETCH_ASSOC)) {
+                    if ($catNameString = $this->memberCategories[$mcdRow['category_id']]) {
+                        $memberCatsArray[] = $catNameString;
+                    }
+                }
+
+                $member['member_category'] = implode('|', $memberCatsArray);
+                $memberContacts[] = $this->createFromArray($member);
+            }
+            return $memberContacts;
+        } catch(PDOException $e) {
+            Toolkit_Common::handleError($e);
+        }
+        return false;
+    }
+    public function findByEmail(PDO $dbh, $email) {
+        try {
+            $sql = "
+            SELECT category_id
+              FROM member_category
+             WHERE member_id = :member_id";
+            $memberCategories = $dbh->prepare($sql);
+            $sql = "
+            SELECT m.member_id,
+                   case
+                    when mc.send_mail <> true then false
+                    when m.active <> true then false
+                    when m.active = true and mc.send_mail = true then true
+                    else true end
+                   as active,
+                   m.member_name as company,
+                   mc.email as email_address,
+                   mc.fname as first_name,
+                   mc.lname as last_name,
+                   '' as address1,
+                   '' as city,
+                   '' as stateprovince,
+                   '' as postal_code,
+                   '' as phone_number,
+                   'Member Contact' as member_type
+              FROM member m
+                   LEFT OUTER JOIN member_contacts mc
+                   ON (m.member_id = mc.member_id)
+             WHERE mc.email != ''
+               AND mc.email is not null
+               AND m.process_email = :process_email";
+            $stmt = $dbh->prepare($sql);
+            $stmt->bindParam(
+                ':process_email',
+                $email,
+                PDO::PARAM_INT
+            );
+            $stmt->execute();
+            $member = $stmt->fetch(PDO::FETCH_ASSOC);
+            // get member categories
+            // this will be used also for its member contacts
+            $memberCatsArray = array();
+            $memberCategories->bindParam(
+                ':member_id',
+                $member['member_id'],
+                PDO::PARAM_INT
+            );
+            $memberCategories->execute();
+            while ($mcdRow = $memberCategories->fetch(PDO::FETCH_ASSOC)) {
+                if ($catNameString = $this->memberCategories[$mcdRow['category_id']]) {
+                    $memberCatsArray[] = $catNameString;
+                }
+            }
+
+            $member['member_category'] = implode('|', $memberCatsArray);
+            return $this->createFromArray($member);
+        } catch(PDOException $e) {
+            Toolkit_Common::handleError($e);
+        }
+        return false;
+    }
+    public function createFromArray($contact) {
+        $member = new StreamSend_DB_MemberContact();
+        $member->setAddress1($contact['address1'])
+            ->setAddress2($contact['address2'])
+            ->setCity($contact['city'])
+            ->setCompany($contact['company'])
+            ->setEmail_address($contact['email_address'])
+            ->setFirst_name($contact['first_name'])
+            ->setLast_name($contact['last_name'])
+            ->setPhone_number($contact['phone_number'])
+            ->setPostal_code($contact['postal_code'])
+            ->setStateprovince($contact['stateprovince'])
+            ->setMember_category($contact['member_category'])
+            ->setMember_type($contact['member_type'])
+            ->setActive($contact['active']);
+        return $member;
+    }
+    public function setMemberCategories($categories)
+    {
+        $this->memberCategories = $categories;
+    }
+}
diff --git a/lib/StreamSend/DB/MemberMapper.php b/lib/StreamSend/DB/MemberMapper.php
new file mode 100644 (file)
index 0000000..4a38880
--- /dev/null
@@ -0,0 +1,100 @@
+<?php
+
+/**
+ * Map the Member object with functions for creating and finding a member
+ *
+ * @author Steve Sutton <steve@gaslightmedia.com>
+ */
+class StreamSend_DB_MemberMapper
+    implements IMapper
+{
+    protected $memberCategories = array();
+    public function findByIdOrEmail( $args )
+    {
+        if ( $args['email'] ) {
+            $email = $args['email'];
+        }
+        if ( $args['id'] ) {
+            $id = $args['id'];
+        }
+        if ( $email || $id ) {
+            throw new StreamSend_Exception(
+                'Name or Id not given findByIdOrEmail'
+            );
+        }
+        try {
+            $sql = "
+            SELECT category_id
+              FROM member_category
+             WHERE member_id = :member_id";
+            $memberCategories = $dbh->prepare($sql);
+            $sql = "
+            SELECT member_id,active,
+                   member_name as company,
+                   process_email as email_address,
+                   primary_contact_fname as first_name,
+                   primary_contact_lname as last_name,
+                   street as address1,
+                   city.city_name as city,
+                   state.state_abb as stateprovince,
+                   zip as postal_code,
+                   phone as phone_number,
+                   'Member' as member_type
+              FROM member
+                   LEFT OUTER JOIN state
+                   ON (member.state_id = state.state_id)
+                   LEFT OUTER JOIN city
+                   ON (member.city_id = city.city_id)
+             WHERE process_email != ''
+               AND process_email is not null
+               AND member_id = :member_id";
+            $stmt = $dbh->prepare($sql);
+            $stmt->bindParam( ':member_id', $id, PDO::PARAM_INT );
+            $stmt->execute();
+            $member = $stmt->fetch(PDO::FETCH_ASSOC);
+            // get member categories
+            // this will be used also for its member contacts
+            $memberCatsArray = array();
+            $memberCategories->bindParam( ':member_id', $member['member_id'], PDO::PARAM_INT );
+            $memberCategories->execute();
+            while ($mcdRow = $memberCategories->fetch(PDO::FETCH_ASSOC)) {
+                if ($catNameString = $this->memberCategories[$mcdRow['category_id']]) {
+                    $memberCatsArray[] = $catNameString;
+                }
+            }
+
+            $member['member_category'] = implode('|', $memberCatsArray);
+            return $this->createFromArray($member);
+        } catch(PDOException $e) {
+            Toolkit_Common::handleError($e);
+        }
+        return false;
+    }
+    public function createFromArray($contact) {
+        $member = new StreamSend_DB_Member();
+        $member->setAddress1($contact['address1'])
+            ->setAddress2($contact['address2'])
+            ->setCity($contact['city'])
+            ->setCompany($contact['company'])
+            ->setEmail_address($contact['email_address'])
+            ->setFirst_name($contact['first_name'])
+            ->setLast_name($contact['last_name'])
+            ->setPhone_number($contact['phone_number'])
+            ->setPostal_code($contact['postal_code'])
+            ->setStateprovince($contact['stateprovince'])
+            ->setMember_category($contact['member_category'])
+            ->setMember_type($contact['member_type'])
+            ->setActive($contact['active']);
+        return $member;
+    }
+    public function setMemberCategories($categories)
+    {
+        $this->memberCategories = $categories;
+    }
+    public function setMemberCategoriesForImport()
+    {
+        foreach ($this->memberCategories as $catId => &$catName) {
+            $this->memberCategories[$catId] = str_replace("&amp;", "&", $catName);
+        }
+    }
+}
diff --git a/lib/StreamSend/Exception.php b/lib/StreamSend/Exception.php
new file mode 100644 (file)
index 0000000..dfd9623
--- /dev/null
@@ -0,0 +1,9 @@
+<?php
+
+class StreamSend_Exception extends Exception
+{
+       public function __construct($message = null, $code = 0)
+       {
+               parent::__construct($message, $code);
+       }
+}
diff --git a/lib/StreamSend/Member.php b/lib/StreamSend/Member.php
new file mode 100644 (file)
index 0000000..4cea8d4
--- /dev/null
@@ -0,0 +1,349 @@
+<?php
+require_once GLM_APP_BASE . 'StreamSend/class_streamsend_api.inc';
+require_once GLM_APP_BASE . 'StreamSend/StreamSendAbstract.php';
+require_once GLM_APP_BASE . 'StreamSend/DB/IMapper.php';
+require_once GLM_APP_BASE . 'StreamSend/DB/MemberMapper.php';
+require_once GLM_APP_BASE . 'StreamSend/DB/Member.php';
+require_once GLM_APP_BASE . 'StreamSend/DB/MemberContactMapper.php';
+require_once GLM_APP_BASE . 'StreamSend/DB/MemberContact.php';
+
+/**
+ * StreamSend_Member
+ *
+ * @package    StreamSend
+ * @version   //autogen//
+ * @copyright Copyright (c) 2017 All rights reserved.
+ * @author
+ * @license   PHP Version 5.0 {@link http://www.php.net/license/3_0.txt}
+ */
+class StreamSend_Member
+    extends StreamSendAbstract
+{
+    public $memberCategories = array();
+    public $memberTypes = array( 'Member', 'Member Contact' );
+
+    public function addContactToStreamSend( StreamSend_DB_AbstractTable $contact ) {
+        $ret = $this->streamSend->contactSearch(
+            $contact->getEmail_address()
+        );
+        $contactData = array(
+            'email-address'   => $contact->getEmail_address(),
+            'first-name'      => $contact->getFirst_name(),
+            'last-name'       => $contact->getLast_name(),
+            'company'         => $contact->getCompany(),
+            'address1'        => $contact->getAddress1(),
+            'address2'        => $contact->getAddress2(),
+            'city'            => $contact->getCity(),
+            'stateprovince'   => $contact->getStateprovince(),
+            'postal-code'     => $contact->getPostal_code(),
+            'phone-number'    => $contact->getPhone_number(),
+            'member-category' => $contact->getMember_category(),
+            'member-type'     => $contact->getMember_type()
+        );
+        if ($ret->contact) {
+            // if the contact is active then update it
+            if ($contact->getActive()) {
+                $contacts = $this->streamSend->contactUpdate(
+                    $ret->contact->id,
+                    $contactData
+                );
+            } else {
+                $contacts = $this->streamSend->contactDelete(
+                    $contact->getEmail_address()
+                );
+            }
+        } else {
+            if ($contact->getActive()) {
+                $contacts = $this->streamSend->contactCreate(
+                    $contactData,
+                    STREAMSEND_DEFAULT_ACTIVATE,
+                    STREAMSEND_DEFAULT_DELIVER_ACTIVATION,
+                    STREAMSEND_DEFAULT_DELIVER_WELCOME
+                );
+            }
+        }
+        if ($contact->getActive() && !$contacts) {
+            // show errors if on development server
+            switch ($_ENV['GLM_HOST_ID']) {
+            case "DEVELOPMENT":
+                echo "<p>A total and complete failure occured.";
+                break;
+            case "PRODUCTION":
+                break;
+            }
+        }
+    }
+
+    public function cleanName($name)
+    {
+        $name = str_replace("&", "&amp;", $name);
+        $name = str_replace("'", "&apos;", $name);
+        $name = str_replace("\"", "&quot;", $name);
+        $name = str_replace("<", "&lt;", $name);
+        $name = str_replace(">", "&gt;", $name);
+        return $name;
+    }
+
+    public function createFieldOption($fieldId, $name)
+    {
+        $name = $this->cleanName($name);
+        $ret = $this->streamSend->fieldOptionCreate($fieldId, $name);
+        if ($this->debug) {
+            echo $this->streamSend->debugBuffer;
+        }
+        return $ret->responseData->optionID;
+    }
+
+    public function createMemberCategories(PDO $dbh)
+    {
+        $this->setMemberCategories($dbh);
+        if (is_array($this->memberCategories)
+            && is_array($this->memberCategories)
+            && !empty($this->memberCategories)
+        ) {
+            $this->streamSendDbMapper->setUpMemberCategory(
+                $this->memberCategories
+            );
+        }
+    }
+
+    public function createMemberTypes()
+    {
+        if (is_array($this->memberTypes)
+            && is_array($this->memberTypes)
+            && !empty($this->memberTypes)
+        ) {
+            $this->streamSendDbMapper->setUpMemberType(
+                $this->memberTypes
+            );
+        }
+    }
+
+    public function deleteFieldOption($fieldId, $optionId)
+    {
+        $this->streamSend->fieldOptionDelete($fieldId, $optionId);
+        if ($this->debug) {
+            echo $this->streamSend->debugBuffer;
+        }
+    }
+
+    public function getMemberCategoryFieldId()
+    {
+        $fieldId = false;
+        foreach ($this->fieldsData as $id => $field) {
+            if ($field['name'] == 'Member Category') {
+                $fieldId = $id;
+            }
+        }
+        return $fieldId;
+    }
+
+    public function getMemberTypeFieldId()
+    {
+        $fieldId = false;
+        foreach ($this->fieldsData as $id => $field) {
+            if ($field['name'] == 'Member Type') {
+                $fieldId = $id;
+            }
+        }
+        return $fieldId;
+    }
+
+    public function importMembers($contacts)
+    {
+        $contactData = array();
+        if (!is_array($contacts) || empty($contacts)) {
+            // nothing to do
+            return false;
+        }
+        foreach ($contacts as $contact) {
+            if (is_subclass_of($contact, 'StreamSend_DB_AbstractTable')) {
+                $contactData[] = array(
+                    'email_address'   => $contact->getEmail_address(),
+                    'first_name'      => $contact->getFirst_name(),
+                    'last_name'       => $contact->getLast_name(),
+                    'company'         => $contact->getCompany(),
+                    'address1'        => $contact->getAddress1(),
+                    'address2'        => $contact->getAddress2(),
+                    'city'            => $contact->getCity(),
+                    'stateprovince'   => $contact->getStateprovince(),
+                    'postal_code'     => $contact->getPostal_code(),
+                    'phone_number'    => $contact->getPhone_number(),
+                    'member_category' => $contact->getMember_category(),
+                    'member_type'     => $contact->getMember_type()
+                );
+            }
+        }
+        var_dump($contactData);
+        $ret = $this->streamSend->uploadContacts($contactData);
+        if ($this->debug) {
+            echo $this->streamSend->debugBuffer;
+        }
+        return $ret;
+    }
+
+    public function initializeMemberStreamSend(PDO $dbh)
+    {
+        $this->setStreamSend();
+        $this->getStreamSendFields();
+        $this->setMemberCategories($dbh);
+        $this->setUpMemberCategory($this->memberCategories);
+        $this->setUpMemberType(
+            array(
+                'Member',
+                'Member Contact'
+            )
+        );
+        $fData = $this->getAllStreamSendFieldData();
+        $fieldData = $fData->fieldsData;
+        $sql = "
+        SELECT parent_id
+          FROM category
+         WHERE category_id = :category_id";
+        $getParentId = $dbh->prepare($sql);
+        $sql = "
+        INSERT INTO streamsend
+        (field_id, field_name, option_id, option_name, category_id, parent)
+        VALUES
+        (:field_id, :field_name, :option_id, :option_name, :category_id, :parent)";
+        $stmt = $dbh->prepare($sql);
+        foreach ($fieldData as $fieldId => $field) {
+            if (   $field['name'] == 'Member Category'
+                && is_array($field['options'])
+            ) {
+                foreach ($field['options'] as $optionId => $fieldName) {
+                    $fieldName = $this->cleanName($fieldName);
+                    if ($categoryId = array_search($fieldName, $this->memberCategories)) {
+                        // first get the parent id for the category
+                        $getParentId->bindParam(
+                            ':category_id',
+                            $categoryId,
+                            PDO::PARAM_INT
+                        );
+                        $getParentId->execute();
+                        $parentId = $getParentId->fetchColumn();
+
+                        // now insert into the streamsend table
+                        $stmt->bindParam( ':field_id', $fieldId, PDO::PARAM_INT );
+                        $stmt->bindParam( ':field_name', $field['name'], PDO::PARAM_STR );
+                        $stmt->bindParam( ':option_id', $optionId, PDO::PARAM_INT );
+                        $stmt->bindParam( ':category_id', $categoryId, PDO::PARAM_INT );
+                        $stmt->bindParam( ':option_name', $fieldName, PDO::PARAM_STR );
+                        $stmt->bindParam( ':parent', $parentId, PDO::PARAM_INT );
+                        $stmt->execute();
+                    } else {
+                        var_dump($fieldName);
+                        throw new StreamSend_Exception(
+                            'Name not found in member categories'
+                        );
+                    }
+                }
+            }
+        }
+    }
+
+    public function setUpMemberCategory($options)
+    {
+        $fieldId = $this->getMemberCategoryFieldId();
+        if (!$fieldId) {
+            $ret = $this->streamSend->fieldCreate(
+                'Member Category',
+                STREAMSEND_API_FIELD_TYPE_CHECKBOX,
+                $options
+            );
+            if (!$ret) {
+                echo $this->streamSend->debugBuffer;
+            }
+        }
+    }
+
+    public function setUpMemberType($options)
+    {
+        $fieldId = $this->getMemberTypeFieldId();
+        if (!$fieldId) {
+            $ret = $this->streamSend->fieldCreate(
+                'Member Type',
+                STREAMSEND_API_FIELD_TYPE_CHECKBOX,
+                $options
+            );
+            if ($this->debug) {
+                echo $this->streamSend->debugBuffer;
+            }
+        }
+    }
+
+    public function setMemberCategories(PDO $dbh, $unClean = false)
+    {
+        static $categories = array();
+        if (empty($categories)) {
+            // first get all member categories
+            // Members cannot belong to a main category so they have to be subs
+            try {
+                $catTree = Toolkit_Common::getHierarchicalTreeStructure(
+                    $dbh,
+                    'category',
+                    'category_id',
+                    'parent_id',
+                    'name',
+                    0,
+                    3,
+                    true
+                );
+                if ($catTree && is_array($catTree) && !empty($catTree)) {
+                    $sql = "
+                    SELECT category_id,name
+                      FROM category
+                     WHERE category_id = :category_id";
+                    $stmt = $dbh->prepare($sql);
+                    $baseName = '';
+                    foreach ($catTree as $catid => $level) {
+                        if ($level == 1) {
+                            $baseName = '';
+                            $stmt->bindParam(':category_id', $catid);
+                            $stmt->execute();
+                            $row      = $stmt->fetch();
+                            $baseName
+                                = ($unClean)
+                                ? $row['name']
+                                : $this->cleanName($row['name']);
+                        }
+                        if ($level == 2) {
+                            $stmt->bindParam(':category_id', $catid);
+                            $stmt->execute();
+                            $row          = $stmt->fetch();
+                            $categoryName = $row['name'];
+                            $categories[$row['category_id']]
+                                = ($unClean)
+                                ? $baseName . '/' . $categoryName
+                                : $baseName . '/' .
+                                    $this->cleanName($categoryName);
+                        }
+                        if ($level == 3) {
+                            $stmt->bindParam(':category_id', $catid);
+                            $stmt->execute();
+                            $row          = $stmt->fetch();
+                            $categoryName = $row['name'];
+                            $categories[$row['category_id']]
+                                = ($unClean)
+                                ? $baseName . '/' . $categoryName
+                                : $baseName . '/' .
+                                    $this->cleanName($categoryName);
+                        }
+                    }
+                }
+            } catch(PDOException $e) {
+                Toolkit_Common::handle_error($e);
+            }
+        }
+        $this->memberCategories = $categories;
+    }
+
+    public function updateFieldOption($fieldId, $optionId, $name)
+    {
+        $name = $this->cleanName($name);
+        $this->streamSend->fieldOptionUpdate($fieldId, $optionId, $name);
+        if ($this->debug) {
+            echo $this->streamSend->debugBuffer;
+        }
+    }
+}
diff --git a/lib/StreamSend/StreamSend.php b/lib/StreamSend/StreamSend.php
new file mode 100644 (file)
index 0000000..5c7eb6f
--- /dev/null
@@ -0,0 +1,442 @@
+<?php
+/**
+ * StreamSend.php
+ *
+ * PHP Version 5
+ *
+ * @category Toolkit
+ * @package  Members
+ * @author   Jamie Kahgee <steve@gaslightmedia.com>
+ * @license  Gaslight Media
+ * @link     <>
+ */
+
+/**
+ * Default parameters for contact create operations.
+ * Note that these are strings for use in XML data not true/false values.
+ * If false, the person will be created with a status of pending
+ */
+define('STREAMSEND_DEFAULT_ACTIVATE', 'true');
+/**
+  * If activate is false, setting this to true will trigger the sending of the built-in
+  * activation notification; if activate is true, this setting has no effect
+  */
+define('STREAMSEND_DEFAULT_DELIVER_ACTIVATION', 'false');
+/**
+  * If activate is true, setting this to true will trigger the sending of the built-in
+  * welcome notification; if activate is false, this setting has no effect
+  */
+define('STREAMSEND_DEFAULT_DELIVER_WELCOME', 'false');
+
+require_once 'StreamSend/Member.php';
+
+/**
+ * Toolkit_Members_StreamSend
+ *
+ * Description for Toolkit_Members_StreamSend
+ *
+ * @category Toolkit
+ * @package  Members
+ * @author   Jamie Kahgee <steve@gaslightmedia.com>
+ * @license  Gaslight Media
+ * @link     <>
+ */
+class Toolkit_Members_StreamSend
+{
+    /**
+     * Description of $streamSendMember
+     * @var StreamSend_Member()
+     * @access protected
+     */
+    protected $streamSendMember;
+
+    /**
+     * Class constructor
+     *
+     * @access public
+     */
+    public function __construct()
+    {
+        $this->streamSendMember = new StreamSend_Member();
+    }
+
+    public function sendMember($member)
+    {
+        $this->streamSendMember->setStreamSend();
+        //$this->streamSendMember->setMemberCategories($this->dbh);
+        $memberMapper = new StreamSend_DB_MemberMapper();
+        $member = $memberMapper->createFromArray(
+            array(
+                'address1'      => $member['addr1'],
+                'address2'      => $member['addr2'],
+                'city'          => $member['city']['name'],
+                'stateprovince' => $member['state']['value'],
+                'postal_code'   => $member['zip'],
+                'email_address' => $member['email'],
+                'phone_number'  => $member['phone'],
+                'active'        => (($member['active']['value'] == 10)?'1':'0'),
+                'member_type'   => 'Member'
+            )
+        );
+        echo '<pre>$member: ' . print_r( $member, true ) . '</pre>';
+
+    }
+    /**
+     * Description for SendMemberById
+     *
+     * @param integer $memberId Member ID
+     *
+     * @return void
+     * @access public
+     */
+    public function sendMemberById($memberId)
+    {
+        $this->streamSendMember->setStreamSend();
+        $this->streamSendMember->setMemberCategories($this->dbh);
+
+        $memberMapper = new StreamSend_DB_MemberMapper();
+        $memberMapper->setMemberCategories(
+            $this->streamSendMember->memberCategories
+        );
+
+        $member = $memberMapper->findById($this->dbh, $memberId);
+        if ($member->getEmail_address()) {
+               $this->streamSendMember->addContactToStreamSend($member);
+        }
+    }
+
+    /**
+     * Description for SendMemberContactsByMemberId
+     *
+     * @param int $memberId Member ID
+     *
+     * @return void
+     * @access public
+     */
+    public function sendMemberContactsByMemberId($memberId)
+    {
+        $this->streamSendMember->debug = true;
+        $this->streamSendMember->setStreamSend();
+        $this->streamSendMember->setMemberCategories($this->dbh);
+
+        $memberContactMapper = new StreamSend_DB_MemberContactMapper();
+        $memberContactMapper->setMemberCategories(
+            $this->streamSendMember->memberCategories
+        );
+        $memberContacts = $memberContactMapper->findById(
+            $this->dbh,
+            $memberId
+        );
+        foreach ($memberContacts as $contact) {
+            $this->streamSendMember->addContactToStreamSend($contact);
+        }
+    }
+
+    /**
+     * Given a Member Category id and name update the streamsend option
+     * for the field 'Member Category'
+     * This also update the streamsend table with correct data
+     * if the category parent is 0 or the category is deleted streamsend table
+     * is updated and the field option is destroyed
+     *
+     * @param integer $categoryId category_id field table category
+     *
+     * @return void
+     */
+    public function updateOptionByCategoryId($categoryId)
+    {
+        $this->streamSendMember->setStreamSend();
+        $this->streamSendMember->setMemberCategories($this->dbh);
+        if (!is_numeric($categoryId)) {
+            throw new InvalidArgumentException(
+                'CategoryId supplied must be numeric'
+            );
+        }
+        try {
+            // for getting the categories parent name (needed for option name)
+            $sql = "
+            SELECT name
+              FROM category
+             WHERE category_id = :parent_id";
+            $getParentId = $this->dbh->prepare($sql);
+            // get data from our cache table of member category options
+            $sql = "
+            SELECT *
+              FROM streamsend
+             WHERE category_id = :category_id";
+            $stmt = $this->dbh->prepare($sql);
+            $stmt->bindParam(
+                ':category_id',
+                $categoryId,
+                PDO::PARAM_INT
+            );
+            $stmt->execute();
+            // $currentFieldData is from the streamsend table
+            $currentFieldData = $stmt->fetch(PDO::FETCH_ASSOC);
+            $sql = "
+            SELECT *
+              FROM category
+             WHERE category_id = :category_id";
+            $stmt = $this->dbh->prepare($sql);
+            $stmt->bindParam(
+                ':category_id',
+                $categoryId,
+                PDO::PARAM_INT
+            );
+            $stmt->execute();
+            // $data will be the array from category table
+            $data = $stmt->fetch(PDO::FETCH_ASSOC);
+            if ($data['parent_id']) {
+                $getParentId->bindParam(
+                    ':parent_id',
+                    $data['parent_id'],
+                    PDO::PARAM_INT
+                );
+                $getParentId->execute();
+                $parentName = $getParentId->fetchColumn();
+                $newOptionName = $parentName . '/' . $data['name'];
+                $newOptionName = $this->streamSendMember->cleanName(
+                    $newOptionName
+                );
+            }
+            if ($currentFieldData) {
+                if (!$data['parent_id'] || $_REQUEST['delete']) {
+                    $this->streamSendFieldOptionDelete(
+                        $currentFieldData,
+                        $categoryId
+                    );
+                    return true;
+                }
+                if ($newOptionName != $currentFieldData['option_name']) {
+                    // we'll need to see if the category is going to parent 0
+                    // if it is then it'll need to be deleted
+                    // if not then it'll be updated
+                    if ($data['parent_id']) {
+                        $this->streamSendFieldOptionUpdate(
+                            $currentFieldData['field_id'],
+                            $currentFieldData['option_id'],
+                            $newOptionName,
+                            $categoryId
+                        );
+                    }
+                }
+            } else {
+                if (!$_REQUEST['delete'] && $data['parent_id']) {
+                    $this->streamSendFieldOptionCreate(
+                        $data,
+                        $newOptionName
+                    );
+                }
+            }
+            return true;
+        } catch(PDOException $e) {
+            Toolkit_Common::handleError($e);
+        }
+    }
+
+    /**
+     * Create a new stream send field option
+     * gets the fieldId from feild_id in 'Member Category' (streamsend table)
+     * function must setup the dabase entry for field option and submit to
+     * streamsend the new field option
+     *
+     * @param array  $data          Array from the category table
+     * @param string $newOptionName option name 'parent/subcategory'
+     *
+     * @return boolean|void
+     * @throws InvalidArgumentException
+     * @access protected
+     */
+    protected function streamSendFieldOptionCreate($data, $newOptionName)
+    {
+        if (!is_numeric($data['category_id'])) {
+            throw new InvalidArgumentException(
+                '$data["category_id"] must be numeric'
+            );
+        }
+        if (!is_numeric($data['parent_id'])) {
+            throw new InvalidArgumentException(
+                '$data["parent_id"] must be numeric'
+            );
+        }
+        if (!isset($newOptionName) || !strstr($newOptionName, '/')) {
+            throw new InvalidArgumentException(
+                'Must have option name with a slash in it'
+            );
+        }
+        $sql = "
+        SELECT field_id
+          FROM streamsend
+         WHERE field_name = 'Member Category'
+         LIMIT 1
+        OFFSET 0";
+        $fieldId = $this->dbh->query($sql)->fetchColumn();
+        $fieldName = 'Member Category';
+        // for a new field option create a record in cache table
+        $sql = "
+        INSERT INTO streamsend
+        (field_id, field_name, option_id, option_name, category_id, parent)
+        VALUES
+        (:field_id, :field_name, :option_id, :option_name, :category_id, :parent)";
+        $stmt = $this->dbh->prepare($sql);
+        $optionId = $this->streamSendMember->createFieldOption(
+            $fieldId,
+            $newOptionName
+        );
+        if (!$optionId) {
+            // if we can't find it here then look into the member category array
+            if (is_array($this->streamSendMember->memberCategories)) {
+                foreach ($this->streamSendMember->memberCategories as $optId => $name) {
+                    if ($newOptionName == $name) {
+                        $optionId = $optId;
+                        continue;
+                    }
+                }
+            }
+        }
+        if (!$optionId) {
+            return true;
+        }
+        // now insert into the streamsend table
+        $stmt->bindParam(
+            ':field_id',
+            $fieldId,
+            PDO::PARAM_INT
+        );
+        $stmt->bindParam(
+            ':field_name',
+            $fieldName,
+            PDO::PARAM_STR
+        );
+        $stmt->bindParam(
+            ':option_id',
+            $optionId,
+            PDO::PARAM_INT
+        );
+        $stmt->bindParam(
+            ':category_id',
+            $data['category_id'],
+            PDO::PARAM_INT
+        );
+        $stmt->bindParam(
+            ':option_name',
+            $newOptionName,
+            PDO::PARAM_STR
+        );
+        $stmt->bindParam(
+            ':parent',
+            $data['parent_id'],
+            PDO::PARAM_INT
+        );
+        $stmt->execute();
+    }
+
+    /**
+     * delete the field option from streamsend
+     * and delete the streamsend record for it
+     * This has to be done before the actual delete of the category
+     * or it won't find the field_id and option_id so it can be deleted
+     * from streamsend
+     *
+     * reamsend the new field option
+     *
+     * @param array  $data       Array from the category table
+     * @param string $categoryId Category ID
+     *
+     * @return void
+     * @throws InvalidArgumentException
+     * @access protected
+     */
+    protected function streamSendFieldOptionDelete($data, $categoryId)
+    {
+        if (!is_numeric($data['field_id'])) {
+            throw new InvalidArgumentException(
+                '$data["field_id"] must be numeric'
+            );
+        }
+        if (!is_numeric($data['option_id'])) {
+            throw new InvalidArgumentException(
+                '$data["option_id"] must be numeric'
+            );
+        }
+        if (!is_numeric($categoryId)) {
+            throw new InvalidArgumentException(
+                '$categoryId must be numeric'
+            );
+        }
+        $this->streamSendMember->deleteFieldOption(
+            $data['field_id'],
+            $data['option_id']
+        );
+        try {
+            $sql = "
+            DELETE FROM streamsend
+             WHERE category_id = :category_id";
+            $stmt = $this->dbh->prepare($sql);
+            $stmt->bindParam(
+                ':category_id',
+                $categoryId,
+                PDO::PARAM_INT
+            );
+            $stmt->execute();
+        } catch(PDOException $e) {
+            Toolkit_Common::handleError($e);
+        }
+    }
+
+    /**
+     * update the streamsend account with new name for an option
+     * and update our streamsend table with new name
+     *
+     * @param integer $fieldId       field_id of streamsend field
+     * @param integer $optionId      option_id of streamsend field
+     * @param string  $newOptionName new name
+     * @param integer $categoryId    category_id for streamsend table
+     *
+     * @return void
+     * @access protected
+     * @throws InvalidArgumentException
+     */
+    protected function streamSendFieldOptionUpdate( $fieldId, $optionId, $newOptionName, $categoryId ) {
+        if (!is_numeric($fieldId)) {
+            throw new InvalidArgumentException(
+                '$fieldId must be numeric'
+            );
+        }
+        if (!is_numeric($optionId)) {
+            throw new InvalidArgumentException(
+                '$optionId must be numeric'
+            );
+        }
+        if (!isset($newOptionName) || !strstr($newOptionName, '/')) {
+            throw new InvalidArgumentException(
+                'Must have option name with a slash in it'
+            );
+        }
+        $this->streamSendMember->updateFieldOption(
+            $fieldId,
+            $optionId,
+            $newOptionName
+        );
+        try {
+            $sql = "
+            UPDATE streamsend
+               SET option_name = :option_name
+             WHERE category_id = :category_id";
+            $stmt = $this->dbh->prepare($sql);
+            $stmt->bindParam(
+                ':category_id',
+                $categoryId,
+                PDO::PARAM_INT
+            );
+            $stmt->bindParam(
+                ':option_name',
+                $newOptionName,
+                PDO::PARAM_STR
+            );
+            $stmt->execute();
+        } catch(PDOException $e) {
+            Toolkit_Common::handleError($e);
+        }
+    }
+}
diff --git a/lib/StreamSend/StreamSendAbstract.php b/lib/StreamSend/StreamSendAbstract.php
new file mode 100644 (file)
index 0000000..722d719
--- /dev/null
@@ -0,0 +1,70 @@
+<?php
+/**
+  * URI for streamsend API
+  */
+define( 'STREAMSEND_BASE_URL', 'https://app.streamsend.com' );
+
+/**
+ * StreamSendAbstract
+ *
+ * @abstract
+ * @package   StreamSend
+ * @version   //autogen//
+ * @copyright Copyright (c) 2017 All rights reserved.
+ * @author    Steve Sutton <steve@gaslightmedia.com>
+ * @license   PHP Version 5.0 {@link http://www.php.net/license/3_0.txt}
+ */
+abstract class StreamSendAbstract
+{
+    public $debug = false;
+
+    protected $fieldsData = array();
+    protected $streamSend;
+    protected $streamSendDbMapper;
+
+    const streamSendLoginId = STREAMSEND_LOGIN_ID;
+    const streamSendKey     = STREAMSEND_KEY;
+
+    abstract public function addContactToStreamSend( StreamSend_DB_AbstractTable $contact );
+
+    public function getStreamSendFields()
+    {
+        $fields = $this->streamSend->fieldsList();
+        if (!$fields || $fields->responseStatus) {
+            return false;
+        }
+        foreach ($fields->responseData->field as $field) {
+
+            $f_id = (int) $field->id;
+
+            /** Add the field data to the plain fieldsData array */
+            $this->fieldsData[$f_id] = array(
+                'name'          => ( (string) $field->name ),
+                'data-type'     => ( (string) $field->{'data-type'} ),
+                'include-blank' => ( (string) $field->{'include-blank'} ),
+                'allow-other'   => ( (string) $field->{'allow-other'} ),
+                'slug'          => ( (string) $field->slug )
+            );
+        }
+    }
+
+    public function getAllStreamSendFieldData()
+    {
+        return $this->streamSend->fieldsGetAll();
+    }
+
+    public function setStreamSend()
+    {
+        $this->streamSend = new streamSend(
+            STREAMSEND_BASE_URL,
+            self::streamSendLoginId,
+            self::streamSendKey
+        );
+        $this->streamSend->debug = $this->debug;
+    }
+
+    public function setStreamSendDBMapper($mapper)
+    {
+        $this->streamSendDbMapper = $mapper;
+    }
+}
diff --git a/lib/StreamSend/class_streamsend_api.inc b/lib/StreamSend/class_streamsend_api.inc
new file mode 100644 (file)
index 0000000..7e4dd1e
--- /dev/null
@@ -0,0 +1,2160 @@
+<?php
+/**
+ * Streamsend API Class
+ *
+ * The Streamsend API class provides simplified interaction with the Streamsend
+ * API. Streamsend is an E-Mail Marketing Solutions provider. Gaslight Media
+ * is considering integrating, or has integrated, Streamsend services into
+ * the Gaslight Media Contact Services applications. This class library intends
+ * to streamline interaction between Gaslight Media applications and the
+ * Streamsend server API. Documentation for the StreamSend API may be found at
+ * the following URL.
+ *
+ * https://app.streamsend.com/docs/api/index.html
+ *
+ * This Class is designed to run under PHP version 5 and require PHP Curl.
+ *
+ * LICENSE: This software constitutes a "trade secret" and "proprietary materials"
+ * of Gaslight Media. It is not available for licensing at this time. Any possession
+ * or use of these materials without the specific written permission of Gaslight
+ * Media is considered a violation of Gaslight Media's rights and will be pursued
+ * to the full extent of available law.
+ *
+ * @category   Gaslight Media Proprietary Class Library
+ * @package    StreamSend API Integration
+ * @author     Gaslight Media <info@gaslightmedia.com>
+ * @copyright  2008 Gaslight Media - All rights Reserved
+ * @license    Trade Secret and Proprietary Materials - No License
+ * @version    CVS: $Id: class_streamsend_api.inc,v 1.2 2010/03/23 14:28:23 matrix Exp $
+ * @link       (none)
+ * @see
+ * @since      File available since Release 1.0
+ * @deprecated
+ */
+
+/**
+* Require DocBlock
+*
+* No includes required.
+*
+* PHP libcurl support required.
+*/
+
+/**
+ * ----------------------------------------------------------
+ * Definitions
+ */
+define ("STREAMSEND_MAX_PAGE_SIZE",     10000);       // This must be set to the maximum page size for the StreamSend system
+
+/**
+ * API Interface Error Codes
+ *
+ * Also add an error message for each error code
+ */
+    /** Errors related to responses from StreamSend */
+define ("STREAMSEND_API_ERROR_NONE", 0);
+define ("STREAMSEND_API_ERROR_NO_RESPONSE", 1);
+define ("STREAMSEND_API_ERROR_HTTP_RESPONSE", 2);
+define ("STREAMSEND_API_ERROR_BAD_XML", 3);
+
+    /** Errors related to use of the GLM StreamSend methods and functions */
+define ("STREAMSEND_API_ERROR_POSTDATA_NOT_SUPPLIED", 100);
+
+/**
+ * API Interface Error Messages
+ *
+ * Requires an error code for each error message
+ */
+$streamsendErrorMessage = array (
+
+        /** Errors related to responses from StreamSend */
+    STREAMSEND_API_ERROR_NONE          => 'No error reported.',
+    STREAMSEND_API_ERROR_NO_RESPONSE   => 'No response received from server.',
+    STREAMSEND_API_ERROR_HTTP_RESPONSE => 'Server replied with an unexpected HTTP response code.',
+    STREAMSEND_API_ERROR_BAD_XML       => 'The XML response was malformed.',
+
+        /** Errors related to use of the GLM StreamSend methods and functions */
+    STREAMSEND_API_ERROR_POSTDATA_NOT_SUPPLIED => 'No POST data was supplied to send to StreamSend.',
+);
+
+/**
+ * Field Types
+ */
+define("STREAMSEND_API_FIELD_TYPE_TEXT", 1);
+define("STREAMSEND_API_FIELD_TYPE_TEXTAREA", 2);
+define("STREAMSEND_API_FIELD_TYPE_DATETIME", 3);
+define("STREAMSEND_API_FIELD_TYPE_DATE", 4);
+define("STREAMSEND_API_FIELD_TYPE_TIME", 5);
+define("STREAMSEND_API_FIELD_TYPE_SELECT", 6);
+define("STREAMSEND_API_FIELD_TYPE_RADIO", 7);
+define("STREAMSEND_API_FIELD_TYPE_CHECKBOX", 8);
+
+$streamsendFieldTypes = array (
+    STREAMSEND_API_FIELD_TYPE_TEXT     => array( 'Name' => 'Text Field',   'Options' => false ),
+    STREAMSEND_API_FIELD_TYPE_TEXTAREA => array( 'Name' => 'Text Area',    'Options' => false ),
+    STREAMSEND_API_FIELD_TYPE_DATETIME => array( 'Name' => 'Date/Time',    'Options' => false ),
+    STREAMSEND_API_FIELD_TYPE_DATE     => array( 'Name' => 'Date',         'Options' => false ),
+    STREAMSEND_API_FIELD_TYPE_TIME     => array( 'Name' => 'Time',         'Options' => false ),
+    STREAMSEND_API_FIELD_TYPE_SELECT   => array( 'Name' => 'Select',       'Options' => true ),
+    STREAMSEND_API_FIELD_TYPE_RADIO    => array( 'Name' => 'Radio',        'Options' => true ),
+    STREAMSEND_API_FIELD_TYPE_CHECKBOX => array( 'Name' => 'Checkbox',     'Options' => true )
+);
+
+/**
+ * Other General Setup Data
+ */
+
+$HTTPResponseCodeText = array(
+    100 => 'Continue',
+    101 => 'Switching Protocols',
+    200 => 'OK',
+    201 => 'Created',
+    202 => 'Accepted',
+    203 => 'Non-Authoritative Information',
+    204 => 'No Content',
+    205 => 'Reset Content',
+    206 => 'Partial Content',
+    300 => 'Multiple Choices',
+    301 => 'Permanently Moved',
+    302 => 'Found',
+    303 => 'See Other',
+    304 => 'Not Modified',
+    305 => 'Use Proxy',
+    306 => 'Tempoorary Redirect',
+    400 => 'Your browser sent a request that this server could not understand. XML included with a request may be malformed',
+    401 => 'Authorization Required or StreamSend authentication error',
+    402 => 'Payment Required, plan upgrade required, number of recipients for blast may exceed available quota for account',
+    403 => 'Forbidden or permission not granted by StreamSend - Contact support staff',
+    404 => 'Not Found - Possible reasons: The resource never existed, The resource was destroyed, The requested URI is invalid.',
+    405 => 'Method Not Allowed',
+    406 => 'Not Acceptable',
+    407 => 'Proxy Authentication Required',
+    408 => 'Request Time-out',
+    409 => 'Conflict',
+    410 => 'Gone',
+    411 => 'Length Required',
+    412 => 'Precondition Failed',
+    413 => 'Request Entity Too Large',
+    414 => 'Request-URI Too Large',
+    415 => 'Unsupported Media Type',
+    416 => 'Requested Range Not Satisfiable',
+    417 => 'Expectation Failed',
+    422 => 'Unprocessable Entity',
+    423 => 'Locked',
+    424 => 'Failed Dependency',
+    425 => 'No code',
+    426 => 'Upgrade Required',
+    500 => 'Internal Server Error',
+    501 => 'Method Not Implemented',
+    502 => 'Bad Gateway',
+    503 => 'Service Temporarily Unavailable',
+    504 => 'Gateway Time-out',
+    505 => 'HTTP Version Not Supported',
+    506 => 'Variant Also Negotiates',
+    507 => 'Insufficient Storage',
+    510 => 'Not Extended'
+);
+
+
+/**
+ * End of Definitions
+ * ----------------------------------------------------------
+ */
+
+
+
+/**
+ *  StreamSend Class
+ */
+
+class StreamSend {
+
+    /**
+     * Field types in StreamSend
+     *
+     * This var is assigned the field type array in the constructor
+     * and is available for reference.
+     */
+    var $streamsendFieldTypes;
+
+    /**
+     * Clase Debug Status
+     *
+     * Set to true to enable debug output
+     */
+    var $debug = false;
+
+    /**
+     * Base URL at which the StreamSend API is located
+     */
+    var $baseURL;
+
+    /**
+     * Complete URL at which the specific StreamSend API method is located
+     */
+    var $completeURL;
+
+    /**
+     * Setting for CURLOPT_SSL_VERIFYPEER
+     *
+     * Defines whether we should verify the peer's certificate
+     *
+     * Normally should be set to "true".
+     */
+    var $verifyPeer;
+
+    /**
+     * Setting for CURLOPT_RETURNTRANSFER
+     *
+     * Defines if PHP curl_exec() should return a string or
+     * display the response directly to the user's browser.
+     *
+     * In all cases this should be "true" to return rather
+     * than display the response.
+     */
+    var $returnTransfer;
+
+    /**
+     * User name to use for authentication into StreamSend API
+     *
+     * The user name and password for the StreamSend API is generated
+     * via the StreamSend partner Web site.
+     */
+    var $userName;
+
+    /**
+     * User password to use for authentication into StreamSend API
+     */
+    var $userPassword;
+
+    /**
+     * XML Request
+     *
+     * This is detail of the request sent to the StreamSend API.
+     */
+    var $xmlRequest = '';
+
+    /**
+     * Default Setting for CURLOPT_HTTPHEADER
+     *
+     * An array of HTTP header fields that is included in all requests.
+     * This is set in the GET, POST, MULTIPART case options in sendRequest().
+     */
+    var $httpHeader = false;
+
+    /**
+     * Setting for CURLOPT_CUSTOMREQUEST
+     *
+     * The HTTP method to use for a request.
+     * For StreamSend API may be: "GET", "POST", "PUT", or "DELETE"
+     */
+    var $requestMethod;
+
+    /**
+     * Curl POST data
+     *
+     * Text to supply to StreamSend in a POST operation
+     * Set to false by default to detect when data is not supplied.
+     */
+    var $postData = false;
+
+    /**
+     * Currently select StreamSend Account
+     *
+     * A StreamSend Account equates to a Gaslight Media customer.
+     * All customer lists and blasts are contained within their account.
+     */
+    var $currentAccount = NULL;
+
+    /**
+     * Int - Last Curl Return Code
+     */
+    var $curlError = NULL;
+
+    /**
+     * String - Last Curl Return String
+     */
+    var $curlErrorString = NULL;
+
+    /**
+     * Boolean - Was last API request successful
+     */
+    var $responseStatus = NULL;
+
+    /**
+     * Numeric Error Code we assign to API failures
+     */
+    var $responseError = NULL;
+
+    /**
+     * Text error message describing $responseError
+     */
+    var $responseErrorText = NULL;
+
+    /**
+     * API Server HTTP Response Code
+     *
+     * The response HTTP response code from the last request to the StreamSend server.
+     * (i.e. 200, 404, ...)
+     */
+    var $responseHTTPStatus = NULL;
+
+    /**
+     * Text describing HTTP Response code
+     */
+    var $responseHTTPStatusText = NULL;
+
+    /**
+     * API XML Response
+     *
+     * The raw XML response from the last API request to the StreamSend server.
+     * $noXMLExpected tells the sendRequest() method to no bother trying to
+     * parse the response from StreamSend as XML.
+     */
+    var $response = NULL;
+    var $noXMLExpected = false;
+
+    /**
+     * Return headers in response from curl.
+     *
+     * If this is true, curl will include the response headers in the response.
+     */
+    var $returnHeaders = false;
+
+    /**
+     * Destination for parsed header data returned in a response when $returnHeaders is true
+     */
+    var $parsedHeaders = false;
+
+    /**
+     * Parsed API Response Data
+     *
+     * This is an array containing the parsed data from the XML response.
+     */
+    var $responseData = NULL;
+
+    /**
+     * Display a debug message if debug is enabled
+     */
+    var $debugBuffer = '';
+
+    function debug($message)
+    {
+        if ($this->debug)
+            $this->debugBuffer .= '<p><b>StreamSend API Debug: </b>'.$message.'<br>';
+    }
+
+    /**
+     * Parse headers into a key/value array
+     *
+     */
+    function parseHeaders( $header )
+    {
+        $retVal = array();
+
+        $fields = explode("\r\n", preg_replace('/\x0D\x0A[\x09\x20]+/', ' ', $header));
+
+        foreach( $fields as $field ) {
+
+            if( preg_match('/([^:]+): (.+)/m', $field, $match) ) {
+
+                $match[1] = preg_replace('/(?<=^|[\x09\x20\x2D])./e', 'strtoupper("\0")', strtolower(trim($match[1])));
+
+                if( isset($retVal[$match[1]]) ) {
+                    $retVal[$match[1]] = array($retVal[$match[1]], $match[2]);
+                } else {
+                    $retVal[$match[1]] = trim($match[2]);
+                }
+            }
+        }
+        return $retVal;
+    }
+
+    /** StreamSend
+     *  Class construtor.  Set up the default variables for the class.
+     *
+     *  May be called with optional $debug parameter to set debug right away.
+     */
+    function StreamSend($base_url, $user_name, $user_password, $debug = false )
+    {
+        global $streamsendFieldTypes;
+
+        $this->baseURL          = $base_url;
+        $this->verifyPeer       = true;
+        $this->returnTransfer   = true;
+        $this->userName         = $user_name;
+        $this->userPassword     = $user_password;
+        $this->debug            = $debug;
+        $this->streamsendFieldTypes = $streamsendFieldTypes;
+
+        $this->debug("StreamSend() Constructor called with: <br>
+            URL: $base_url<br>
+            User Name: $user_name<br>
+            User Password: $user_password<br>");
+    }
+
+    /**
+     * Sets and API error code along with the corresponding error description.
+     *
+     * If the error code is anything but 0 (successful), it must be a failure
+     */
+    function setAPIError($error)
+    {
+        global $streamsendErrorMessage;
+
+        $this->responseError = $error;
+        $this->responseErrorText = $streamsendErrorMessage[$error];
+        $this->responseStatus = ( $error > 0 );
+    }
+
+    function sendRequest($method, $uri )
+    {
+        global $HTTPResponseCodeText;
+
+        $this->debug("sendRequest() called with: <br>$uri");
+
+        /** Build complete URL to specific StreamSend API method */
+        $this->completeURL = $this->baseURL.'/'.$uri;
+
+        /** Create curl object */
+        $c = curl_init( );
+
+        /** Setup Curl Parameters */
+        curl_setopt($c, CURLOPT_URL,                $this->completeURL);
+        curl_setopt($c, CURLOPT_SSL_VERIFYPEER,     $this->verifyPeer);
+        curl_setopt($c, CURLOPT_RETURNTRANSFER,     $this->returnTransfer);
+        curl_setopt($c, CURLOPT_USERPWD,            $this->userName.':'.$this->userPassword);
+        curl_setopt($c, CURLOPT_POST,               false);
+        curl_setopt($c, CURLOPT_POSTFIELDS,         NULL);
+
+        /** set request method */
+        switch ($method) {
+         case 'PUT' :
+             $this->httpHeader = array (
+                    'Accept: application/xml',       # any data returned should be XML
+                    'Content-Type: application/xml'  # any data we send will be XML
+                );
+                curl_setopt($c, CURLOPT_HTTPHEADER,         $this->httpHeader);
+                curl_setopt($c, CURLOPT_CUSTOMREQUEST, 'PUT');
+                curl_setopt($c, CURLOPT_POST,            true);
+
+                /** Check for post data */
+                if( !$this->postData ) {
+                    $this->setAPIError(STREAMSEND_API_ERROR_POSTDATA_NOT_SUPPLIED);
+                    return(false);
+                }
+
+                /** Provide the post data */
+                curl_setopt($c, CURLOPT_POSTFIELDS, $this->postData);
+             break;
+            case 'GET':
+                $this->httpHeader = array (
+                    'Accept: application/xml',       # any data returned should be XML
+                    'Content-Type: application/xml'  # any data we send will be XML
+                );
+                curl_setopt($c, CURLOPT_HTTPHEADER,         $this->httpHeader);
+                curl_setopt($c, CURLOPT_HTTPGET,            true);
+
+                break;
+
+            case 'DELETE':
+                $this->httpHeader = array (
+                    'Accept: application/xml'        # any data returned should be XML
+                );
+                curl_setopt($c, CURLOPT_HTTPHEADER,         $this->httpHeader);
+                curl_setopt($c, CURLOPT_CUSTOMREQUEST,      'DELETE');
+
+                break;
+
+            case 'POST':
+
+                $this->httpHeader = array (
+                    'Accept: application/xml',       # any data returned should be XML
+                    'Content-Type: application/xml'  # any data we send will be XML
+                );
+                curl_setopt($c, CURLOPT_HTTPHEADER,         $this->httpHeader);
+                curl_setopt($c, CURLOPT_POST,               true);
+
+                /** Check for post data */
+                if( !$this->postData ) {
+                    $this->setAPIError(STREAMSEND_API_ERROR_POSTDATA_NOT_SUPPLIED);
+                    return(false);
+                }
+
+                /** Provide the post data */
+                curl_setopt($c, CURLOPT_POSTFIELDS, $this->postData);
+                break;
+
+            case 'MULTIPART':
+                /** This method sends multipart/form-data for uploading files */
+
+                $this->httpHeader = array (
+                    'Accept: application/xml',       # any data returned should be XML
+                );
+                curl_setopt($c, CURLOPT_HTTPHEADER,         $this->httpHeader);
+                curl_setopt($c, CURLOPT_POST,               true);
+
+                /** Check for post data */
+                if( !$this->postData ) {
+                    $this->setAPIError(STREAMSEND_API_ERROR_POSTDATA_NOT_SUPPLIED);
+                    return(false);
+                }
+
+                /** Provide the post data */
+                curl_setopt($c, CURLOPT_POSTFIELDS, $this->postData);
+                break;
+
+            default:
+                curl_setopt($c, CURLOPT_CUSTOMREQUEST, $method);
+                break;
+        }
+
+        /** Clear all response data */
+        $this->setAPIError(STREAMSEND_API_ERROR_NONE);
+        $this->responseHTTPStatus   = NULL;
+        $this->response             = NULL;
+        $this->responseData         = NULL;
+
+        /** If requested, include the returned headers in the respone output */
+        if( $this->returnHeaders )
+            curl_setopt($c, CURLOPT_HEADER, true);
+
+        /**
+         * Send the request to the StreamSend server.
+         *
+         * If there's a complete failure to talk with it, return false.
+         */
+        if (!($this->response = curl_exec($c))) {
+            $this->setAPIError(STREAMSEND_API_ERROR_NO_RESPONSE);
+            $this->curlError = curl_errno($c);
+            $this->curlErrorString = curl_error($c);
+            $this->responseErrorText = curl_error($c);
+            $this->debug('sendRequest() CURL Error: <br>'.$this->responseErrorText);
+            return(clone $this);
+        }
+
+        $this->debug('sendRequest() Call to curl_exec() completed<br>Full Response<br /><table border="1"><tr><td><pre>'
+                    ."\n".htmlentities($this->response)."\n"
+                    .'</pre></td></tr></table>');
+
+
+        /**
+         * We received some kind of response from the StreamSend server.
+         *
+         * Save the returned HTTP result status (i.e. 200, 404, etc)
+         */
+        $this->responseHTTPStatus = curl_getinfo($c, CURLINFO_HTTP_CODE);
+        $this->debug('sendRequest() HTTP response code returned: '.$this->responseHTTPStatus);
+
+        /**
+         * Check for if the HTTP result status indicates a problem
+         */
+        $this->responseHTTPStatusText = $HTTPResponseCodeText[$this->responseHTTPStatus];
+        if ($this->responseHTTPStatus > 299) {
+            $this->debug('sendRequest() Received unexpected HTTP response status ('.$this->responseHTTPStatus.').<br>'.$this->responseHTTPStatusText);
+            $this->setAPIError(STREAMSEND_API_ERROR_HTTP_RESPONSE);
+        }
+
+        /**
+         * If we have a failed HTTP reponse then return now
+         */
+        if ($this->responseError > 0) {
+            return( $this->responseError );
+        }
+
+        /**
+         * If there's no data returned, assume that's what was expected
+         * and return.
+         */
+        $response = trim($this->response);
+        if (empty($response)) {
+            $this->debug('sendRequest() No response provided. Assuming it was not expected. Returning.');
+            $this->setAPIError(STREAMSEND_API_ERROR_NONE);
+            return( $this->responseError );
+        }
+
+        /**
+         * If return headers were requested, parse them out into an array
+         */
+        if( $this->returnHeaders )
+            $this->parsedHeaders = $this->parseHeaders($response);
+
+        /** If no XML is expected, return now */
+        if( $this->noXMLExpected )
+            return $this->responseError;
+
+        /**
+         * Parse the XML response to a data array.
+         */
+        $this->debug('sendRequest() Have text of a response. Trying to parse XML.');
+        try {
+            $this->responseData = new SimpleXMLElement($response);
+        } catch (Exception $e) {
+            $this->setAPIError(STREAMSEND_API_ERROR_BAD_XML);
+            return( $this->responseError );
+        }
+        $this->setAPIError(STREAMSEND_API_ERROR_NONE);
+        $this->debug('sendRequest() Result of parsed XML<br /><table border="1"><tr><td><pre>'.htmlentities(print_r($this->responseData,true)).'</pre></td></tr></table><p>');
+
+        return( $this->responseError );
+    }
+
+    /*****************************************************************************************************
+     *
+     * STREAMSEND RESOURCE CATEGORIES
+     *
+     * The StreamSend API is divided into general categories of functionality. Below are groupings of
+     * methods intended to address GLM needs to interact with the StreamSend API under each category.
+     *
+     * System Configuration
+     *      Audiences        Top level grouping - There is an Audience ID for each account that doesn't change
+     *      Fields           Attributes that describe each contact who receives E-Mail from a blast
+     *      Fields Options   Available options for contact picklist fields
+     *      Accounts         Customer level functionality
+     *      Users            Users who have access to specific accounts
+     *
+     * Contacts and Contact Groupings
+     *      People           Individual contacts
+     *      Lists            Groups of contacts
+     *      Memberships      Links contacts to lists (subscriptions)
+     *      Filters          Means of grouping lists and filtering contacts for use in a blast
+     *
+     * E-Mail Blasts
+     *      Emails           E-Mails (newsletters) that may be sent
+     *      Blasts           An individual instance of E-Mail distribution
+     *      Bounces          Bounce reporting for a specific Blast
+     *      Unsubscribes     Unsubscribe requests resulting from a specific blast
+     *      Clicks           Instances of contacts clicking on a link in an E-Mail
+     *      Views            Instances of a contact viewing an E-Mail
+     *
+     *****************************************************************************************************/
+
+
+    /** ---------------------------------- System Configuration Methods -------------------------------- */
+
+
+    /**
+     * Audience Methods
+     *
+     * Audiences form the highest level grouping of people. Within each audience, people may be grouped
+     * into any number of lists, but audiences themselves are self-contained and mutually exclusive.
+     * Each StreamSend account has one audience, as StreamSend does not currently provide support for
+     * multiple audiences per account. This resource is provided for conceptual consistency and is
+     * limited to the index, show, and update actions.
+     *
+     * Each account had an Audience ID that doesn't change, but it's not the same for all accounts. This
+     * method can be used to determine the correct ID for an account.
+     * another request to the StreamSend API.
+     */
+
+    function audienceList()
+    {
+        $this->debug('audienceList() called');
+
+        /** This operation requires the "GET" Method */
+        if ($this->sendRequest('GET', 'audiences') === false) {
+            $this->debug('audienceList() Call to sendRequest() failed.');
+            return( false );
+        }
+
+        /**
+         * Check if the response request was successful
+         */
+        if ($this->responseError > 0) {
+            $this->debug('audienceList() Call to sendRequest() returned an error code.');
+        } else {
+            $this->debug('audienceList() Received good response from sendRequest().');
+        }
+
+        return(clone $this);
+    }
+
+    /**
+     * Fields Methods
+     *
+     * Fields are the various attributes that describe each person, e.g. first name, last name.
+     * Each account may have a unique set of fields. Be sure to maintain a standard set of
+     * fields to ensure compatibility with Gaslight Media applications.
+     */
+
+    function fieldCreate( $name, $type, $options = false, $include_blank = false, $allow_other = false )
+    {
+        global $streamsendFieldTypes;
+
+        $this->debug('fieldCreate() called to create field type: '.$streamsendFieldTypes[$type]['Name']);
+
+        /** Make sure a valid name is supplied */
+        if( ($name = trim($name)) == '') {
+            $this->debug('fieldCreate() Name not provided or invalid.');
+            return false;
+        }
+
+        /** Force type to be numerit and make sure the specified field type is valid. */
+        $type = ($type + 0);
+        if( $type == 0 || !isset($streamsendFieldTypes[$type]) ) {
+            $this->debug('fieldCreate() Field type not provided or not a valid type number.');
+            return false;
+        }
+
+        /** If the field type requires options then make sure at least one was provided */
+        if( $streamsendFieldTypes[$type]['Options'] && ( !is_array($options) || count($options) == 0 ) ) {
+            $this->debug('fieldCreate() Options not provided for field type that requires options.');
+            return false;
+        }
+
+        /**
+         * Build XML POST data string for supplied field
+         */
+        $post_data = "<field>\n";
+        $post_data .= "  <name>$name</name>\n"
+                     ."  <data-type>".$streamsendFieldTypes[$type]['Name']."</data-type>\n"
+                     ."  <include_blank>".($include_blank?'true':'false')."</include_blank>\n"
+                     ."  <allow_other>".($allow_other?'true':'false')."</allow_other>\n";
+        if( $streamsendFieldTypes[$type]['Options'] ) {
+            $post_data .= "  <options>\n";
+            foreach( $options as $opt )
+                $post_data .= "    <option>$opt</option>\n";
+            $post_data .= "  </options>\n";
+        }
+        $post_data .= "</field>\n";
+
+        $this->postData = $post_data;
+        $this->debug('fieldCreate() XML POST request data<br /><table border="1"><tr><td><pre>'
+                    ."\n".htmlentities($this->postData)."\n"
+                    .'</pre></td></tr></table>');
+
+        /** Tell sendRequest() to not bother parsing response as XML and to return response headers for parsing */
+        $this->noXMLExpected = true;
+        $this->returnHeaders = true;
+
+       /** This operation requires the "POST" Method */
+        if ($this->sendRequest('POST', 'audiences/'.STREAMSEND_AUDIENCE.'/fields') === false) {
+            $this->debug('fieldCreate() Call to sendRequest() failed.');
+            return false;
+        }
+
+        /** Be sure to reset these parameters or other functions may have trouble */
+        $this->noXMLExpected = false;
+        $this->returnHeaders = false;
+
+        /**
+         * Check if the response request was successful
+         */
+        if ($this->responseError > 0) {
+            $this->debug('fieldCreate() Call to sendRequest() returned an error code.');
+        } else {
+            $this->debug('fieldCreate() Received good response from sendRequest().');
+        }
+
+        /** Did we get a 422 response? If so, it could be that the field already exists */
+        if ($this->responseHTTPStatus != '201' ) {
+            $this->debug('fieldCreate() StreamSend returned a "422 Unprocessable Entry" response. Does the field already exist?');
+            return false;
+        }
+
+        /** We should have received a 201 response */
+        if ($this->responseHTTPStatus != '201' ) {
+            $this->debug('fieldCreate() StreamSend did not return a "201 Created" response.');
+            return false;
+        }
+
+        /** Check for the field ID in the "Location:" header line */
+        if( !isset($this->parsedHeaders['Location']) ) {
+            $this->debug('fieldCreate() StreamSend did not return a "Location:" header with the field ID.');
+            return false;
+        }
+
+        /** Get field ID from "Location" header and force it to a number, then check it's not 0 */
+        if( ($field_id = substr( strrchr( $this->parsedHeaders['Location'], '/' ), 1 ) + 0) == 0 ) {
+            $this->debug('fieldCreate() StreamSend did not return a valid field ID. ('.$id.')');
+            return false;
+        }
+
+        $this->debug('fieldCreate() Have a valid field ID. ('.$field_id.')');
+
+        $this->responseData->fieldID = $field_id;
+
+        return clone $this;
+
+    }
+
+
+    function fieldsList()
+    {
+        $this->debug('fieldsList() called');
+
+       /** This operation requires the "GET" Method */
+        if ($this->sendRequest('GET', 'audiences/'.STREAMSEND_AUDIENCE.'/fields') === false) {
+            $this->debug('fieldsList() Call to sendRequest() failed.');
+            return false;
+        }
+
+        /**
+         * Check if the response request was successful
+         */
+        if ($this->responseError > 0) {
+            $this->debug('fieldsList() Call to sendRequest() returned an error code.');
+        } else {
+            $this->debug('fieldsList() Received good response from sendRequest().');
+        }
+
+        return(clone $this);
+    }
+
+    /**
+     * Field Get
+     *
+     * Fields are the various attributes that describe each person, e.g. first name, last name.
+     * Each account may have a unique set of fields. Be sure to maintain a standard set of
+     * fields to ensure compatibility with Gaslight Media applications.
+     */
+
+    function fieldGet( $id )
+    {
+        $this->debug('fieldGet() called');
+
+       /** This operation requires the "GET" Method */
+        if ($this->sendRequest('GET', 'audiences/'.STREAMSEND_AUDIENCE.'/fields/'.$id) === false) {
+            $this->debug('fieldGet() Call to sendRequest() failed.');
+            return false;
+        }
+
+        /**
+         * Check if the response request was successful
+         */
+        if ($this->responseError > 0) {
+            $this->debug('fieldGet() Call to sendRequest() returned an error code.');
+        } else {
+            $this->debug('fieldGet() Received good response from sendRequest().');
+        }
+
+        return(clone $this);
+    }
+
+    function fieldOptionCreate($fieldId, $name)
+    {
+        global $streamsendFieldTypes;
+
+        $this->debug('fieldOptionCreate() called to create field type: '.$streamsendFieldTypes[$type]['Name']);
+
+        /** Make sure a valid name is supplied */
+        if( ($name = trim($name)) == '') {
+            $this->debug('fieldOptionCreate() Name not provided or invalid.');
+            return false;
+        }
+
+        /** Force fieldId to be numeric and make sure the specified field type is valid. */
+        $fieldId = ($fieldId + 0);
+        if( $fieldId == 0 || !is_numeric($fieldId) ) {
+            $this->debug('fieldOptionCreate() FieldId not provided or not a number.');
+            return false;
+        }
+
+        /**
+         * Build XML POST data string for supplied field
+         */
+        $post_data = "<option>\n";
+        $post_data .= "  <name>$name</name>\n";
+        $post_data .= "</option>\n";
+
+        $this->postData = $post_data;
+        $this->debug('fieldOptionCreate() XML POST request data<br /><table border="1"><tr><td><pre>'
+                    ."\n".htmlentities($this->postData)."\n"
+                    .'</pre></td></tr></table>');
+
+       /** Tell sendRequest() to not bother parsing response as XML and to return response headers for parsing */
+        $this->noXMLExpected = true;
+        $this->returnHeaders = true;
+
+       /** This operation requires the "POST" Method */
+        $postUrl = "audiences/".STREAMSEND_AUDIENCE."/fields/{$fieldId}/options";
+        if ($this->sendRequest('POST', $postUrl) === false) {
+            $this->debug('fieldOptionCreate() Call to sendRequest() failed.');
+            return false;
+        }
+
+        /** Be sure to reset these parameters or other functions may have trouble */
+        $this->noXMLExpected = false;
+        $this->returnHeaders = false;
+
+        /**
+         * Check if the response request was successful
+         */
+        if ($this->responseError > 0) {
+            $this->debug('fieldOptionCreate() Call to sendRequest() returned an error code.');
+        } else {
+            $this->debug('fieldOptionCreate() Received good response from sendRequest().');
+        }
+
+        /** Did we get a 404 response? */
+        if ($this->responseHTTPStatus == '404' ) {
+            $this->debug('fieldOptionCreate() StreamSend returned a "404 Page Not Found" response.');
+            return false;
+        }
+
+        /** Did we get a 422 response? If so, it could be that the field already exists */
+        if ($this->responseHTTPStatus == '422' ) {
+            $this->debug('fieldOptionCreate() StreamSend returned a "422 Unprocessable Entry" response. Does the field already exist?');
+            return false;
+        }
+
+        /** We should have received a 201 response */
+        if ($this->responseHTTPStatus != '201' ) {
+            $this->debug('fieldOptionCreate() StreamSend did not return a "201 Created" response.');
+            return false;
+        }
+
+        /** Check for the field ID in the "Location:" header line */
+        if( !isset($this->parsedHeaders['Location']) ) {
+            $this->debug('fieldOptionCreate() StreamSend did not return a "Location:" header with the field ID.');
+            return false;
+        }
+
+        /** Get field ID from "Location" header and force it to a number, then check it's not 0 */
+        if( ($optionId = substr( strrchr( $this->parsedHeaders['Location'], '/' ), 1 ) + 0) == 0 ) {
+            $this->debug('fieldOptionCreate() StreamSend did not return a valid option ID. ('.$id.')');
+            return false;
+        }
+
+        $this->debug('fieldOptionCreate() Have a valid field ID. ('.$optionId.')');
+
+        $this->responseData->optionID = $optionId;
+
+        return clone $this;
+    }
+
+    function fieldOptionDelete($fieldId, $optionId)
+    {
+        $this->debug('fieldOptionDelete() Called to delete Field Option '.$optionId );
+
+        /** Force fieldId to be numeric and make sure the specified field type is valid. */
+        $fieldId = ($fieldId + 0);
+        if( $fieldId == 0 || !is_numeric($fieldId) ) {
+            $this->debug('fieldOptionDelete() FieldId not provided or not a number.');
+            return false;
+        }
+
+        /** Force optionId to be numeric and make sure the specified field type is valid. */
+        $optionId = ($optionId + 0);
+        if( $optionId == 0 || !is_numeric($optionId) ) {
+            $this->debug('fieldOptionDelete() OptionId not provided or not a number.');
+            return false;
+        }
+
+        /** Expecting only an HTTP response code, so no XML parsing is required */
+        $this->noXMLExpected = true;
+
+        /** This operation requires the "DELETE" Method */
+        $deleteUrl = "audiences/"
+            . STREAMSEND_AUDIENCE
+            . "/fields/{$fieldId}/options/{$optionId}/";
+        if ($this->sendRequest('DELETE', $deleteUrl) === false) {
+            $this->debug('fieldOptionDelete() Call to sendRequest() failed.');
+            return false;
+        }
+
+        /** Be sure to reset these parameters or other functions may have trouble */
+        $this->noXMLExpected = false;
+
+        /** Process the two normally expected response codes related to deleting a contact */
+        switch( $this->responseHTTPStatus ) {
+
+            case '200':
+                $this->debug('fieldOptionDelete() Call to sendRequest() returned a 200 OK. Contact should be deleted.');
+                return clone $this;
+                break;
+
+            case '423':
+                $this->debug('fieldOptionDelete() Call to sendRequest() returned a 423 LOCKED. Contact cannot be deleted at this time.');
+                return clone $this;
+                break;
+
+        }
+
+        /** We get here because an unexpected HTTP response code was recieved */
+        $this->debug('fieldOptionDelete() Call to sendRequest() returned an unexpected code. Contact may not be deleted.');
+        return false;
+    }
+
+    /**
+     * Fields Options Methods
+     *
+     * Those fields that utilize enumerated values, e.g. Select, Radio, Checkbox, will present
+     * each person with a series of options from which they may choose. The available options
+     * for a given field constitute an ordered set onto which new options will be appended.
+     */
+
+    function fieldOptionsGet( $id )
+    {
+        $this->debug('fieldOptionsGet() called');
+
+       /** This operation requires the "GET" Method */
+        if ($this->sendRequest('GET', 'audiences/'.STREAMSEND_AUDIENCE.'/fields/'.$id.'/options') === false) {
+            $this->debug('fieldOptionsGet() Call to sendRequest() failed.');
+            return false;
+        }
+
+        /**
+         * Check if the response request was successful
+         */
+        if ($this->responseError > 0) {
+            $this->debug('fieldOptionsGet() Call to sendRequest() returned an error code.');
+        } else {
+            $this->debug('fieldOptionsGet() Received good response from sendRequest().');
+        }
+
+        return(clone $this);
+    }
+
+    /**
+     * To update a given option for a field.
+     *
+     * @global type $streamsendFieldTypes (why we still using globals?)
+     *
+     * @param type $fieldId
+     * @param type $optionId
+     * @param type $name
+     *
+     * @return type
+     */
+    function fieldOptionUpdate($fieldId, $optionId, $name)
+    {
+        global $streamsendFieldTypes;
+
+        $this->debug('fieldOptionUpdate() called to create field type: '.$streamsendFieldTypes[$type]['Name']);
+
+        /** Make sure a valid name is supplied */
+        if( ($name = trim($name)) == '') {
+            $this->debug('fieldOptionUpdate() Name not provided or invalid.');
+            return false;
+        }
+
+        /** Force fieldId to be numeric and make sure the specified field type is valid. */
+        $fieldId = ($fieldId + 0);
+        if( $fieldId == 0 || !is_numeric($fieldId) ) {
+            $this->debug('fieldOptionUpdate() FieldId not provided or not a number.');
+            return false;
+        }
+
+        /** Force optionId to be numeric and make sure the specified field type is valid. */
+        $optionId = ($optionId + 0);
+        if( $optionId == 0 || !is_numeric($optionId) ) {
+            $this->debug('fieldOptionUpdate() OptionId not provided or not a number.');
+            return false;
+        }
+
+        /**
+         * Build XML POST data string for supplied field
+         */
+        $post_data = "<option>\n";
+        $post_data .= "  <name>$name</name>\n";
+        $post_data .= "</option>\n";
+
+        $this->postData = $post_data;
+        $this->debug('fieldOptionUpdate() XML POST request data<br /><table border="1"><tr><td><pre>'
+                    ."\n".htmlentities($this->postData)."\n"
+                    .'</pre></td></tr></table>');
+
+       /** This operation requires the "POST" Method */
+        $putUrl = "audiences/".STREAMSEND_AUDIENCE."/fields/{$fieldId}/options/{$optionId}/";
+        if ($this->sendRequest('PUT', $putUrl) === false) {
+            $this->debug('fieldOptionUpdate() Call to sendRequest() failed.');
+            return false;
+        }
+
+        /**
+         * Check if the response request was successful
+         */
+        if ($this->responseError > 0) {
+            $this->debug('fieldOptionUpdate() Call to sendRequest() returned an error code.');
+        } else {
+            $this->debug('fieldOptionUpdate() Received good response from sendRequest().');
+        }
+
+        return clone $this;
+    }
+
+    function fieldsGetAll()
+    {
+        $this->debug('fieldsGetAll() called');
+
+        if (!($fields = $this->fieldsList($field_id)) || $fields->responseStatus) {
+            $this->debug('fieldsGetAll() Call to fieldGet() failed.');
+            return false;
+        }
+
+        $this->fieldsData = array();
+
+        foreach ($fields->responseData->field as $field) {
+
+            $f_id = (int) $field->id;
+
+            /** Add the field data to the plain fieldsData array */
+            $this->fieldsData[$f_id] = array(
+                'name'      => ( (string) $field->name ),
+                'data-type' => ( (string) $field->{'data-type'} ),
+                'include-blank' => ( (string) $field->{'include-blank'} ),
+                'allow-other'   => ( (string) $field->{'allow-other'} ),
+                'slug'          => ( (string) $field->slug )
+            );
+
+
+            switch( $field->{'data-type'} ) {
+
+                /** These fields have no additional data */
+                case 'Text Field':
+                case 'Text Area':
+                case 'Date/Time':
+                case 'Date':
+                case 'Time':
+                    $this->fieldsData[$f_id]['options'] = false;
+                    break;
+
+                /** These fields have options associated with them */
+                case 'Select':
+                case 'Radio':
+                case 'Checkbox':
+
+                    /** Get the options for this field - Don't fail completely here */
+                    if (!($options = $this->fieldOptionsGet($f_id)) || $options->responseStatus) {
+                        $this->debug('fieldsGetAll() Call to fieldOptionsGet() failed.');
+                        $fields_data[$f_id]['Options'] = false;
+                        break;
+                    }
+
+
+                    //$fields_data
+                    foreach( $options->responseData->option as $option ) {
+                        $this->fieldsData[$f_id]['options'][(int) $option->id] = (string) $option->name;
+                    }
+
+                    break;
+            }
+
+        }
+
+        return(clone $this);
+    }
+
+    /**
+     * Accounts Methods
+     *
+     * Resellers of StreamSend may create additional accounts underneath their parent account.
+     * The quota for child accounts constitutes a subset of the quota for the parent reseller account.
+     *
+     * Gaslight Media will have one account per customer or Web site. In some cases, customers may
+     * want to conduct E-Mail blasts for multiple related Web sites.
+     */
+    function accountList()
+    {
+     $this->debug('accountList() called');
+
+        /**
+         * This operation requires the "GET" Method
+         * A false indicates that the server didn't respond at all.
+         */
+        if ($this->sendRequest('GET', 'accounts') === false) {
+            $this->debug('accountList() Call to sendRequest() failed.');
+            return false;
+        }
+
+        /**
+         * Check if the response request was successful
+         */
+        if ($this->responseError > 0) {
+            $this->debug('accountList() Call to sendRequest() returned an error code.');
+        } else {
+            $this->debug('accountList() Received good response from sendRequest().');
+        }
+
+        return(clone $this);
+    }
+
+    function accountGet( $id )
+    {
+        $this->debug('acccountGet() called');
+
+       /** This operation requires the "GET" Method */
+        if ($this->sendRequest('GET', 'accounts/'.$id) === false) {
+            $this->debug('accountGet() Call to sendRequest() failed.');
+            return false;
+        }
+
+        /**
+         * Check if the response request was successful
+         */
+        if ($this->responseError > 0) {
+            $this->debug('accountGet() Call to sendRequest() returned an error code.');
+        } else {
+            $this->debug('accoun tGet() Received good response from sendRequest().');
+        }
+
+        /** Stuff the results into a logical and consistent place to use by the calling program */
+        $this->contact = $this->responseData;
+
+        return clone $this;
+    }
+
+
+
+    /**
+     * Destroy Account - USE WITH CAUTION!!!
+     * Seems to take the StreamSend servers some time to complete and may
+     * cause their systems to be unresponsive or require killing the cookie for
+     * the Web login to their account management interface and loging in again.
+     *
+     * This may have to be used to remove an account since it's not possible as far
+     * as I can tell to change the name on a sub-account and no way to delete it from
+     * the Web interface. This does totally remove it.
+     *
+     * @return unknown_type
+     */
+    function accountDestroy( $id )
+    {
+        $this->debug('accountDestroy() called');
+
+        /**
+         * This operation requires the "GET" Method
+         * A false indicates that the server didn't respond at all.
+         */
+        if ($this->sendRequest('DELETE', 'accounts/'.$id) === false) {
+            $this->debug('accountDestroy() Call to sendRequest() failed.');
+            return false;
+        }
+
+        /**
+         * Check if the response request was successful
+         */
+        if ($this->responseError > 0) {
+            $this->debug('accountDestroy() Call to sendRequest() returned an error code.');
+        } else {
+            $this->debug('accountDestroy() Received good response from sendRequest().');
+        }
+
+        return(clone $this);
+    }
+
+
+
+
+
+    /**
+     * User Methods
+     *
+     * One may manage the users that have access to one�s account or, if one is a
+     * reseller, the users that have access to one�s resold accounts.
+     *
+     * Gaslight Media will normally have at least one user per account to permit
+     * the customer access to the StreamSend interface.
+     */
+
+    function userList()
+    {
+        $this->debug('userList() called');
+
+        /** This operation requires the "GET" Method */
+        if ($this->sendRequest('GET', 'users') === false) {
+            $this->debug('userList() Call to sendRequest() failed.');
+            return false;
+        }
+
+        /**
+         * Check if the response request was successful
+         */
+        if ($this->responseError > 0) {
+            $this->debug('userList() Call to sendRequest() returned an error code.');
+        } else {
+            $this->debug('userList() Received good response from sendRequest().');
+        }
+
+        return(clone $this);
+    }
+
+
+    /** ------------------------------ Contacts and Contact Grouping Methods --------------------------- */
+
+    /**
+     * People (contacts) Methods
+     *
+     * People are the recipients of mailings.
+     */
+
+    function contactList( $page = 1, $pagesize = 100 )
+    {
+        $this->debug('contactList() called');
+
+        /** Check for and limit to the maximum page size **/
+        if ($pagesize > STREAMSEND_MAX_PAGE_SIZE) {
+            $pagesize = STREAMSEND_MAX_PAGE_SIZE;
+            $this->debug('contactList() Maximum page size exceeded. Page size set to StreamSend maximum.');
+        }
+
+       /** This operation requires the "GET" Method */
+        if ($this->sendRequest('GET', 'audiences/'.STREAMSEND_AUDIENCE.'/people?page='.$page.'&per_page='.$pagesize) === false) {
+            $this->debug('contactList() Call to sendRequest() failed.');
+            return false;
+        }
+
+        /**
+         * Check if the response request was successful
+         */
+        if ($this->responseError > 0) {
+            $this->debug('contactList() Call to sendRequest() returned an error code.');
+        } else {
+            $this->debug('contactList() Received good response from sendRequest().');
+        }
+
+        /** Place list of "persons" in results as "contacts" */
+        $this->contacts = $contacts->responseData->person;
+
+        return clone $this;
+    }
+
+    function contactSearch( $email )
+    {
+        $this->debug('contactSearch() called');
+
+       /** This operation requires the "GET" Method */
+        if ($this->sendRequest('GET', 'audiences/'.STREAMSEND_AUDIENCE.'/people?email_address='.$email) === false) {
+            $this->debug('contactSearch() Call to sendRequest() failed.');
+            return false;
+        }
+
+        /**
+         * Check if the response request was successful
+         */
+        if ($this->responseError > 0) {
+            $this->debug('contactSearch() Call to sendRequest() returned an error code.');
+        } else {
+            $this->debug('contactSearch() Received good response from sendRequest().');
+        }
+
+        /**
+         * We now need to see if we actually got any contact data back
+         */
+        $this->contact = false;
+        foreach ($this->responseData->person as $contact) {
+
+            /** Check to see if there's more than one contact returned, which would be an error */
+            if( $this->contact != false ) {
+                $this->debug('contactSearch() Received mulitple contact results. This should not happen.');
+                return false;
+            }
+
+            /** Stuff the results into a logical and consistent place to use by the calling program */
+            $this->contact = $contact;
+        }
+
+        return clone $this;
+    }
+
+    function contactGet( $id )
+    {
+        $this->debug('contactGet() called');
+
+       /** This operation requires the "GET" Method */
+        if ($this->sendRequest('GET', 'audiences/'.STREAMSEND_AUDIENCE.'/people/'.$id) === false) {
+            $this->debug('contactGet() Call to sendRequest() failed.');
+            return false;
+        }
+
+        /**
+         * Check if the response request was successful
+         */
+        if ($this->responseError > 0) {
+            $this->debug('contactGet() Call to sendRequest() returned an error code.');
+        } else {
+            $this->debug('contactGet() Received good response from sendRequest().');
+        }
+
+        /** Stuff the results into a logical and consistent place to use by the calling program */
+        $this->contact = $this->responseData;
+
+        return(clone $this);
+    }
+
+    /**
+     * Create a Contact
+     *
+     * $contact is field/value array.
+     *
+     * The index of contactData is the field name slug (i.e. field name = "First Name", slug = "first_name" )
+     * Slugs may only contain lower-case alphanumeric characters and "_".
+     *
+     * Minimum required fields include: email_address
+     *
+     * See setup file for explanation on other default values.
+     */
+
+    function contactCreate( $contact,
+                            $activate = STREAMSEND_DEFAULT_ACTIVATE,
+                            $deliver = STREAMSEND_DEFAULT_DELIVER_ACTIVATION,
+                            $welcome = STREAMSEND_DEFAULT_DELIVER_WELCOME )
+    {
+        $this->debug('contactCreate() called');
+
+        /**
+         * Check for minimum contact data
+         */
+        if( !isset($contact['email-address']) || preg_match( "/^[A-Z0-9._%-]+@[A-Z0-9._%-]+\.[A-Z]{2,4}$/i", $contact['email-address'] ) == 0 ) {
+            $this->debug('contactCreate() E-Mail address not provided or not a valid E-Mail address.');
+            return false;
+        }
+
+        /**
+         * Build XML POST data string for supplied contact
+         */
+        $post_data = "<person>\n";
+        while( list($k, $v) = each($contact) ) {
+            $v = str_replace("&", "&amp;", $v);
+            $v = str_replace("'", "&apos;", $v);
+            if (strstr($v, "|")) {
+                $mSect = explode("|", $v);
+                foreach ($mSect as $multiples) {
+                    $post_data .= '  <'.trim($k).'>'.trim($multiples).'</'.trim($k).">\n";
+                }
+            } else {
+                $post_data .= '  <'.trim($k).'>'.trim($v).'</'.trim($k).">\n";
+            }
+        }
+        $post_data .= "  <activate>$activate</activate>\n"
+                     ."  <deliver-activation>$deliver</deliver-activation>\n"
+                     ."  <deliver-welcome>$welcome</deliver-welcome>\n";
+        $post_data .= "</person>\n";
+        $this->postData = $post_data;
+        $this->debug('contactCreate() XML POST request data<br /><table border="1"><tr><td><pre>'
+                    ."\n".htmlentities($this->postData)."\n"
+                    .'</pre></td></tr></table>');
+
+       /** This operation requires the "POST" Method */
+        if ($this->sendRequest('POST', 'audiences/'.STREAMSEND_AUDIENCE.'/people') === false) {
+            $this->debug('contactCreate() Call to sendRequest() failed.');
+            return false;
+        }
+
+        /**
+         * Check if the response request was successful
+         */
+        if ($this->responseError > 0) {
+            $this->debug('contactCreate() Call to sendRequest() returned an error code.');
+        } else {
+            $this->debug('contactCreate() Received good response from sendRequest().');
+        }
+
+        return clone $this;
+    }
+
+    function contactUpdate($id, $contact)
+    {
+        $this->debug('contactUpdate() called');
+
+        /**
+         * Check for minimum contact data
+         */
+        if( !isset($contact['email-address']) || preg_match( "/^[A-Z0-9._%-]+@[A-Z0-9._%-]+\.[A-Z]{2,4}$/i", $contact['email-address'] ) == 0 ) {
+            $this->debug('contactUpdate() E-Mail address not provided or not a valid E-Mail address.');
+            return false;
+        }
+
+        /**
+         * Build XML POST data string for supplied contact
+         */
+        $post_data = "<person>\n";
+        while( list($k, $v) = each($contact) ) {
+            $v = str_replace("&", "&amp;", $v);
+            $v = str_replace("'", "&apos;", $v);
+            if (strstr($v, "|")) {
+                $mSect = explode("|", $v);
+                foreach ($mSect as $multiples) {
+                    $post_data .= '  <'.trim($k).'>'.trim($multiples).'</'.trim($k).">\n";
+                }
+            } else {
+                $post_data .= '  <'.trim($k).'>'.trim($v).'</'.trim($k).">\n";
+            }
+        }
+        $post_data .= "</person>\n";
+        $this->postData = $post_data;
+        $this->debug('contactUpdate() XML POST request data<br /><table border="1"><tr><td><pre>'
+                    ."\n".htmlentities($this->postData)."\n"
+                    .'</pre></td></tr></table>');
+
+       /** This operation requires the "POST" Method */
+        if ($this->sendRequest('PUT', 'audiences/'.STREAMSEND_AUDIENCE.'/people/'.$id) === false) {
+            $this->debug('contactUpdate() Call to sendRequest() failed.');
+            return false;
+        }
+
+        /**
+         * Check if the response request was successful
+         */
+        if ($this->responseError > 0) {
+            var_dump($this->responseError);
+            $this->debug('contactUpdate() Call to sendRequest() returned an error code.');
+        } else {
+            $this->debug('contactUpdate() Received good response from sendRequest().');
+        }
+
+        return clone $this;
+    }
+
+    /**
+     * Delete a Contact
+     *
+     * Requires a valid contact E-Mail address
+     */
+    function contactDelete( $email )
+    {
+        $this->debug('contactDelete() Called to delete E-Mail address: '.$email );
+
+        /**
+         * First find the contact to get the contact ID
+         */
+        if (!($c = $this->contactSearch( $email ))) {
+            $this->debug('contactDelete() Call to sendRequest() failed.');
+            return false;
+        } elseif ($c->responseStatus){
+            $this->debug('contactDelete() Call to sendRequest returned an error code.');
+            return false;
+        }
+
+        if( $c->contact == false ) {
+            $this->debug('contactDelete() Search for specified E-Mail address failed. Does Contact exist?');
+            return false;
+        }
+
+        $id = $c->contact->id;
+        $this->debug('contactDelete() Have an ID for this contact: '.$id);
+
+        /** Expecting only an HTTP response code, so no XML parsing is required */
+        $this->noXMLExpected = true;
+
+        /** This operation requires the "DELETE" Method */
+        if ($this->sendRequest('DELETE', 'audiences/'.STREAMSEND_AUDIENCE.'/people/'.$id) === false) {
+            $this->debug('contactDelete() Call to sendRequest() failed.');
+            return false;
+        }
+
+        /** Be sure to reset these parameters or other functions may have trouble */
+        $this->noXMLExpected = false;
+
+        /** Process the two normally expected response codes related to deleting a contact */
+        switch( $this->responseHTTPStatus ) {
+
+            case '200':
+                $this->debug('uploadContacts() Call to sendRequest() returned a 200 OK. Contact should be deleted.');
+                return clone $this;
+                break;
+
+            case '423':
+                $this->debug('uploadContacts() Call to sendRequest() returned a 423 LOCKED. Contact cannot be deleted at this time.');
+                return clone $this;
+                break;
+
+        }
+
+        /** We get here because an unexpected HTTP response code was recieved */
+        $this->debug('uploadContacts() Call to sendRequest() returned an unexpected code. Contact may not be deleted.');
+        return false;
+    }
+
+
+    /**
+     * Lists Methods
+     *
+     * Lists are groups of contacts. People (contacts) may be subscribed to multiple lists.
+     */
+
+    function listList()
+    {
+        $this->debug('listList() called');
+
+        /** This operation requires the "GET" Method */
+        if ($this->sendRequest('GET', 'audiences/'.STREAMSEND_AUDIENCE.'/lists') === false) {
+            $this->debug('listList() Call to sendRequest() failed.');
+            return( false );
+        }
+
+        /**
+         * Check if the response request was successful
+         */
+        if ($this->responseError > 0) {
+            $this->debug('listList() Call to sendRequest() returned an error code.');
+        } else {
+            $this->debug('listsList() Received good response from sendRequest().');
+        }
+
+        return(clone $this);
+    }
+
+
+
+    /**
+     * Memberships (subscription) Methods
+     *
+     * Membership records track to which lists each person belongs. For any given list to
+     * which a person is subscribed, a membership record exists. The creation of a membership
+     * record linking a person and a list represents the act of joining that list. The
+     * destruction of a membership record represents the act of leaving that list.
+     */
+
+        /** Not written yet */
+
+
+    /**
+     * Filters Methods
+     *
+     * Filters, along with lists, are one of two ways that the people in your audience
+     * can be grouped and manipulated. While lists are fairly static and unchanging,
+     * filters are completely dynamic. That is, no person ever "belongs" to a filter,
+     * as they might a list. Instead they are said to "match" a filter, and only when
+     * the filter is used does a clear group of people emerge.
+     */
+
+        /** Not written yet */
+
+
+    /** -------------------------------------- E-Mail Blast Methods ------------------------------------ */
+
+
+    /**
+     * Emails Methods
+     *
+     * Emails in StreamSend are those that have been saved for later use. They consist
+     * of some combination of HTML and plain text content and may be modified at any
+     * time without affecting any blasts that have used or will be using them.
+     */
+
+        /** Not written yet */
+
+
+    /**
+     * Blasts Methods
+     *
+     * A blast is the act of sending an email to one or more people in your audience. Blasts
+     * may be sent immediately or at some specified date in the future.
+     */
+
+    function blastList()
+    {
+        $this->debug('blastList() called');
+
+        /** This operation requires the "GET" Method */
+        if ($this->sendRequest('GET', 'audiences/'.STREAMSEND_AUDIENCE.'/blasts') === false) {
+            $this->debug('blastList() Call to sendRequest() failed.');
+            return( false );
+        }
+
+        /**
+         * Check if the response request was successful
+         */
+        if ($this->responseError > 0) {
+            $this->debug('blastList() Call to sendRequest() returned an error code.');
+        } else {
+            $this->debug('blastList() Received good response from sendRequest().');
+        }
+
+        return(clone $this);
+    }
+
+    function blastGet( $id )
+    {
+        $this->debug('blastGet() called');
+
+       /** This operation requires the "GET" Method */
+        if ($this->sendRequest('GET', 'audiences/'.STREAMSEND_AUDIENCE.'/blasts/'.$id) === false) {
+            $this->debug('blastGet() Call to sendRequest() failed.');
+            return false;
+        }
+
+        /**
+         * Check if the response request was successful
+         */
+        if ($this->responseError > 0) {
+            $this->debug('blastGet() Call to sendRequest() returned an error code.');
+        } else {
+            $this->debug('blastGet() Received good response from sendRequest().');
+        }
+
+        /** Stuff the results into a logical and consistent place to use by the calling program */
+        $this->contact = $this->responseData;
+
+        return clone $this;
+    }
+
+    function monthlyStats( $month, $year )
+    {
+        global $StreamSendAccounts;
+
+        $this->debug('monthlyStats() called');
+
+        $total_accounts = $total_blasts = $total_sent = $total_delivered = $total_undelivered = $total_bounced =
+            $total_views = $total_clicks = $total_unsubscribes = $total_complaints = 0;
+
+        /**
+         * Get list of all accounts
+         */
+        if ( !($accounts = $this->accountList()) || $accounts->responseStatus ) {
+            $this->debug('monthlyStats() Unable to get account list');
+            return false;
+        }
+
+        /** Array that will contact all resulting data */
+        $account_data = array();
+
+        /**
+         * For each account
+         */
+        foreach ($accounts->responseData->account as $account) {
+
+            $a_id = (int) $account->id;
+            $account_data[$a_id] = array(
+                'name'                      => (string) $account->name,
+                'quota'                     => (int) $account->quota,
+                'soft-bounce-tolerance'     => (int) $account->{'soft-bounce-tolerance'},
+                'automated-email-address'   => (string) $account->{'automated-email-address'},
+                'owner'                     => (string) $account->owner->{'last-name'}.', '.$account->owner->{'first-name'},
+                'email-address'             => (string) $account->owner->{'email-address'},
+                'blasts'                    => 0,
+                'sent'                      => 0,
+                'delivered'                 => 0,
+                'undelivered'               => 0,
+                'bounced'                   => 0,
+                'views'                     => 0,
+                'clicks'                    => 0,
+                'unsubscribes'              => 0,
+                'complaints'                => 0
+            );
+
+            $this->debug('monthlyStats() Processing account: '.$a_id);
+
+            $total_accounts++;
+
+            /**
+             * Get all blasts for the specified month
+             */
+
+            $account_data[$a_id]['blast_detail'] = array();
+
+            /** Set the account id */
+            $this->currentAccount   = $a_id;
+            $this->userName         = $StreamSendAccounts[$a_id]['id'];
+            $this->userPassword     = $StreamSendAccounts[$a_id]['key'];
+
+            if (!($blasts = $this->blastList()) || $blasts->responseStatus ) {
+                $this->debug('monthlyStats() Unable to get blast list for account: '.$a_id);
+                $account_data[$a_id][$b_id] = false;
+            } else {
+
+                foreach ($blasts->responseData->blast as $blast) {
+
+                    /** Get the scheduled date for this blast and break it up into parts */
+                    $date = getdate(strtotime( $blast->{'scheduled-for'} ));
+
+                    /** Is this blast scheduled for the requested month */
+                    if( $date['mon'] == $month && $date['year'] == $year ) {
+
+                        $account_data[$a_id]['blasts']++;
+
+                        $b_id = (int) $blast->id;
+
+                        $account_data[$a_id]['blast_detail'][$b_id] = array(
+                            'subject'                   => (string) $blast->subject,
+                            'email-address'             => (string) $blast->from->{'email-address'},
+                            'scheduled-for'             => (string) $blast->{'scheduled-for'},
+                            'completed-at'              => (string) $blast->{'completed-at'},
+                            'status'                    => (string) $blast->status,
+                            'sent'                      => 0
+                        );
+
+                        /**
+                         * Get blast detail
+                         */
+
+                        if (!($b = $this->blastGet($b_id)) || $b->responseStatus ){
+                            $this->debug('monthlyStats() Unable to get blast list for account: '.$a_id);
+                        } else {
+
+                        $b_delivery = $b->responseData->statistics->delivery;
+                        $b_activity = $b->responseData->statistics->activity;
+
+                        $account_data[$a_id]['blast_detail'][$b_id]['delivered']           = (int) $b_delivery->delivered;
+                        $account_data[$a_id]['blast_detail'][$b_id]['undelivered']         = (int) $b_delivery->undelivered;
+                        $account_data[$a_id]['blast_detail'][$b_id]['bounced']             = (int) $b_delivery->bounced;
+                        $account_data[$a_id]['blast_detail'][$b_id]['views']               = (int) $b_activity->views;
+                        $account_data[$a_id]['blast_detail'][$b_id]['clicks']              = (int) $b_activity->clicks;
+                        $account_data[$a_id]['blast_detail'][$b_id]['unsubscribes']        = (int) $b_activity->unsubscribes;
+                        $account_data[$a_id]['blast_detail'][$b_id]['complaints']    = (int) $b_activity->complaints;
+
+                        /** Calculate total number of E-Mails sent for this blast */
+                        $sent = (int) $b_delivery->delivered + (int) $b_delivery->undelivered + (int) $b_delivery->bounced;
+                        $account_data[$a_id]['blast_detail'][$b_id]['sent'] = $sent;
+
+                        /** Add into account stats */
+                        $account_data[$a_id]['blasts']++;
+                        $account_data[$a_id]['sent']            += $sent;
+                        $account_data[$a_id]['delivered']       += (int) $b_delivery->delivered;
+                        $account_data[$a_id]['undelivered']     += (int) $b_delivery->undelivered;
+                        $account_data[$a_id]['bounced']         += (int) $b_delivery->bounced;
+                        $account_data[$a_id]['views']           += (int) $b_activity->views;
+                        $account_data[$a_id]['clicks']          += (int) $b_activity->clicks;
+                        $account_data[$a_id]['unsubscribes']    += (int) $b_activity->unsubscribes;
+                        $account_data[$a_id]['complaints']      += (int) $b_activity->complaints;
+
+                        /** Add into overall stats */
+                        $total_blasts++;
+                        $total_sent             += $sent;
+                        $total_delivered        += (int) $b_delivery->delivered;
+                        $total_undelivered      += (int) $b_delivery->undelivered;
+                        $total_bounced          += (int) $b_delivery->bounced;
+                        $total_views            += (int) $b_activity->views;
+                        $total_clicks           += (int) $b_activity->clicks;
+                        $total_unsubscribes     += (int) $b_activity->unsubscribes;
+                        $total_complaints       += (int) $b_activity->complaints;
+
+                        } // Have good blast detail
+
+                    } // Check date
+
+                } // for each blast
+
+            } // Have good list of blasts for account
+
+        } // For each account
+
+        /** Stuff the results into a logical and consistent place to use by the calling program */
+        $this->stats = array(
+            'total_blasts'          => $total_blasts,
+            'total_sent'            => $total_sent,
+            'total_delivered'       => $total_delivered,
+            'total_undelivered'     => $total_undelivered,
+            'total_bounced'         => $total_bounced,
+            'total_views'           => $total_views,
+            'total_clicks'          => $total_clicks,
+            'total_unsubscribes'    => $total_unsubscribes,
+            'total_complaints'      => $total_complaints,
+            'account_detail'        => $account_data
+        );
+
+        return clone $this;
+    }
+
+
+
+    /**
+     * Bounces Methods
+     *
+     * Bounces are those instances wherein certain people do not receive a particular blast.
+     * There are many reasons for this including an invalid email address, a full inbox, a
+     * problem with the receiving email server, etc.
+     */
+
+        /** Not written yet */
+
+
+    /**
+     * Unsubcribes Methods
+     *
+     * Unsubscribes track two instances of people unsubscribing from one�s audience: manual
+     * and complaint-based.
+     */
+
+        /** Not written yet */
+
+
+    /**
+     * Clicks Methods
+     *
+     * Clicks are those instances in which a person clicks on an external link in a particular
+     * message. Because clicks are tracked by modifying hyperlink URLs in the body of the message,
+     * clicks can only be registered for those viewing the HTML portion of the message and only
+     * for those URLs that are linked via an HTML hyperlink tag.
+     */
+
+        /** Not written yet */
+
+
+    /**
+     * Views Methods
+     *
+     * Views are those instances in which a person views a particular message. Because views are
+     * tracked by way of a tracking image embedded in the message, views can only be registered
+     * for those viewing the HTML portion of the message and for whom the viewing of images is
+     * currently enabled in his or her email client.
+     */
+
+        /** Not written yet */
+
+    /** -------------------------------------- Upload and Import Methods ------------------------------------ */
+
+
+    /**
+     * Upload Method
+     *
+     * Uploads a list of contacts and import into the contact list.
+     *
+     * $contacts is an array of contacts to upload. The array should be formatted as...
+     *
+     *  $contacts = array(
+     *      0   => array(
+     *          'email_address' => '{email}',
+     *          '{field_name}'  => '{field_value}',
+     *          '{field_name}'  => '{field_value}',
+     *          ...
+     *      ),
+     *      1   => array(
+     *          'email_address' => '{email}',
+     *          '{field_name}'  => '{field_value}',
+     *          '{field_name}'  => '{field_value}',
+     *          ...
+     *      ),
+     *      ...
+     *  );
+     *
+     * The contact data must have at least 'email_address', but can have additional
+     * fields that are setup for the account on the StreamSend side. The {field_name}
+     * for the addional field names must a "slug" for the actual name. Slugs are the
+     * field name but converted to only contain lower-case alphanumeric characters and "_".
+     *
+     * Also, all contact sub arrays must have exactly the same field names or the upload
+     * will be rejected.
+     *
+     * $reactivate re-starts the double opt-in process for the uploaded contacts that
+     * already exist in StreamSend. This is an optional parameter that defaults to false.
+     *
+     * $lists is a simple array of List ID's for Lists to which uploaded contacts should
+     * be added. This is an optional parameter.
+     *
+     */
+
+    function uploadContacts( $contacts, $reactivate = false, $lists = '' )
+    {
+        $this->debug('uploadContacts() called');
+
+        /**
+         * Compile the contacts array into a text file for upload and send it to StreamSend.
+         *
+         * This is the first step in the import process. It should result in a valid
+         * upload ID that will then be used to import the contacts.
+         */
+
+        /**
+         * Check for minimum required data
+         *
+         * Was there a list of contacts supplied?
+         */
+        if( !isset($contacts) || !is_array($contacts) || count($contacts) == 0 ) {
+            $this->debug('uploadContacts() A valid list of contacts was not provided.');
+            return false;
+        }
+
+        $this->debug('uploadContacts() '.count($contacts).' contacts provided.');
+
+        /**
+         * Get the list of valid fields for this account and convert to array with key = slug and value = id
+         */
+        if( !($fields_list = $this->fieldsList()) ) {
+            $this->debug('uploadContacts() Call to fieldsList() failed.');
+            return false;
+        }
+
+        /** Email_address is not returned in fieldsList() but is required. Curiously it doesn't have an ID like the other fields */
+        $available_fields = array( 'email_address' => 'email_address' );
+        foreach( $fields_list->responseData->field as $field ) {
+            $available_fields[(string) $field->slug] = (string) $field->id;
+        }
+
+        /**
+         * Build the text file for submission to StreamSend, "|" delimited, one contact per line.
+         * Also get the list of fields from the first contact and verify that all the rest are the same.
+         */
+        $field_names = array();
+        $upload_data = $contact_fields = $contact_data_header = $contact_data_header_sep = '';
+        $numb_contacts = $numb_fields = 0;
+        $have_email_field = false;
+
+        foreach( $contacts as $fields ) {
+
+            $field = 0;
+            $contact_data = '';
+
+            /** If this is not the first contact, make sure we have the same number of fields as the first. */
+            if( $numb_contacts > 0 && count($fields) != $numb_fields ) {
+                $this->debug('uploadContacts() Contact # '.($numb_contacts+1).' does not have the same number of fields as the first.');
+                return false;
+            }
+
+            /** For each field in this contact */
+            while( list($k, $v) = each($fields) ) {
+
+                /** If this is the first contact in the supplied array, save the field name */
+                if( $numb_contacts == 0 ) {
+                    /** Check for proper field name Slug */
+                    if( !isset($available_fields[$k]) ) {
+                        $this->debug('uploadContacts() Improper field name Slug provided: '.$k);
+                        return false;
+                    }
+
+                    /** Add this field to our array of field names */
+                    $field_names[$field] = $k;
+
+                    /** Add this field to a header that will be sent as the first line in the contact data file */
+                    $contact_data_header .= $contact_data_header_sep.$k;
+
+                    /** Check if this is the email_address field. If so, say that we have it */
+                    if( $k == 'email_address' )
+                        $have_email_field = true;
+
+                    /** Compile list of contact fields for debug output */
+                    $contact_fields .= $k."\n";
+
+                }
+                else
+                    /** Otherwise check to see if this is the expected field name */
+                    if( $field_names[$field] != $k ) {
+                        $this->debug('uploadContacts() Fields for contact # '.($numb_contacts+1).' did not match first contact.');
+                        return false;
+                    }
+
+                /** Add field to contact, use "|" separator if it's not the first field */
+                $contact_data .= ($field>0?"\t":'').$v;
+
+                $contact_data_header_sep = "\t";
+
+                $field++;
+
+            }   // for each field
+
+            /** If this is the first contact, save the number of expected fields, and set the header line for the file */
+            if( $numb_contacts == 0 ) {
+                $numb_fields = $field;
+                $upload_data = "$contact_data_header\n";
+            }
+
+            /** Add this contact to the upload data */
+            $upload_data .= "$contact_data\n";
+
+            $numb_contacts++;
+
+        } // For each Contact
+
+        /** Was the required email_address field supplied? */
+        if( !$have_email_field ) {
+            $this->debug('uploadContacts() Required "email_address" field not supplied.');
+            return false;
+        }
+        $this->debug('uploadContacts() Have required "email_address" field.');
+
+        $this->debug('uploadContacts() Contacts fields supplied...<br /><table border="1"><tr><td><pre>'
+                    ."\n".htmlentities($contact_fields)
+                    .'</pre></td></tr></table>');
+
+        $this->debug('uploadContacts() Contacts data supplied...<br /><table border="1"><tr><td><pre>'
+                    ."\n".htmlentities($upload_data)
+                    .'</pre></td></tr></table>');
+
+        /** Write the upload data to a file for Curl to send using multipart/form-data */
+        $tmpname = tempnam("/tmp", "ss_");
+        $h = fopen($tmpname, "w");
+        fwrite($h, $upload_data);
+        fclose($h);
+
+        /** Add file name of upload data to Post array  */
+        $this->postData = array( 'data' => "@$tmpname" );
+
+        /** Tell sendRequest() to not bother parsing response as XML and to return response headers for parsing */
+        $this->noXMLExpected = true;
+        $this->returnHeaders = true;
+
+        /** This operation requires the "MULTIPART" Method */
+        if ($this->sendRequest('MULTIPART', 'uploads') === false) {
+            $this->debug('uploadContacts() Contacts upload call to sendRequest() failed.');
+            return false;
+        }
+
+        /** Be sure to reset these parameters or other functions may have trouble */
+        $this->noXMLExpected = false;
+        $this->returnHeaders = false;
+
+        /** Delete the temporary file */
+        unlink($tmpname);
+
+        /** We should have received a 201 response */
+        if ($this->responseHTTPStatus != '201' ) {
+            $this->debug('uploadContacts() StreamSend did not return a "201 Created" response.');
+            return false;
+        }
+
+        /** Check for the upload ID in the "Location:" header line */
+        if( !isset($this->parsedHeaders['Location']) ) {
+            $this->debug('uploadContacts() StreamSend did not return a "Location:" header with the upload ID.');
+            return false;
+        }
+
+        /** Get upload ID from "Location" header and force it to a number, then check it's not 0 */
+        if( ($upload_id = substr( strrchr( $this->parsedHeaders['Location'], '/' ), 1 ) + 0) == 0 ) {
+            $this->debug('uploadContacts() StreamSend did not return a valid upload ID. ('.$id.')');
+            return false;
+        }
+        $this->debug('uploadContacts() Have a valid upload ID. ('.$upload_id.')');
+
+        $this->responseData->uploadID = $upload_id;
+
+        /**
+         * Tell StreamSend to import the contacts supplied in the upload above.
+         *
+         * This is the second step of the process.
+         */
+
+        /** Build XML POST data string for import - Note that fields other than email_address use the field IDs here. */
+        $post_data = "<import>\n"
+                     ."  <reactivate>".($reactivate?'true':'false')."</reactivate>\n"
+                     ."  <lists>$lists</lists>\n"
+                     ."  <upload-id>$upload_id</upload-id>\n"
+                     ."  <separator>Tab</separator>\n"
+                     ."  <columns>\n";
+        reset( $field_names );
+        foreach( $field_names as $f ) {
+            $post_data .= "    <column>".$available_fields[$f]."</column>\n";
+        }
+        $post_data .= "  </columns>\n"
+                     ."</import>";
+
+        $this->postData = $post_data;
+        $this->debug('uploadContacts() XML POST request data<br /><table border="1"><tr><td><pre>'
+                    ."\n".htmlentities($this->postData)."\n"
+                    .'</pre></td></tr></table>');
+
+        /** This operation requires the "POST" Method */
+        if ($this->sendRequest('POST', 'audiences/'.STREAMSEND_AUDIENCE.'/imports') === false) {
+            $this->debug('uploadContacts() Contact import call to sendRequest() failed.');
+            return false;
+        }
+
+        /** We should have received a 201 response */
+        if ($this->responseHTTPStatus != '201' ) {
+            $this->debug('uploadContacts() StreamSend did not return a "201 Created" response.');
+            return false;
+        }
+
+        /** Check for the upload ID in the "Location:" header line */
+        if( !isset($this->parsedHeaders['Location']) ) {
+            $this->debug('uploadContacts() StreamSend did not return a "Location:" header with import ID.');
+            return false;
+        }
+
+        /** Get upload ID from "Location" header and force it to a number, then check it's not 0 */
+        if( ($import_id = substr( strrchr( $this->parsedHeaders['Location'], '/' ), 1 ) + 0) == 0 ) {
+            $this->debug('uploadContacts() StreamSend did not return a valid upload ID. ('.$id.')');
+            return false;
+        }
+        $this->debug('uploadContacts() Have a valid import ID. ('.$import_id.')');
+
+        $this->responseData->importID = $import_id;
+
+        return clone $this;
+    }
+
+
+}
+
+?>
index 3d30e93..5dbfa86 100644 (file)
@@ -296,6 +296,9 @@ class GlmMembersAdmin_member_memberInfo extends GlmDataMemberInfo
 
             // Process submission of a member information record update
             case 'submit':
+                $thisEntry = $this->getEntry( $this->memberInfoID );
+                echo '<pre>$thisEntry: ' . print_r( $thisEntry, true ) . '</pre>';
+                exit;
 
                 // Check for new cities being submitted
                 $this->checkNewCities();