<?php

namespace PhpOffice\PhpSpreadsheet\Calculation;

use PhpOffice\PhpSpreadsheet\Cell\Cell;

class Functions
{
    const PRECISION = 8.88E-016;

    /**
     * 2 / PI.
     */
    const M_2DIVPI = 0.63661977236758134307553505349006;

    /** constants */
    const COMPATIBILITY_EXCEL = 'Excel';
    const COMPATIBILITY_GNUMERIC = 'Gnumeric';
    const COMPATIBILITY_OPENOFFICE = 'OpenOfficeCalc';
    const RETURNDATE_PHP_NUMERIC = 'P';
    const RETURNDATE_PHP_OBJECT = 'O';
    const RETURNDATE_EXCEL = 'E';

    /**
     * Compatibility mode to use for error checking and responses.
     *
     * @var string
     */
    protected static $compatibilityMode = self::COMPATIBILITY_EXCEL;

    /**
     * Data Type to use when returning date values.
     *
     * @var string
     */
    protected static $returnDateType = self::RETURNDATE_EXCEL;

    /**
     * List of error codes.
     *
     * @var array
     */
    protected static $errorCodes = [
        'null' => '#NULL!',
        'divisionbyzero' => '#DIV/0!',
        'value' => '#VALUE!',
        'reference' => '#REF!',
        'name' => '#NAME?',
        'num' => '#NUM!',
        'na' => '#N/A',
        'gettingdata' => '#GETTING_DATA',
    ];

    /**
     * Set the Compatibility Mode.
     *
     * @category Function Configuration
     *
     * @param string $compatibilityMode Compatibility Mode
     *                                                Permitted values are:
     *                                                    Functions::COMPATIBILITY_EXCEL            'Excel'
     *                                                    Functions::COMPATIBILITY_GNUMERIC        'Gnumeric'
     *                                                    Functions::COMPATIBILITY_OPENOFFICE    'OpenOfficeCalc'
     *
     * @return bool (Success or Failure)
     */
    public static function setCompatibilityMode($compatibilityMode)
    {
        if (($compatibilityMode == self::COMPATIBILITY_EXCEL) ||
            ($compatibilityMode == self::COMPATIBILITY_GNUMERIC) ||
            ($compatibilityMode == self::COMPATIBILITY_OPENOFFICE)
        ) {
            self::$compatibilityMode = $compatibilityMode;

            return true;
        }

        return false;
    }

    /**
     * Return the current Compatibility Mode.
     *
     * @category Function Configuration
     *
     * @return string Compatibility Mode
     *                            Possible Return values are:
     *                                Functions::COMPATIBILITY_EXCEL            'Excel'
     *                                Functions::COMPATIBILITY_GNUMERIC        'Gnumeric'
     *                                Functions::COMPATIBILITY_OPENOFFICE    'OpenOfficeCalc'
     */
    public static function getCompatibilityMode()
    {
        return self::$compatibilityMode;
    }

    /**
     * Set the Return Date Format used by functions that return a date/time (Excel, PHP Serialized Numeric or PHP Object).
     *
     * @category Function Configuration
     *
     * @param string $returnDateType Return Date Format
     *                                                Permitted values are:
     *                                                    Functions::RETURNDATE_PHP_NUMERIC        'P'
     *                                                    Functions::RETURNDATE_PHP_OBJECT        'O'
     *                                                    Functions::RETURNDATE_EXCEL            'E'
     *
     * @return bool Success or failure
     */
    public static function setReturnDateType($returnDateType)
    {
        if (($returnDateType == self::RETURNDATE_PHP_NUMERIC) ||
            ($returnDateType == self::RETURNDATE_PHP_OBJECT) ||
            ($returnDateType == self::RETURNDATE_EXCEL)
        ) {
            self::$returnDateType = $returnDateType;

            return true;
        }

        return false;
    }

    /**
     * Return the current Return Date Format for functions that return a date/time (Excel, PHP Serialized Numeric or PHP Object).
     *
     * @category Function Configuration
     *
     * @return string Return Date Format
     *                            Possible Return values are:
     *                                Functions::RETURNDATE_PHP_NUMERIC        'P'
     *                                Functions::RETURNDATE_PHP_OBJECT        'O'
     *                                Functions::RETURNDATE_EXCEL            'E'
     */
    public static function getReturnDateType()
    {
        return self::$returnDateType;
    }

    /**
     * DUMMY.
     *
     * @category Error Returns
     *
     * @return string #Not Yet Implemented
     */
    public static function DUMMY()
    {
        return '#Not Yet Implemented';
    }

    /**
     * DIV0.
     *
     * @category Error Returns
     *
     * @return string #Not Yet Implemented
     */
    public static function DIV0()
    {
        return self::$errorCodes['divisionbyzero'];
    }

    /**
     * NA.
     *
     * Excel Function:
     *        =NA()
     *
     * Returns the error value #N/A
     *        #N/A is the error value that means "no value is available."
     *
     * @category Logical Functions
     *
     * @return string #N/A!
     */
    public static function NA()
    {
        return self::$errorCodes['na'];
    }

    /**
     * NaN.
     *
     * Returns the error value #NUM!
     *
     * @category Error Returns
     *
     * @return string #NUM!
     */
    public static function NAN()
    {
        return self::$errorCodes['num'];
    }

    /**
     * NAME.
     *
     * Returns the error value #NAME?
     *
     * @category Error Returns
     *
     * @return string #NAME?
     */
    public static function NAME()
    {
        return self::$errorCodes['name'];
    }

    /**
     * REF.
     *
     * Returns the error value #REF!
     *
     * @category Error Returns
     *
     * @return string #REF!
     */
    public static function REF()
    {
        return self::$errorCodes['reference'];
    }

    /**
     * NULL.
     *
     * Returns the error value #NULL!
     *
     * @category Error Returns
     *
     * @return string #NULL!
     */
    public static function null()
    {
        return self::$errorCodes['null'];
    }

    /**
     * VALUE.
     *
     * Returns the error value #VALUE!
     *
     * @category Error Returns
     *
     * @return string #VALUE!
     */
    public static function VALUE()
    {
        return self::$errorCodes['value'];
    }

    public static function isMatrixValue($idx)
    {
        return (substr_count($idx, '.') <= 1) || (preg_match('/\.[A-Z]/', $idx) > 0);
    }

    public static function isValue($idx)
    {
        return substr_count($idx, '.') == 0;
    }

    public static function isCellValue($idx)
    {
        return substr_count($idx, '.') > 1;
    }

    public static function ifCondition($condition)
    {
        $condition = self::flattenSingleValue($condition);
        if (!isset($condition[0])) {
            $condition = '=""';
        }
        if (!in_array($condition[0], ['>', '<', '='])) {
            if (!is_numeric($condition)) {
                $condition = Calculation::wrapResult(strtoupper($condition));
            }

            return '=' . $condition;
        }
        preg_match('/([<>=]+)(.*)/', $condition, $matches);
        list(, $operator, $operand) = $matches;

        if (!is_numeric($operand)) {
            $operand = str_replace('"', '""', $operand);
            $operand = Calculation::wrapResult(strtoupper($operand));
        }

        return $operator . $operand;
    }

    /**
     * ERROR_TYPE.
     *
     * @param mixed $value Value to check
     *
     * @return bool
     */
    public static function errorType($value = '')
    {
        $value = self::flattenSingleValue($value);

        $i = 1;
        foreach (self::$errorCodes as $errorCode) {
            if ($value === $errorCode) {
                return $i;
            }
            ++$i;
        }

        return self::NA();
    }

    /**
     * IS_BLANK.
     *
     * @param mixed $value Value to check
     *
     * @return bool
     */
    public static function isBlank($value = null)
    {
        if ($value !== null) {
            $value = self::flattenSingleValue($value);
        }

        return $value === null;
    }

    /**
     * IS_ERR.
     *
     * @param mixed $value Value to check
     *
     * @return bool
     */
    public static function isErr($value = '')
    {
        $value = self::flattenSingleValue($value);

        return self::isError($value) && (!self::isNa(($value)));
    }

    /**
     * IS_ERROR.
     *
     * @param mixed $value Value to check
     *
     * @return bool
     */
    public static function isError($value = '')
    {
        $value = self::flattenSingleValue($value);

        if (!is_string($value)) {
            return false;
        }

        return in_array($value, array_values(self::$errorCodes));
    }

    /**
     * IS_NA.
     *
     * @param mixed $value Value to check
     *
     * @return bool
     */
    public static function isNa($value = '')
    {
        $value = self::flattenSingleValue($value);

        return $value === self::NA();
    }

    /**
     * IS_EVEN.
     *
     * @param mixed $value Value to check
     *
     * @return bool|string
     */
    public static function isEven($value = null)
    {
        $value = self::flattenSingleValue($value);

        if ($value === null) {
            return self::NAME();
        } elseif ((is_bool($value)) || ((is_string($value)) && (!is_numeric($value)))) {
            return self::VALUE();
        }

        return $value % 2 == 0;
    }

    /**
     * IS_ODD.
     *
     * @param mixed $value Value to check
     *
     * @return bool|string
     */
    public static function isOdd($value = null)
    {
        $value = self::flattenSingleValue($value);

        if ($value === null) {
            return self::NAME();
        } elseif ((is_bool($value)) || ((is_string($value)) && (!is_numeric($value)))) {
            return self::VALUE();
        }

        return abs($value) % 2 == 1;
    }

    /**
     * IS_NUMBER.
     *
     * @param mixed $value Value to check
     *
     * @return bool
     */
    public static function isNumber($value = null)
    {
        $value = self::flattenSingleValue($value);

        if (is_string($value)) {
            return false;
        }

        return is_numeric($value);
    }

    /**
     * IS_LOGICAL.
     *
     * @param mixed $value Value to check
     *
     * @return bool
     */
    public static function isLogical($value = null)
    {
        $value = self::flattenSingleValue($value);

        return is_bool($value);
    }

    /**
     * IS_TEXT.
     *
     * @param mixed $value Value to check
     *
     * @return bool
     */
    public static function isText($value = null)
    {
        $value = self::flattenSingleValue($value);

        return is_string($value) && !self::isError($value);
    }

    /**
     * IS_NONTEXT.
     *
     * @param mixed $value Value to check
     *
     * @return bool
     */
    public static function isNonText($value = null)
    {
        return !self::isText($value);
    }

    /**
     * N.
     *
     * Returns a value converted to a number
     *
     * @param null|mixed $value The value you want converted
     *
     * @return number N converts values listed in the following table
     *        If value is or refers to N returns
     *        A number            That number
     *        A date                The serial number of that date
     *        TRUE                1
     *        FALSE                0
     *        An error value        The error value
     *        Anything else        0
     */
    public static function n($value = null)
    {
        while (is_array($value)) {
            $value = array_shift($value);
        }

        switch (gettype($value)) {
            case 'double':
            case 'float':
            case 'integer':
                return $value;
            case 'boolean':
                return (int) $value;
            case 'string':
                //    Errors
                if ((strlen($value) > 0) && ($value[0] == '#')) {
                    return $value;
                }

                break;
        }

        return 0;
    }

    /**
     * TYPE.
     *
     * Returns a number that identifies the type of a value
     *
     * @param null|mixed $value The value you want tested
     *
     * @return number N converts values listed in the following table
     *        If value is or refers to N returns
     *        A number            1
     *        Text                2
     *        Logical Value        4
     *        An error value        16
     *        Array or Matrix        64
     */
    public static function TYPE($value = null)
    {
        $value = self::flattenArrayIndexed($value);
        if (is_array($value) && (count($value) > 1)) {
            end($value);
            $a = key($value);
            //    Range of cells is an error
            if (self::isCellValue($a)) {
                return 16;
            //    Test for Matrix
            } elseif (self::isMatrixValue($a)) {
                return 64;
            }
        } elseif (empty($value)) {
            //    Empty Cell
            return 1;
        }
        $value = self::flattenSingleValue($value);

        if (($value === null) || (is_float($value)) || (is_int($value))) {
            return 1;
        } elseif (is_bool($value)) {
            return 4;
        } elseif (is_array($value)) {
            return 64;
        } elseif (is_string($value)) {
            //    Errors
            if ((strlen($value) > 0) && ($value[0] == '#')) {
                return 16;
            }

            return 2;
        }

        return 0;
    }

    /**
     * Convert a multi-dimensional array to a simple 1-dimensional array.
     *
     * @param array $array Array to be flattened
     *
     * @return array Flattened array
     */
    public static function flattenArray($array)
    {
        if (!is_array($array)) {
            return (array) $array;
        }

        $arrayValues = [];
        foreach ($array as $value) {
            if (is_array($value)) {
                foreach ($value as $val) {
                    if (is_array($val)) {
                        foreach ($val as $v) {
                            $arrayValues[] = $v;
                        }
                    } else {
                        $arrayValues[] = $val;
                    }
                }
            } else {
                $arrayValues[] = $value;
            }
        }

        return $arrayValues;
    }

    /**
     * Convert a multi-dimensional array to a simple 1-dimensional array, but retain an element of indexing.
     *
     * @param array $array Array to be flattened
     *
     * @return array Flattened array
     */
    public static function flattenArrayIndexed($array)
    {
        if (!is_array($array)) {
            return (array) $array;
        }

        $arrayValues = [];
        foreach ($array as $k1 => $value) {
            if (is_array($value)) {
                foreach ($value as $k2 => $val) {
                    if (is_array($val)) {
                        foreach ($val as $k3 => $v) {
                            $arrayValues[$k1 . '.' . $k2 . '.' . $k3] = $v;
                        }
                    } else {
                        $arrayValues[$k1 . '.' . $k2] = $val;
                    }
                }
            } else {
                $arrayValues[$k1] = $value;
            }
        }

        return $arrayValues;
    }

    /**
     * Convert an array to a single scalar value by extracting the first element.
     *
     * @param mixed $value Array or scalar value
     *
     * @return mixed
     */
    public static function flattenSingleValue($value = '')
    {
        while (is_array($value)) {
            $value = array_pop($value);
        }

        return $value;
    }

    /**
     * ISFORMULA.
     *
     * @param mixed $value The cell to check
     * @param Cell $pCell The current cell (containing this formula)
     *
     * @return bool|string
     */
    public static function isFormula($value = '', Cell $pCell = null)
    {
        if ($pCell === null) {
            return self::REF();
        }

        return substr($pCell->getWorksheet()->getCell($value)->getValue(), 0, 1) === '=';
    }
}


<?php

namespace XQueue\Maileon\API\Contacts;

use XQueue\Maileon\API\AbstractMaileonService;
use XQueue\Maileon\API\Contacts\Contact;
use XQueue\Maileon\API\Contacts\Contacts;

/**
 * This service wrapps the REST API calls for the contact features.
 *
 * @author Felix Heinrichs | Trusted Mails GmbH | felix.heinrichs@trusted-mails.com
 * @author Marcus Ständer | Trusted Mails GmbH | marcus.staender@trusted-mails.com
 */
class ContactsService extends AbstractMaileonService
{

    /**
     * Creates or updates a contact and optionally triggers a double opt-in (doi) process.
     * Note that none of the attributes is required.
     *
     * @param com_maileon_api_contacts_Contact $contact
     *  the contact to create or update; if no permission is set, the Maileon default permission "NONE" will be used
     * @param com_maileon_api_contacts_SynchronizationMode $syncMode
     *  the synchronization mode to employ
     * @param string $src
     *  A string intended to describe the source of the contact.
     *  If provided, the string will be stored with the doi process.
     * @param string $subscriptionPage
     *  In case where this method was called by a subscription page,
     *  this string offers the possibility to keep track of it for use in reports.
     * @param bool $doi
     *  Tells whether a double opt-in process should be started for the created contact.
     *  Note that the status code returned for this request does not mean that the doi
     *  process succeeded.
     * @param bool $doiPlus
     *  This parameter is ignored if doi is not provided or false. In case the doi
     *  process succeeds, Maileon will be allowed to track opens and clicks of the contact.
     * @param string $doiMailingKey
     *  This parameter is ignored if doi is not provided or false. References the
     *  doi mailing to be used. If not provided, the default doi mailing will be used.
     * @return \em com_maileon_api_MaileonAPIResult
     *    the result object of the API call
     * @throws com_maileon_api_MaileonAPIException
     *  if there was a connection problem or a server error occurred
     */
    function createContact($contact, $syncMode, $src = "",
                           $subscriptionPage = "", $doi = FALSE, $doiPlus = FALSE, $doiMailingKey = "")
    {
        $queryParameters = array(
            'sync_mode' => $syncMode->getCode(),
            'src' => $src,
            'subscription_page' => $subscriptionPage,
            'doi' => ($doi == TRUE) ? "true" : "false",
            'doiplus' => ($doiPlus == TRUE) ? "true" : "false"
        );

        // As empty does not work with return values (sometimes?), first trim the variable, then check it
        $doiMailingKey = trim($doiMailingKey);
        if (!empty($doiMailingKey)) {
            $queryParameters['doimailing'] = $doiMailingKey;
        }

        if (isset($contact->permission)) {
            $queryParameters['permission'] = $contact->permission->getCode();
        }

        // The API allows only some of the fields to be submitted
        $contactToSend = new Contact(null, $contact->email, null, $contact->external_id, null, $contact->standard_fields, $contact->custom_fields);

        return $this->post("contacts/" . $contactToSend->email, $contactToSend->toXMLString(), $queryParameters);
    }

    /**
     * Creates or updates a contact based on the external ID and optionally triggers a double opt-in (doi) process.
     * Note that none of the attributes is required.
     * Also note: this call returns 409 Conflict if more then one contact with the given external ID exists as it is impossible to determine the correct contact to update.
     *
     * @param com_maileon_api_contacts_Contact $contact
     *  the contact to create or update
     * @param com_maileon_api_contacts_SynchronizationMode $syncMode
     *  the synchronization mode to employ
     * @param string $src
     *  A string intended to describe the source of the contact.
     *  If provided, the string will be stored with the doi process.
     * @param string $subscriptionPage
     *  In case where this method was called by a subscription page,
     *  this string offers the possibility to keep track of it for use in reports.
     * @param bool $doi
     *  Tells whether a double opt-in process should be started for the created contact.
     *  Note that the status code returned for this request does not mean that the doi
     *  process succeeded.
     * @param bool $doiPlus
     *  This parameter is ignored if doi is not provided or false. In case the doi
     *  process succeeds, Maileon will be allowed to track opens and clicks of the contact.
     * @param string $doiMailingKey
     *  This parameter is ignored if doi is not provided or false. References the
     *  doi mailing to be used. If not provided, the default doi mailing will be used.
     * @return \em com_maileon_api_MaileonAPIResult
     *    the result object of the API call
     * @throws com_maileon_api_MaileonAPIException
     *  if there was a connection problem or a server error occurred
     */
    function createContactByExternalId($contact, $syncMode, $src = "",
                                       $subscriptionPage = "", $doi = FALSE, $doiPlus = FALSE, $doiMailingKey = "")
    {
        $queryParameters = array(
            'permission' => $contact->permission->getCode(),
            'sync_mode' => $syncMode->getCode(),
            'src' => $src,
            'subscription_page' => $subscriptionPage,
            'doi' => ($doi == TRUE) ? "true" : "false",
            'doiplus' => ($doiPlus == TRUE) ? "true" : "false",
            'doimailing' => trim($doiMailingKey)
        );

        // The API allows only some of the fields to be submitted
        $contactToSend = new Contact(null, $contact->email, null, $contact->external_id, null, $contact->standard_fields, $contact->custom_fields);

        return $this->post("contacts/externalid/" . $contactToSend->external_id, $contactToSend->toXMLString(), $queryParameters);
    }

    /**
     * Return a contact using the maileon contact id. This resource is intended to be used
     * in profile update pages to prefill profile update forms. In order to prevent form
     * fields manipulation, a checksum of the maileon contact id is required as parameter.
     * Please refer to the documentation of the profile update pages for more details
     * about how to get the maileon contact id and the corresponding checksum.
     *
     * @param string $contactId
     *  the maileon contact id
     * @param string $checksum
     *  the checksum of the maileon contact id
     * @param string[] $standard_fields
     *  the standard fields to retrieve with the contact
     * @param string[] $custom_fields
     *  the custom fields to retrieve with the contact
     * @param bool $ignoreChecksum
     *  if set to true, no checksum is required
     * @return \em com_maileon_api_MaileonAPIResult
     *    the result object of the API call, with a com_maileon_api_contacts_Contact
     *  available at com_maileon_api_MaileonAPIResult::getResult()
     * @throws com_maileon_api_MaileonAPIException
     *  if there was a connection problem or a server error occurred
     */
    function getContact($contactId, $checksum, $standard_fields = array(), $custom_fields = array(), $ignoreChecksum = false)
    {
        $queryParameters = array(
            'id' => $contactId,
            'checksum' => $checksum,
            'standard_field' => $standard_fields,
            'ignore_checksum' => $ignoreChecksum ? "true" : "false"
        );

        $queryParameters = $this->appendArrayFields($queryParameters, 'custom_field', $custom_fields);

        return $this->get('contacts/contact', $queryParameters);
    }

    /**
     * This method returns the number of contacts in the maileon newsletter account.
     *
     * @return \em com_maileon_api_MaileonAPIResult
     *    the result object of the API call, with the count of contacts
     *  available at com_maileon_api_MaileonAPIResult::getResult()
     * @throws com_maileon_api_MaileonAPIException
     *  if there was a connection problem or a server error occurred
     */
    function getContactsCount()
    {
        return $this->get('contacts/count');
    }

    /**
     * Returns a page of contacts in the account.
     *
     * @param number $page_index
     *  the index of the result page to fetch
     * @param number $page_size
     *  the number of results to fetch per page
     * @param string[] $standard_fields
     *  the standard fields to retrieve for the contacts
     * @param string[] $custom_fields
     *  the custom fields to retrieve for the contacts
     * @return \em com_maileon_api_MaileonAPIResult
     *    the result object of the API call, with a com_maileon_api_contacts_Contacts
     *  available at com_maileon_api_MaileonAPIResult::getResult()
     * @throws com_maileon_api_MaileonAPIException
     *  if there was a connection problem or a server error occurred
     */
    function getContacts($page_index = 1, $page_size = 100, $standard_fields = array(), $custom_fields = array())
    {
        $queryParameters = array(
            'page_index' => $page_index,
            'page_size' => $page_size,
            'standard_field' => $standard_fields
        );

        $queryParameters = $this->appendArrayFields($queryParameters, 'custom_field', $custom_fields);

        return $this->get('contacts', $queryParameters);
    }

    /**
     * Returns a contact with the provided email address.
     *
     * @param string $email
     *  the email address to retrieve a contact for
     * @param string[] $standard_fields
     *  the standard fields to return with the contact
     * @param string[] $custom_fields
     *  the custom fields to return with the contact
     * @return \em com_maileon_api_MaileonAPIResult
     *    the result object of the API call, with a com_maileon_api_contacts_Contact
     *  available at com_maileon_api_MaileonAPIResult::getResult()
     * @throws com_maileon_api_MaileonAPIException
     *  if there was a connection problem or a server error occurred
     */
    function getContactByEmail($email, $standard_fields = array(), $custom_fields = array())
    {
        $queryParameters = array(
            'standard_field' => $standard_fields
        );

        $queryParameters = $this->appendArrayFields($queryParameters, 'custom_field', $custom_fields);

        return $this->get('contacts/email/' . utf8_encode($email), $queryParameters);
    }

    /**
     * Returns a list of contacts with the provided email address.
     *
     * @param string $email
     *  the email address to retrieve a contact for
     * @param string[] $standard_fields
     *  the standard fields to return with the contact
     * @param string[] $custom_fields
     *  the custom fields to return with the contact
     * @return \em com_maileon_api_MaileonAPIResult
     *    the result object of the API call, with an array of com_maileon_api_contacts_Contact
     *  available at com_maileon_api_MaileonAPIResult::getResult()
     * @throws com_maileon_api_MaileonAPIException
     *  if there was a connection problem or a server error occurred
     */
    function getContactsByEmail($email, $standard_fields = array(), $custom_fields = array())
    {
        $queryParameters = array(
            'standard_field' => $standard_fields
        );

        $queryParameters = $this->appendArrayFields($queryParameters, 'custom_field', $custom_fields);

        return $this->get('contacts/emails/' . utf8_encode($email), $queryParameters);
    }

    /**
     * Retrieves all contacts with a given external ID.
     *
     * @param string $externalId the external ID to search for
     * @param string[] $standard_fields
     * @param string[] $custom_fields
     * @return \em com_maileon_api_MaileonAPIResult
     *    the result object of the API call, with a com_maileon_api_contacts_Contacts
     *  available at com_maileon_api_MaileonAPIResult::getResult()
     * @throws com_maileon_api_MaileonAPIException
     *  if there was a connection problem or a server error occurred
     */
    function getContactsByExternalId($externalId, $standard_fields = array(), $custom_fields = array())
    {
        $queryParameters = array(
            'standard_field' => $standard_fields
        );

        $queryParameters = $this->appendArrayFields($queryParameters, 'custom_field', $custom_fields);

        return $this->get('contacts/externalid/' . utf8_encode($externalId), $queryParameters);
    }

    /**
     * Retrieves all contacts with a given contact filter ID.
     *
     * @param string $filterId the filter ID to use to select contacts
     * @param string[] $standard_fields
     * @param string[] $custom_fields
     * @return \em com_maileon_api_MaileonAPIResult
     *    the result object of the API call, with a com_maileon_api_contacts_Contacts
     *  available at com_maileon_api_MaileonAPIResult::getResult()
     * @throws com_maileon_api_MaileonAPIException
     *  if there was a connection problem or a server error occurred
     */
    function getContactsByFilterId($filterId, $page_index = 1, $page_size = 100, $standard_fields = array(), $custom_fields = array())
    {
        $queryParameters = array(
            'page_index' => $page_index,
            'page_size' => $page_size,
            'standard_field' => $standard_fields
        );
        $queryParameters = $this->appendArrayFields($queryParameters, 'custom_field', $custom_fields);
        return $this->get('contacts/filter/' . utf8_encode($filterId), $queryParameters);
    }

    /**
     * Retrieves the number of contacts matching a given contact filter ID.
     *
     * @param string $filterId the filter ID to use to select contacts
     * @return \em com_maileon_api_MaileonAPIResult
     *    the result object of the API call, with the number
     *  available at com_maileon_api_MaileonAPIResult::getResult()
     * @throws com_maileon_api_MaileonAPIException
     *  if there was a connection problem or a server error occurred
     */
    function getCountContactsByFilterId($filterId)
    {
        return $this->get('contacts/filter/' . utf8_encode($filterId) . '/count');
    }

    /**
     * Retrieves the number of active contacts matching a given contact filter ID.
     *
     * @param string $filterId the filter ID to use to select contacts
     * @return \em com_maileon_api_MaileonAPIResult
     *    the result object of the API call, with the number
     *  available at com_maileon_api_MaileonAPIResult::getResult()
     * @throws com_maileon_api_MaileonAPIException
     *  if there was a connection problem or a server error occurred
     */
    function getCountActiveContactsByFilterId($filterId)
    {
        return $this->get('contacts/filter/' . utf8_encode($filterId) . '/count/active');
    }

    /**
     * This methods updates the data of a Maileon contact identifying a contact by its internal Maileon ID
     *
     * @param com_maileon_api_contacts_Contact $contact
     *  The contact object to send to Maileon.
     * @param string $checksum
     *  This is the checksum that must be used when the request comes from a user, see documentation under http://dev.maileon.com for details.
     * @param string $src
     *  The source that shall be passed to the API.
     * @param string $subscriptionPage
     *  The subscription page the request comes from.
     * @param boolean $triggerDoi
     *  If true, a DOI mailing will be triggered.
     * @param string $doiMailingKey
     *  If this parameter is set, the DOI mailing with the given ID will be triggered. If not set, the default DOI Mailing will be triggered.
     * @param string $ignoreChecksum
     *  If this is true, the checksum will not be validated. This is only valid if the request is NOT triggered by the contact (e.g. on a profile change landing page) but from a third party system.
     * @return \em com_maileon_api_MaileonAPIResult
     *    the result object of the API call
     * @throws com_maileon_api_MaileonAPIException
     *  if there was a connection problem or a server error occurred
     */
    function updateContact($contact, $checksum = "", $src = null, $subscriptionPage = null, $triggerDoi = FALSE, $doiMailingKey = null, $ignoreChecksum = false)
    {
        $queryParameters = array(
            'id' => $contact->id,
            'checksum' => $checksum,
            'triggerdoi' => ($triggerDoi == TRUE) ? "true" : "false",
            'ignore_checksum' => $ignoreChecksum ? "true" : "false"
        );

        if (isset($contact->permission)) $queryParameters['permission'] = $contact->permission->getCode();
        if (isset($src)) $queryParameters['src'] = $src;
        if (isset($subscriptionPage)) $queryParameters['page_key'] = $subscriptionPage;
        $doiMailingKey = trim($doiMailingKey);
        if (!empty($doiMailingKey)) $queryParameters['doimailing'] = $doiMailingKey;

        // The API allows only some of the fields to be submitted
        $contactToSend = new Contact(null, $contact->email, null, $contact->external_id, null, $contact->standard_fields, $contact->custom_fields);

        return $this->put("contacts/contact", $contactToSend->toXMLString(), $queryParameters);
    }

    /**
     * Synchronizes a list of contacts with the contacts in the account and returns a detailed report with stats and validation errors.
     *
     * @param com_maileon_api_contacts_Contacts $contacts
     *  the contacts to synchronize
     * @param com_maileon_api_contacts_Permission $permission
     *  the permission to set for the contacts
     * @param com_maileon_api_contacts_SynchronizationMode $syncMode
     *  the sync mode to use
     * @param string $useExternalId
     *  if set to true, the external id is used as identifier for the contacts.
     *  Otherwise the email address is used as identifier.
     * @param string $ignoreInvalidContacts
     *  if set to true, invalid contacts are ignored and the synchronization
     *  succeeds for valid contacts.
     * @param string $reimportUnsubscribedContacts
     *  if set to true, unsubscribed contacts will be imported, if false, they will be ommitted
     * @param boolean $overridePermission
     *  if set to true the permission of existing and non existing contacts will be overwridden,
     *  if false, the permission will be used for new contacts only and existing contacts will not be influenced.
     * @param boolean $updateOnly
     *  If true, only existing contacts are updated and no new contacts are created
     * @param boolean $preferMaileonId
     *  If true, Maileon tries identifying contacts by Maileon-ID, if available. Fallback is always the email address.
     * @return \em com_maileon_api_MaileonAPIResult
     *    the result object of the API call. The
     *  response XML reports which contacts were successfully synchronized as well as any errors that
     *  might have occurred.
     * @throws com_maileon_api_MaileonAPIException
     *  if there was a connection problem or a server error occurred
     */
    function synchronizeContacts($contacts, $permission = null, $syncMode = null, $useExternalId = false, $ignoreInvalidContacts = false, $reimportUnsubscribedContacts = true, $overridePermission = true, $updateOnly = false, $preferMaileonId = false)
    {
        $queryParameters = array(
            'permission' => ($permission == null) ? 1 : $permission->getCode(),
            'sync_mode' => ($syncMode == null) ? 2 : $syncMode->getCode(),
            'use_external_id' => ($useExternalId == TRUE) ? "true" : "false",
            'ignore_invalid_contacts' => ($ignoreInvalidContacts == TRUE) ? "true" : "false",
            'reimport_unsubscribed_contacts' => ($reimportUnsubscribedContacts == TRUE) ? "true" : "false",
            'override_permission' => ($overridePermission == TRUE) ? "true" : "false",
            'update_only' => ($updateOnly == TRUE) ? "true" : "false",
            'prefer_maileon_id' => ($preferMaileonId == TRUE) ? "true" : "false"
        );

        $cleanedContacts = new Contacts();
        foreach ($contacts as $contact) {
            $cleanedContact = new Contact($contact->id, $contact->email, null, $contact->external_id, null, $contact->standard_fields, $contact->custom_fields);
            $cleanedContacts->addContact($cleanedContact);
        }

        return $this->post("contacts", $cleanedContacts->toXMLString(), $queryParameters);
    }

    /**
     * This method unsubscribes a contact from Maileon using the contact's email adress.
     *
     * @param string $email The email address of the contact.
     * @param long $mailingId The ID of the mailing to assign the unsubscribe to. the mailing must have been sent, i.e. be sealed.
     * @param array $reasons an array of reasons or a single reason (string). Unsubscription reasons have two layers
     * of information, see http://dev.maileon.com/api/rest-api-1-0/contacts/unsubscribe-contacts-by-email for more details about the format.
     * The parameter(s) will be url-encoded by the client, you do not need to provide urlencoded strings.
     * @return \em com_maileon_api_MaileonAPIResult
     *    the result object of the API call
     * @throws com_maileon_api_MaileonAPIException
     *  if there was a connection problem or a server error occurred
     */
    function unsubscribeContactByEmail($email, $mailingId = "", $reasons = null)
    {
        $queryParameters = array();
        if (!empty($mailingId)) {
            $queryParameters['mailingId'] = $mailingId;
        }

        if (!empty($reasons)) {
            if (is_array($reasons)) {
                $queryParameters = $this->appendArrayFields($queryParameters, 'reason', $reasons);
            } else {
                $queryParameters['reason'] = urlencode($reasons);
            }
        }

        $encodedEmail = utf8_encode($email);
        return $this->delete("contacts/email/${encodedEmail}/unsubscribe", $queryParameters);
    }

    /**
     * This method adds unsubscription reasons to an unsubscribed contact.
     * The contact must already be unsubscribed, otherwise 400 will be returned by the PAI
     *
     * @param int $id The ID of the contact.
     * @param string $checksum The checksum generated by Maileon
     * @param array $reasons an array of reasons or a single reason (string). Unsubscription reasons have two layers
     * of information, see http://dev.maileon.com/api/rest-api-1-0/contacts/unsubscribe-contacts-by-email for more details about the format.
     * The parameter(s) will be url-encoded by the client, you do not need to provide urlencoded strings.
     * @param boolean $ignore_checksum If the call comes from an authorized system instead of the user you might ignore the checksum
     * @return \em com_maileon_api_MaileonAPIResult
     *    the result object of the API call
     * @throws com_maileon_api_MaileonAPIException
     *  if there was a connection problem or a server error occurred
     */
    function addUnsubscriptionReasonsToUnsubscribedContact($id, $checksum = null, $reasons = null, $ignore_checksum = false)
    {
        $queryParameters = array();
        $queryParameters['id'] = $id;
        if (!empty($checksum)) {
            $queryParameters['checksum'] = $checksum;
        }
        if ($ignore_checksum===true) $queryParameters['ignore_checksum'] = "true";

        if (!empty($reasons)) {
            if (is_array($reasons)) {
                $queryParameters = $this->appendArrayFields($queryParameters, 'reason', $reasons);
            } else {
                $queryParameters['reason'] = urlencode($reasons);
            }
        }

        return $this->put("contacts/contact/unsubscribe/reasons", null, $queryParameters);
    }

    /**
     * This method unsubscribes a contact from Maileon using the Maileon id.
     *
     * @param int $id
     * @param long $mailingId The ID of the mailing to assign the unsubscribe to. the mailing must have been sent, i.e. be sealed.
     * @param array $reasons an array of reasons or a single reason (string). Unsubscription reasons have two layers
     * of information, see http://dev.maileon.com/api/rest-api-1-0/contacts/unsubscribe-contacts-by-maileon-id for more details about the format.
     * The parameter(s) will be url-encoded by the client, you do not need to provide urlencoded strings.
     * @return \em com_maileon_api_MaileonAPIResult
     *    the result object of the API call
     * @throws com_maileon_api_MaileonAPIException
     *  if there was a connection problem or a server error occurred
     */
    function unsubscribeContactById($id, $mailingId = "", $reasons = null)
    {
        $queryParameters = array(
            'id' => $id
        );

        if (!empty($mailingId)) {
            $queryParameters['mailingId'] = $mailingId;
        }
        if (!empty($reasons)) {
            if (is_array($reasons)) {
                $queryParameters = $this->appendArrayFields($queryParameters, 'reason', $reasons);
            } else {
                $queryParameters['reason'] = urlencode($reasons);
            }
        }

        return $this->delete("contacts/contact/unsubscribe", $queryParameters);
    }

    /**
     * This method unsubscribes a contact from Maileon using the external id.
     *
     * @param string $externalId The external ID of the contact.
     * @param long $mailingId The ID of the mailing to assign the unsubscribe to. the mailing must have been sent, i.e. be sealed.
     * @param array $reasons an array of reasons or a single reason (string). Unsubscription reasons have two layers
     * of information, see http://dev.maileon.com/api/rest-api-1-0/contacts/unsubscribe-contacts-external-id for more details about the format.
     * The parameter(s) will be url-encoded by the client, you do not need to provide urlencoded strings.
     * @return \em com_maileon_api_MaileonAPIResult
     *    the result object of the API call
     * @throws com_maileon_api_MaileonAPIException
     *  if there was a connection problem or a server error occurred
     */
    function unsubscribeContactByExternalId($externalId, $mailingId = "", $reasons = null)
    {
        $queryParameters = array();
        if (!empty($mailingId)) {
            $queryParameters['mailingId'] = $mailingId;
        }

        if (!empty($reasons)) {
            if (is_array($reasons)) {
                $queryParameters = $this->appendArrayFields($queryParameters, 'reason', $reasons);
            } else {
                $queryParameters['reason'] = urlencode($reasons);
            }
        }

        $encodedExternalId = utf8_encode($externalId);
        return $this->delete("contacts/externalid/${encodedExternalId}/unsubscribe", $queryParameters);
    }

    /**
     * This method unsubscribes a contact from Maileon from several accounts (owner of API key must also be the same customer owning the other accounts).
     *
     * @param int $externalId
     * @param long $nlaccountid The ID of the mailing to assign the unsubscribe to. the mailing must have been sent, i.e. be sealed.
     * @return \em com_maileon_api_MaileonAPIResult
     *    the result object of the API call
     * @throws com_maileon_api_MaileonAPIException
     *  if there was a connection problem or a server error occurred
     */
    function unsubscribeContactByExternalIdFromMultipleAccounts($externalId, $nlAccountIds = array())
    {
        $queryParameters = array();
        $queryParameters = $this->appendArrayFields($queryParameters, "nlaccountid", $nlAccountIds);

        $encodedExternalId = utf8_encode($externalId);
        return $this->delete("contacts/externalid/${encodedExternalId}/unsubscribe", $queryParameters);
    }

    /**
     * Returns a page of blocked contacts. Blocked contacts are contacts with available permission
     * but that are blocked for sendouts because of blacklist matches or similar reasons such as
     * bounce policy.
     *
     * @param string[] $standardFields
     *  the standard fields to select
     * @param string[] $customFields
     *  the custom fields to select
     * @param number $pageIndex
     *  the paging index of the page to retrieve
     * @param number $pageSize
     *  the number of results per page
     * @return \em com_maileon_api_MaileonAPIResult
     *    the result object of the API call
     * @return \em com_maileon_api_MaileonAPIResult
     *    the result object of the API call, with a com_maileon_api_co
     * @throws com_maileon_api_MaileonAPIException
     *  if there was a connection problem or a server error occurredntacts_Contacts
     *  available at com_maileon_api_MaileonAPIResult::getResult()
     */
    function getBlockedContacts($standardFields = array(), $customFields = array(), $pageIndex = 1, $pageSize = 1000)
    {
        $queryParameters = array(
            'standard_field' => $standardFields,
            'page_index' => $pageIndex,
            'page_size' => $pageSize
        );

        $queryParameters = $this->appendArrayFields($queryParameters, 'custom_field', $customFields);

        return $this->get('contacts/blocked', $queryParameters);
    }

    /**
     * This method removes a contact completely from Maileon using the maileon ID. WARNING: the contact is COMPLETELY removed, not only unsubscribed. This means that not only the contact data is removed but also all statistics change.
     * For most usecases the unsubscribe method is more appropriate.
     *
     * @param string $email
     *  The email address of the contact to delete. Does not need to be unique.
     * @return \em com_maileon_api_MaileonAPIResult
     *    the result object of the API call
     * @throws com_maileon_api_MaileonAPIException
     *  if there was a connection problem or a server error occurred
     */
    function deleteContact($id)
    {
        $queryParameters = array('id' => $id);
        return $this->delete("contacts/contact", $queryParameters);
    }

    /**
     * This method removes all contacts completely from Maileon using the email address. WARNING: the contacts are COMPLETELY removed, not only unsubscribed. This means that not only the contact data is removed but also all statistics change.
     * For most usecases the unsubscribe method is more appropriate.
     *
     * @param string $email
     *  The email address of the contact to delete. Does not need to be unique.
     * @return \em com_maileon_api_MaileonAPIResult
     *    the result object of the API call
     * @throws com_maileon_api_MaileonAPIException
     *  if there was a connection problem or a server error occurred
     */
    function deleteContactByEmail($email)
    {
        return $this->delete("contacts/email/" . utf8_encode($email));
    }

    /**
     * This method removes the contacts completely from Maileon using the external id. WARNING: the contacts are COMPLETELY removed, not only unsubscribed. This means that not only the contact data is removed but also all statistics change.
     * For most usecases the unsubscribe method is more appropriate.
     *
     * @param string $external ID
     *  The external ID of the contact to delete. Does not need to be unique.
     * @return \em com_maileon_api_MaileonAPIResult
     *    the result object of the API call
     * @throws com_maileon_api_MaileonAPIException
     *  if there was a connection problem or a server error occurred
     */
    function deleteContactsByExternalId($externalId)
    {
        return $this->delete("contacts/externalid/" . utf8_encode($externalId));
    }

    /**
     * This method DELETES ALL CONTACTS. Never ever call this unless you'd prefer a career change anyway.
     *
     * @return \em com_maileon_api_MaileonAPIResult
     *    the result object of the API call, with a com_maileon_api_contacts_Contact
     *  available at com_maileon_api_MaileonAPIResult::getResult()
     * @throws com_maileon_api_MaileonAPIException
     *  if there was a connection problem or a server error occurred
     */
    function deleteAllContacts($authorized = "no")
    {
        $queryParameters = array(
            'authorized' => $authorized
        );
        return $this->delete("contacts", $queryParameters);
    }

    /**
     * Creates a custom contact field with the provided name and data type.
     *
     * @param string $name
     *  the name of the new field
     * @param string $type
     *  the type of the new field. Valid values are 'string', 'integer', 'float', 'date' or 'boolean'.
     * @return \em com_maileon_api_MaileonAPIResult
     *    the result object of the API call, with a com_maileon_api_contacts_Contact
     *  available at com_maileon_api_MaileonAPIResult::getResult()
     * @throws com_maileon_api_MaileonAPIException
     *  if there was a connection problem or a server error occurred
     */
    function createCustomField($name, $type = 'string')
    {
        $queryParameters = array('type' => $type);
        $encodedName = urlencode(mb_convert_encoding($name, "UTF-8"));
        return $this->post("contacts/fields/custom/${encodedName}", "", $queryParameters);
    }

    /**
     * Returns the custom contact fields defined in the account.
     *
     * @return \em com_maileon_api_MaileonAPIResult
     *    the result object of the API call, with a com_maileon_api_contacts_CustomFields
     *  available at com_maileon_api_MaileonAPIResult::getResult()
     * @throws com_maileon_api_MaileonAPIException
     *  if there was a connection problem or a server error occurred
     */
    function getCustomFields()
    {
        return $this->get('contacts/fields/custom');
    }

    /**
     * Renames a custom contact field. The data type and the recorded values for
     * the contacts remain unchanged.
     *
     * @param string $oldName
     *  the current name of the field
     * @param string $newName
     *  the new name of the field
     * @return \em com_maileon_api_MaileonAPIResult
     *    the result object of the API call
     * @throws com_maileon_api_MaileonAPIException
     *  if there was a connection problem or a server error occurred
     */
    function renameCustomField($oldName, $newName)
    {
        $encodedOldName = urlencode(mb_convert_encoding($oldName, "UTF-8"));
        $encodedNewName = urlencode(mb_convert_encoding($newName, "UTF-8"));
        return $this->put("contacts/fields/custom/${encodedOldName}/${encodedNewName}");
    }

    /**
     * Deletes the custom contact field with the provided name. Note that all the values of the
     * field get auotmatically deleted by this call.
     *
     * @param string $name
     *  the name of the field to delete
     * @return \em com_maileon_api_MaileonAPIResult
     *    the result object of the API call
     * @throws com_maileon_api_MaileonAPIException
     *  if there was a connection problem or a server error occurred
     */
    function deleteCustomField($name)
    {
        $encodedName = urlencode(mb_convert_encoding($name, "UTF-8"));
        return $this->delete("contacts/fields/custom/{$encodedName}");
    }

    /**
     * Deletes the values of the given standard contact field for all contacts.
     *
     * @param string $name
     *  the name of the field whose values to delete
     * @return \em com_maileon_api_MaileonAPIResult
     *    the result object of the API call
     * @throws com_maileon_api_MaileonAPIException
     *  if there was a connection problem or a server error occurred
     */
    function deleteStandardFieldValues($name)
    {
        $encodedName = urlencode(mb_convert_encoding($name, "UTF-8"));
        return $this->delete("contacts/fields/standard/{$encodedName}/values");
    }

    /**
     * Deletes the values of the given custom contact field for all contacts.
     *
     * @param string $name
     *  the name of the field whose values to delete
     * @return \em com_maileon_api_MaileonAPIResult
     *    the result object of the API call
     * @throws com_maileon_api_MaileonAPIException
     *  if there was a connection problem or a server error occurred
     */
    function deleteCustomFieldValues($name)
    {
        $encodedName = urlencode(mb_convert_encoding($name, "UTF-8"));
        return $this->delete("contacts/fields/custom/{$encodedName}/values");
    }

}


<?php

namespace PhpOffice\PhpSpreadsheet\Reader;

use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Cell\DataType;
use PhpOffice\PhpSpreadsheet\Document\Properties;
use PhpOffice\PhpSpreadsheet\RichText\RichText;
use PhpOffice\PhpSpreadsheet\Settings;
use PhpOffice\PhpSpreadsheet\Shared\Date;
use PhpOffice\PhpSpreadsheet\Shared\File;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
use PhpOffice\PhpSpreadsheet\Style\Border;
use PhpOffice\PhpSpreadsheet\Style\Font;
use SimpleXMLElement;

/**
 * Reader for SpreadsheetML, the XML schema for Microsoft Office Excel 2003.
 */
class Xml extends BaseReader
{
    /**
     * Formats.
     *
     * @var array
     */
    protected $styles = [];

    /**
     * Character set used in the file.
     *
     * @var string
     */
    protected $charSet = 'UTF-8';

    /**
     * Create a new Excel2003XML Reader instance.
     */
    public function __construct()
    {
        $this->readFilter = new DefaultReadFilter();
    }

    /**
     * Can the current IReader read the file?
     *
     * @param string $pFilename
     *
     * @throws Exception
     *
     * @return bool
     */
    public function canRead($pFilename)
    {
        //    Office                    xmlns:o="urn:schemas-microsoft-com:office:office"
        //    Excel                    xmlns:x="urn:schemas-microsoft-com:office:excel"
        //    XML Spreadsheet            xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
        //    Spreadsheet component    xmlns:c="urn:schemas-microsoft-com:office:component:spreadsheet"
        //    XML schema                 xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882"
        //    XML data type            xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882"
        //    MS-persist recordset    xmlns:rs="urn:schemas-microsoft-com:rowset"
        //    Rowset                    xmlns:z="#RowsetSchema"
        //

        $signature = [
            '<?xml version="1.0"',
            '<?mso-application progid="Excel.Sheet"?>',
        ];

        // Open file
        $this->openFile($pFilename);
        $fileHandle = $this->fileHandle;

        // Read sample data (first 2 KB will do)
        $data = fread($fileHandle, 2048);
        fclose($fileHandle);
        $data = strtr($data, "'", '"'); // fix headers with single quote

        $valid = true;
        foreach ($signature as $match) {
            // every part of the signature must be present
            if (strpos($data, $match) === false) {
                $valid = false;

                break;
            }
        }

        //    Retrieve charset encoding
        if (preg_match('/<?xml.*encoding=[\'"](.*?)[\'"].*?>/um', $data, $matches)) {
            $this->charSet = strtoupper($matches[1]);
        }

        return $valid;
    }

    /**
     * Check if the file is a valid SimpleXML.
     *
     * @param string $pFilename
     *
     * @throws Exception
     *
     * @return false|\SimpleXMLElement
     */
    public function trySimpleXMLLoadString($pFilename)
    {
        try {
            $xml = simplexml_load_string(
                $this->securityScan(file_get_contents($pFilename)),
                'SimpleXMLElement',
                Settings::getLibXmlLoaderOptions()
            );
        } catch (\Exception $e) {
            throw new Exception('Cannot load invalid XML file: ' . $pFilename, 0, $e);
        }

        return $xml;
    }

    /**
     * Reads names of the worksheets from a file, without parsing the whole file to a Spreadsheet object.
     *
     * @param string $pFilename
     *
     * @throws Exception
     *
     * @return array
     */
    public function listWorksheetNames($pFilename)
    {
        File::assertFile($pFilename);
        if (!$this->canRead($pFilename)) {
            throw new Exception($pFilename . ' is an Invalid Spreadsheet file.');
        }

        $worksheetNames = [];

        $xml = $this->trySimpleXMLLoadString($pFilename);

        $namespaces = $xml->getNamespaces(true);

        $xml_ss = $xml->children($namespaces['ss']);
        foreach ($xml_ss->Worksheet as $worksheet) {
            $worksheet_ss = $worksheet->attributes($namespaces['ss']);
            $worksheetNames[] = self::convertStringEncoding((string) $worksheet_ss['Name'], $this->charSet);
        }

        return $worksheetNames;
    }

    /**
     * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns).
     *
     * @param string $pFilename
     *
     * @throws Exception
     *
     * @return array
     */
    public function listWorksheetInfo($pFilename)
    {
        File::assertFile($pFilename);

        $worksheetInfo = [];

        $xml = $this->trySimpleXMLLoadString($pFilename);

        $namespaces = $xml->getNamespaces(true);

        $worksheetID = 1;
        $xml_ss = $xml->children($namespaces['ss']);
        foreach ($xml_ss->Worksheet as $worksheet) {
            $worksheet_ss = $worksheet->attributes($namespaces['ss']);

            $tmpInfo = [];
            $tmpInfo['worksheetName'] = '';
            $tmpInfo['lastColumnLetter'] = 'A';
            $tmpInfo['lastColumnIndex'] = 0;
            $tmpInfo['totalRows'] = 0;
            $tmpInfo['totalColumns'] = 0;

            if (isset($worksheet_ss['Name'])) {
                $tmpInfo['worksheetName'] = (string) $worksheet_ss['Name'];
            } else {
                $tmpInfo['worksheetName'] = "Worksheet_{$worksheetID}";
            }

            if (isset($worksheet->Table->Row)) {
                $rowIndex = 0;

                foreach ($worksheet->Table->Row as $rowData) {
                    $columnIndex = 0;
                    $rowHasData = false;

                    foreach ($rowData->Cell as $cell) {
                        if (isset($cell->Data)) {
                            $tmpInfo['lastColumnIndex'] = max($tmpInfo['lastColumnIndex'], $columnIndex);
                            $rowHasData = true;
                        }

                        ++$columnIndex;
                    }

                    ++$rowIndex;

                    if ($rowHasData) {
                        $tmpInfo['totalRows'] = max($tmpInfo['totalRows'], $rowIndex);
                    }
                }
            }

            $tmpInfo['lastColumnLetter'] = Coordinate::stringFromColumnIndex($tmpInfo['lastColumnIndex'] + 1);
            $tmpInfo['totalColumns'] = $tmpInfo['lastColumnIndex'] + 1;

            $worksheetInfo[] = $tmpInfo;
            ++$worksheetID;
        }

        return $worksheetInfo;
    }

    /**
     * Loads Spreadsheet from file.
     *
     * @param string $pFilename
     *
     * @throws Exception
     *
     * @return Spreadsheet
     */
    public function load($pFilename)
    {
        // Create new Spreadsheet
        $spreadsheet = new Spreadsheet();
        $spreadsheet->removeSheetByIndex(0);

        // Load into this instance
        return $this->loadIntoExisting($pFilename, $spreadsheet);
    }

    private static function identifyFixedStyleValue($styleList, &$styleAttributeValue)
    {
        $styleAttributeValue = strtolower($styleAttributeValue);
        foreach ($styleList as $style) {
            if ($styleAttributeValue == strtolower($style)) {
                $styleAttributeValue = $style;

                return true;
            }
        }

        return false;
    }

    /**
     * pixel units to excel width units(units of 1/256th of a character width).
     *
     * @param float $pxs
     *
     * @return float
     */
    protected static function pixel2WidthUnits($pxs)
    {
        $UNIT_OFFSET_MAP = [0, 36, 73, 109, 146, 182, 219];

        $widthUnits = 256 * ($pxs / 7);
        $widthUnits += $UNIT_OFFSET_MAP[($pxs % 7)];

        return $widthUnits;
    }

    /**
     * excel width units(units of 1/256th of a character width) to pixel units.
     *
     * @param float $widthUnits
     *
     * @return float
     */
    protected static function widthUnits2Pixel($widthUnits)
    {
        $pixels = ($widthUnits / 256) * 7;
        $offsetWidthUnits = $widthUnits % 256;
        $pixels += round($offsetWidthUnits / (256 / 7));

        return $pixels;
    }

    protected static function hex2str($hex)
    {
        return chr(hexdec($hex[1]));
    }

    /**
     * Loads from file into Spreadsheet instance.
     *
     * @param string $pFilename
     * @param Spreadsheet $spreadsheet
     *
     * @throws Exception
     *
     * @return Spreadsheet
     */
    public function loadIntoExisting($pFilename, Spreadsheet $spreadsheet)
    {
        File::assertFile($pFilename);
        if (!$this->canRead($pFilename)) {
            throw new Exception($pFilename . ' is an Invalid Spreadsheet file.');
        }

        $xml = $this->trySimpleXMLLoadString($pFilename);

        $namespaces = $xml->getNamespaces(true);

        $docProps = $spreadsheet->getProperties();
        if (isset($xml->DocumentProperties[0])) {
            foreach ($xml->DocumentProperties[0] as $propertyName => $propertyValue) {
                switch ($propertyName) {
                    case 'Title':
                        $docProps->setTitle(self::convertStringEncoding($propertyValue, $this->charSet));

                        break;
                    case 'Subject':
                        $docProps->setSubject(self::convertStringEncoding($propertyValue, $this->charSet));

                        break;
                    case 'Author':
                        $docProps->setCreator(self::convertStringEncoding($propertyValue, $this->charSet));

                        break;
                    case 'Created':
                        $creationDate = strtotime($propertyValue);
                        $docProps->setCreated($creationDate);

                        break;
                    case 'LastAuthor':
                        $docProps->setLastModifiedBy(self::convertStringEncoding($propertyValue, $this->charSet));

                        break;
                    case 'LastSaved':
                        $lastSaveDate = strtotime($propertyValue);
                        $docProps->setModified($lastSaveDate);

                        break;
                    case 'Company':
                        $docProps->setCompany(self::convertStringEncoding($propertyValue, $this->charSet));

                        break;
                    case 'Category':
                        $docProps->setCategory(self::convertStringEncoding($propertyValue, $this->charSet));

                        break;
                    case 'Manager':
                        $docProps->setManager(self::convertStringEncoding($propertyValue, $this->charSet));

                        break;
                    case 'Keywords':
                        $docProps->setKeywords(self::convertStringEncoding($propertyValue, $this->charSet));

                        break;
                    case 'Description':
                        $docProps->setDescription(self::convertStringEncoding($propertyValue, $this->charSet));

                        break;
                }
            }
        }
        if (isset($xml->CustomDocumentProperties)) {
            foreach ($xml->CustomDocumentProperties[0] as $propertyName => $propertyValue) {
                $propertyAttributes = $propertyValue->attributes($namespaces['dt']);
                $propertyName = preg_replace_callback('/_x([0-9a-z]{4})_/', ['self', 'hex2str'], $propertyName);
                $propertyType = Properties::PROPERTY_TYPE_UNKNOWN;
                switch ((string) $propertyAttributes) {
                    case 'string':
                        $propertyType = Properties::PROPERTY_TYPE_STRING;
                        $propertyValue = trim($propertyValue);

                        break;
                    case 'boolean':
                        $propertyType = Properties::PROPERTY_TYPE_BOOLEAN;
                        $propertyValue = (bool) $propertyValue;

                        break;
                    case 'integer':
                        $propertyType = Properties::PROPERTY_TYPE_INTEGER;
                        $propertyValue = (int) $propertyValue;

                        break;
                    case 'float':
                        $propertyType = Properties::PROPERTY_TYPE_FLOAT;
                        $propertyValue = (float) $propertyValue;

                        break;
                    case 'dateTime.tz':
                        $propertyType = Properties::PROPERTY_TYPE_DATE;
                        $propertyValue = strtotime(trim($propertyValue));

                        break;
                }
                $docProps->setCustomProperty($propertyName, $propertyValue, $propertyType);
            }
        }

        $this->parseStyles($xml, $namespaces);

        $worksheetID = 0;
        $xml_ss = $xml->children($namespaces['ss']);

        foreach ($xml_ss->Worksheet as $worksheet) {
            $worksheet_ss = $worksheet->attributes($namespaces['ss']);

            if ((isset($this->loadSheetsOnly)) && (isset($worksheet_ss['Name'])) &&
                (!in_array($worksheet_ss['Name'], $this->loadSheetsOnly))) {
                continue;
            }

            // Create new Worksheet
            $spreadsheet->createSheet();
            $spreadsheet->setActiveSheetIndex($worksheetID);
            if (isset($worksheet_ss['Name'])) {
                $worksheetName = self::convertStringEncoding((string) $worksheet_ss['Name'], $this->charSet);
                //    Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in
                //        formula cells... during the load, all formulae should be correct, and we're simply bringing
                //        the worksheet name in line with the formula, not the reverse
                $spreadsheet->getActiveSheet()->setTitle($worksheetName, false, false);
            }

            $columnID = 'A';
            if (isset($worksheet->Table->Column)) {
                foreach ($worksheet->Table->Column as $columnData) {
                    $columnData_ss = $columnData->attributes($namespaces['ss']);
                    if (isset($columnData_ss['Index'])) {
                        $columnID = Coordinate::stringFromColumnIndex((int) $columnData_ss['Index']);
                    }
                    if (isset($columnData_ss['Width'])) {
                        $columnWidth = $columnData_ss['Width'];
                        $spreadsheet->getActiveSheet()->getColumnDimension($columnID)->setWidth($columnWidth / 5.4);
                    }
                    ++$columnID;
                }
            }

            $rowID = 1;
            if (isset($worksheet->Table->Row)) {
                $additionalMergedCells = 0;
                foreach ($worksheet->Table->Row as $rowData) {
                    $rowHasData = false;
                    $row_ss = $rowData->attributes($namespaces['ss']);
                    if (isset($row_ss['Index'])) {
                        $rowID = (int) $row_ss['Index'];
                    }

                    $columnID = 'A';
                    foreach ($rowData->Cell as $cell) {
                        $cell_ss = $cell->attributes($namespaces['ss']);
                        if (isset($cell_ss['Index'])) {
                            $columnID = Coordinate::stringFromColumnIndex((int) $cell_ss['Index']);
                        }
                        $cellRange = $columnID . $rowID;

                        if ($this->getReadFilter() !== null) {
                            if (!$this->getReadFilter()->readCell($columnID, $rowID, $worksheetName)) {
                                ++$columnID;

                                continue;
                            }
                        }

                        if (isset($cell_ss['HRef'])) {
                            $spreadsheet->getActiveSheet()->getCell($cellRange)->getHyperlink()->setUrl($cell_ss['HRef']);
                        }

                        if ((isset($cell_ss['MergeAcross'])) || (isset($cell_ss['MergeDown']))) {
                            $columnTo = $columnID;
                            if (isset($cell_ss['MergeAcross'])) {
                                $additionalMergedCells += (int) $cell_ss['MergeAcross'];
                                $columnTo = Coordinate::stringFromColumnIndex(Coordinate::columnIndexFromString($columnID) + $cell_ss['MergeAcross']);
                            }
                            $rowTo = $rowID;
                            if (isset($cell_ss['MergeDown'])) {
                                $rowTo = $rowTo + $cell_ss['MergeDown'];
                            }
                            $cellRange .= ':' . $columnTo . $rowTo;
                            $spreadsheet->getActiveSheet()->mergeCells($cellRange);
                        }

                        $cellIsSet = $hasCalculatedValue = false;
                        $cellDataFormula = '';
                        if (isset($cell_ss['Formula'])) {
                            $cellDataFormula = $cell_ss['Formula'];
                            $hasCalculatedValue = true;
                        }
                        if (isset($cell->Data)) {
                            $cellValue = $cellData = $cell->Data;
                            $type = DataType::TYPE_NULL;
                            $cellData_ss = $cellData->attributes($namespaces['ss']);
                            if (isset($cellData_ss['Type'])) {
                                $cellDataType = $cellData_ss['Type'];
                                switch ($cellDataType) {
                                    /*
                                    const TYPE_STRING        = 's';
                                    const TYPE_FORMULA        = 'f';
                                    const TYPE_NUMERIC        = 'n';
                                    const TYPE_BOOL            = 'b';
                                    const TYPE_NULL            = 'null';
                                    const TYPE_INLINE        = 'inlineStr';
                                    const TYPE_ERROR        = 'e';
                                    */
                                    case 'String':
                                        $cellValue = self::convertStringEncoding($cellValue, $this->charSet);
                                        $type = DataType::TYPE_STRING;

                                        break;
                                    case 'Number':
                                        $type = DataType::TYPE_NUMERIC;
                                        $cellValue = (float) $cellValue;
                                        if (floor($cellValue) == $cellValue) {
                                            $cellValue = (int) $cellValue;
                                        }

                                        break;
                                    case 'Boolean':
                                        $type = DataType::TYPE_BOOL;
                                        $cellValue = ($cellValue != 0);

                                        break;
                                    case 'DateTime':
                                        $type = DataType::TYPE_NUMERIC;
                                        $cellValue = Date::PHPToExcel(strtotime($cellValue));

                                        break;
                                    case 'Error':
                                        $type = DataType::TYPE_ERROR;

                                        break;
                                }
                            }

                            if ($hasCalculatedValue) {
                                $type = DataType::TYPE_FORMULA;
                                $columnNumber = Coordinate::columnIndexFromString($columnID);
                                if (substr($cellDataFormula, 0, 3) == 'of:') {
                                    $cellDataFormula = substr($cellDataFormula, 3);
                                    $temp = explode('"', $cellDataFormula);
                                    $key = false;
                                    foreach ($temp as &$value) {
                                        //    Only replace in alternate array entries (i.e. non-quoted blocks)
                                        if ($key = !$key) {
                                            $value = str_replace(['[.', '.', ']'], '', $value);
                                        }
                                    }
                                } else {
                                    //    Convert R1C1 style references to A1 style references (but only when not quoted)
                                    $temp = explode('"', $cellDataFormula);
                                    $key = false;
                                    foreach ($temp as &$value) {
                                        //    Only replace in alternate array entries (i.e. non-quoted blocks)
                                        if ($key = !$key) {
                                            preg_match_all('/(R(\[?-?\d*\]?))(C(\[?-?\d*\]?))/', $value, $cellReferences, PREG_SET_ORDER + PREG_OFFSET_CAPTURE);
                                            //    Reverse the matches array, otherwise all our offsets will become incorrect if we modify our way
                                            //        through the formula from left to right. Reversing means that we work right to left.through
                                            //        the formula
                                            $cellReferences = array_reverse($cellReferences);
                                            //    Loop through each R1C1 style reference in turn, converting it to its A1 style equivalent,
                                            //        then modify the formula to use that new reference
                                            foreach ($cellReferences as $cellReference) {
                                                $rowReference = $cellReference[2][0];
                                                //    Empty R reference is the current row
                                                if ($rowReference == '') {
                                                    $rowReference = $rowID;
                                                }
                                                //    Bracketed R references are relative to the current row
                                                if ($rowReference[0] == '[') {
                                                    $rowReference = $rowID + trim($rowReference, '[]');
                                                }
                                                $columnReference = $cellReference[4][0];
                                                //    Empty C reference is the current column
                                                if ($columnReference == '') {
                                                    $columnReference = $columnNumber;
                                                }
                                                //    Bracketed C references are relative to the current column
                                                if ($columnReference[0] == '[') {
                                                    $columnReference = $columnNumber + trim($columnReference, '[]');
                                                }
                                                $A1CellReference = Coordinate::stringFromColumnIndex($columnReference) . $rowReference;
                                                $value = substr_replace($value, $A1CellReference, $cellReference[0][1], strlen($cellReference[0][0]));
                                            }
                                        }
                                    }
                                }
                                unset($value);
                                //    Then rebuild the formula string
                                $cellDataFormula = implode('"', $temp);
                            }

                            $spreadsheet->getActiveSheet()->getCell($columnID . $rowID)->setValueExplicit((($hasCalculatedValue) ? $cellDataFormula : $cellValue), $type);
                            if ($hasCalculatedValue) {
                                $spreadsheet->getActiveSheet()->getCell($columnID . $rowID)->setCalculatedValue($cellValue);
                            }
                            $cellIsSet = $rowHasData = true;
                        }

                        if (isset($cell->Comment)) {
                            $commentAttributes = $cell->Comment->attributes($namespaces['ss']);
                            $author = 'unknown';
                            if (isset($commentAttributes->Author)) {
                                $author = (string) $commentAttributes->Author;
                            }
                            $node = $cell->Comment->Data->asXML();
                            $annotation = strip_tags($node);
                            $spreadsheet->getActiveSheet()->getComment($columnID . $rowID)->setAuthor(self::convertStringEncoding($author, $this->charSet))->setText($this->parseRichText($annotation));
                        }

                        if (($cellIsSet) && (isset($cell_ss['StyleID']))) {
                            $style = (string) $cell_ss['StyleID'];
                            if ((isset($this->styles[$style])) && (!empty($this->styles[$style]))) {
                                if (!$spreadsheet->getActiveSheet()->cellExists($columnID . $rowID)) {
                                    $spreadsheet->getActiveSheet()->getCell($columnID . $rowID)->setValue(null);
                                }
                                $spreadsheet->getActiveSheet()->getStyle($cellRange)->applyFromArray($this->styles[$style]);
                            }
                        }
                        ++$columnID;
                        while ($additionalMergedCells > 0) {
                            ++$columnID;
                            --$additionalMergedCells;
                        }
                    }

                    if ($rowHasData) {
                        if (isset($row_ss['Height'])) {
                            $rowHeight = $row_ss['Height'];
                            $spreadsheet->getActiveSheet()->getRowDimension($rowID)->setRowHeight($rowHeight);
                        }
                    }

                    ++$rowID;
                }
            }
            ++$worksheetID;
        }

        // Return
        return $spreadsheet;
    }

    protected static function convertStringEncoding($string, $charset)
    {
        if ($charset != 'UTF-8') {
            return StringHelper::convertEncoding($string, 'UTF-8', $charset);
        }

        return $string;
    }

    protected function parseRichText($is)
    {
        $value = new RichText();

        $value->createText(self::convertStringEncoding($is, $this->charSet));

        return $value;
    }

    /**
     * @param SimpleXMLElement $xml
     * @param array $namespaces
     */
    private function parseStyles(SimpleXMLElement $xml, array $namespaces)
    {
        if (!isset($xml->Styles)) {
            return;
        }

        foreach ($xml->Styles[0] as $style) {
            $style_ss = $style->attributes($namespaces['ss']);
            $styleID = (string) $style_ss['ID'];
            $this->styles[$styleID] = (isset($this->styles['Default'])) ? $this->styles['Default'] : [];
            foreach ($style as $styleType => $styleData) {
                $styleAttributes = $styleData->attributes($namespaces['ss']);
                switch ($styleType) {
                    case 'Alignment':
                        $this->parseStyleAlignment($styleID, $styleAttributes);

                        break;
                    case 'Borders':
                        $this->parseStyleBorders($styleID, $styleData, $namespaces);

                        break;
                    case 'Font':
                        $this->parseStyleFont($styleID, $styleAttributes);

                        break;
                    case 'Interior':
                        $this->parseStyleInterior($styleID, $styleAttributes);

                        break;
                    case 'NumberFormat':
                        $this->parseStyleNumberFormat($styleID, $styleAttributes);

                        break;
                }
            }
        }
    }

    /**
     * @param string $styleID
     * @param SimpleXMLElement $styleAttributes
     */
    private function parseStyleAlignment($styleID, SimpleXMLElement $styleAttributes)
    {
        $verticalAlignmentStyles = [
            Alignment::VERTICAL_BOTTOM,
            Alignment::VERTICAL_TOP,
            Alignment::VERTICAL_CENTER,
            Alignment::VERTICAL_JUSTIFY,
        ];
        $horizontalAlignmentStyles = [
            Alignment::HORIZONTAL_GENERAL,
            Alignment::HORIZONTAL_LEFT,
            Alignment::HORIZONTAL_RIGHT,
            Alignment::HORIZONTAL_CENTER,
            Alignment::HORIZONTAL_CENTER_CONTINUOUS,
            Alignment::HORIZONTAL_JUSTIFY,
        ];

        foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) {
            $styleAttributeValue = (string) $styleAttributeValue;
            switch ($styleAttributeKey) {
                case 'Vertical':
                    if (self::identifyFixedStyleValue($verticalAlignmentStyles, $styleAttributeValue)) {
                        $this->styles[$styleID]['alignment']['vertical'] = $styleAttributeValue;
                    }

                    break;
                case 'Horizontal':
                    if (self::identifyFixedStyleValue($horizontalAlignmentStyles, $styleAttributeValue)) {
                        $this->styles[$styleID]['alignment']['horizontal'] = $styleAttributeValue;
                    }

                    break;
                case 'WrapText':
                    $this->styles[$styleID]['alignment']['wrapText'] = true;

                    break;
            }
        }
    }

    /**
     * @param $styleID
     * @param SimpleXMLElement $styleData
     * @param array $namespaces
     */
    private function parseStyleBorders($styleID, SimpleXMLElement $styleData, array $namespaces)
    {
        foreach ($styleData->Border as $borderStyle) {
            $borderAttributes = $borderStyle->attributes($namespaces['ss']);
            $thisBorder = [];
            foreach ($borderAttributes as $borderStyleKey => $borderStyleValue) {
                switch ($borderStyleKey) {
                    case 'LineStyle':
                        $thisBorder['borderStyle'] = Border::BORDER_MEDIUM;

                        break;
                    case 'Weight':
                        break;
                    case 'Position':
                        $borderPosition = strtolower($borderStyleValue);

                        break;
                    case 'Color':
                        $borderColour = substr($borderStyleValue, 1);
                        $thisBorder['color']['rgb'] = $borderColour;

                        break;
                }
            }
            if (!empty($thisBorder)) {
                if (($borderPosition == 'left') || ($borderPosition == 'right') || ($borderPosition == 'top') || ($borderPosition == 'bottom')) {
                    $this->styles[$styleID]['borders'][$borderPosition] = $thisBorder;
                }
            }
        }
    }

    /**
     * @param $styleID
     * @param SimpleXMLElement $styleAttributes
     */
    private function parseStyleFont($styleID, SimpleXMLElement $styleAttributes)
    {
        $underlineStyles = [
            Font::UNDERLINE_NONE,
            Font::UNDERLINE_DOUBLE,
            Font::UNDERLINE_DOUBLEACCOUNTING,
            Font::UNDERLINE_SINGLE,
            Font::UNDERLINE_SINGLEACCOUNTING,
        ];

        foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) {
            $styleAttributeValue = (string) $styleAttributeValue;
            switch ($styleAttributeKey) {
                case 'FontName':
                    $this->styles[$styleID]['font']['name'] = $styleAttributeValue;

                    break;
                case 'Size':
                    $this->styles[$styleID]['font']['size'] = $styleAttributeValue;

                    break;
                case 'Color':
                    $this->styles[$styleID]['font']['color']['rgb'] = substr($styleAttributeValue, 1);

                    break;
                case 'Bold':
                    $this->styles[$styleID]['font']['bold'] = true;

                    break;
                case 'Italic':
                    $this->styles[$styleID]['font']['italic'] = true;

                    break;
                case 'Underline':
                    if (self::identifyFixedStyleValue($underlineStyles, $styleAttributeValue)) {
                        $this->styles[$styleID]['font']['underline'] = $styleAttributeValue;
                    }

                    break;
            }
        }
    }

    /**
     * @param $styleID
     * @param SimpleXMLElement $styleAttributes
     */
    private function parseStyleInterior($styleID, SimpleXMLElement $styleAttributes)
    {
        foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) {
            switch ($styleAttributeKey) {
                case 'Color':
                    $this->styles[$styleID]['fill']['color']['rgb'] = substr($styleAttributeValue, 1);

                    break;
                case 'Pattern':
                    $this->styles[$styleID]['fill']['fillType'] = strtolower($styleAttributeValue);

                    break;
            }
        }
    }

    /**
     * @param $styleID
     * @param SimpleXMLElement $styleAttributes
     */
    private function parseStyleNumberFormat($styleID, SimpleXMLElement $styleAttributes)
    {
        $fromFormats = ['\-', '\ '];
        $toFormats = ['-', ' '];

        foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) {
            $styleAttributeValue = str_replace($fromFormats, $toFormats, $styleAttributeValue);
            switch ($styleAttributeValue) {
                case 'Short Date':
                    $styleAttributeValue = 'dd/mm/yyyy';

                    break;
            }

            if ($styleAttributeValue > '') {
                $this->styles[$styleID]['numberFormat']['formatCode'] = $styleAttributeValue;
            }
        }
    }
}


<?php

namespace XQueue\Maileon\API\ContactFilters;

/**
 * The wrapper class for a contact filter rule
 *
 * @author Marcus Ständer | Trusted Mails GmbH | marcus.staender@trusted-mails.com
 */
class Rule
{

    public $isCustomfield;
    public $field;
    public $operator;
    public $value;
    public $type;

    /**
     * Constructor initializing rule
     *
     * @param bool $isCustomfield
     * @param string $field
     * @param string $operator The operator that should be used. Best: use EQUALS, then STARTS_WITH, then, if not possible with these, use others. Valid: EQUALS, NOTEQUALS, CONTAINS, NOTCONTAINS, STARTS_WITH
     * @param string $value
     */
    function __construct($isCustomfield, $field, $operator, $value, $type = "string")
    {
        $this->isCustomfield = $isCustomfield;
        $this->field = $field;
        $this->operator = $operator;
        $this->value = $value;
        $this->type = $type;
    }

    /**
     * Human readable representation of this rule.
     *
     * @return \em string
     *  A human readable version of the rule.
     */
    function toString()
    {
        return "Rule [isCustomfield=" . ($this->isCustomfield) ? "true" : "false" . ", field=" . $this->field . ", operator=" . $this->operator . ", value=" . $this->value . " (type = " . $this->type . ")";
    }

}


<?php

namespace Cityware\MemoryShared;

use Zend\ServiceManager\AbstractPluginManager;

class StoragePluginManager extends AbstractPluginManager {

    /**
     * Default set of storage
     *
     * @var array
     */
    protected $invokableClasses = array(
        'apc' => 'Cityware\MemoryShared\Storage\Apc',
        'db' => 'Cityware\MemoryShared\Storage\Db',
        'file' => 'Cityware\MemoryShared\Storage\File',
        //'file_ftp'    => 'Cityware\MemoryShared\Storage\FileFtp',
        'memcached' => 'Cityware\MemoryShared\Storage\Memcached',
        //'redis'       => 'Cityware\MemoryShared\Storage\Redis',
        'segment' => 'Cityware\MemoryShared\Storage\Segment',
        'bloc' => 'Cityware\MemoryShared\Storage\Bloc',
        'session' => 'Cityware\MemoryShared\Storage\Session',
        'zendshmcache' => 'Cityware\MemoryShared\Storage\ZendShmCache',
        'zendshm' => 'Cityware\MemoryShared\Storage\ZendShmCache',
        'zenddiskcache' => 'Cityware\MemoryShared\Storage\ZendDiskCache',
        'zenddisk' => 'Cityware\MemoryShared\Storage\ZendDiskCache',
    );

    /**
     * Validate the plugin
     *
     * Checks that the adapter loaded is an instance
     * of Storage\StorageInterfaceInterface.
     *
     * @param  mixed $plugin
     * @return void
     * @throws Exception\RuntimeException if invalid
     */
    public function validatePlugin($plugin) {
        if ($plugin instanceof Storage\StorageInterface) {
            return;
        }

        throw new Storage\Exception\RuntimeException(sprintf('Plugin of type %s is invalid; must implement %s\Storage\StorageInterfaceInterface', (is_object($plugin) ? get_class($plugin) : gettype($plugin)), __NAMESPACE__));
    }

}


<?php
/**
 * Shipping Piece Bag
 */
namespace Omniship\Common;

use Omniship\Helper\Collection;
use Omniship\Interfaces\PieceInterface;

/**
 * Shipping Piece Bag
 *
 * This class defines a bag (multi element set or array) of single shipping pieces
 * in the Omniship system.
 *
 * @see Piece
 */
class PieceBag extends Collection
{

    /**
     * Create a new collection.
     *
     * @param  mixed  $pieces
     */
    public function __construct($pieces = [])
    {
        $pieces = array_map(function($piece) {
            return !($piece instanceof PieceInterface) ? new Piece($piece) : $piece;
        }, $pieces);
        parent::__construct($pieces);
    }

    /**
     * Set the piece at a given offset.
     *
     * @param  mixed  $key
     * @param  mixed  $value
     * @return void
     */
    public function offsetSet($key, $value)
    {
        if(!($value instanceof PieceInterface)) {
            $value = new Piece($value);
        }
        parent::offsetSet($key, $value);
    }

    /**
     * Get all of the pieces in the collection.
     *
     * @return Piece[]
     */
    public function all()
    {
        return parent::all();
    }
}


<?php
/*   _            _          _ ____   ___  _____
 *  | |          | |        | |___ \ / _ \| ____|
 *  | |      __ _| |__   ___| | __) | | | | |__
 *  | |     / _` | '_ \ / _ \ ||__ <|  -  |___ \
 *  | |____| (_| | |_) |  __/ |___) |     |___) |
 *  |______|\__,_|_.__/ \___|_|____/ \___/|____/
 *
 *  Copyright Label305 B.V. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

namespace Label305\AujaLaravel\Factory;

use Doctrine\DBAL\Types\Type;
use Illuminate\Support\Facades\Lang;
use Label305\Auja\Page\FormItem\CheckboxFormItem;
use Label305\Auja\Page\FormItem\DateFormItem;
use Label305\Auja\Page\FormItem\DateTimeFormItem;
use Label305\Auja\Page\FormItem\FormItem;
use Label305\Auja\Page\FormItem\IntegerFormItem;
use Label305\Auja\Page\FormItem\NumberFormItem;
use Label305\Auja\Page\FormItem\PasswordFormItem;
use Label305\Auja\Page\FormItem\SelectFormItem;
use Label305\Auja\Page\FormItem\SelectOption;
use Label305\Auja\Page\FormItem\TextAreaFormItem;
use Label305\Auja\Page\FormItem\TextFormItem;
use Label305\Auja\Page\FormItem\TimeFormItem;
use Label305\AujaLaravel\Config\AujaConfigurator;
use Label305\AujaLaravel\Config\Column;
use Label305\AujaLaravel\Config\Model;

/**
 * A factory class for creating PageComponents out of types.
 *
 * @package Label305\AujaLaravel\Factory
 */
class FormItemFactory {

    /**
     * @var AujaConfigurator
     */
    private $aujaConfigurator;

    public function __construct(AujaConfigurator $aujaConfigurator) {
        $this->aujaConfigurator = $aujaConfigurator;
    }

    /**
     * @param Model     $model  The `Model` which contains given `Column`.
     * @param Column    $column The `Column` to create a `FormItem` for.
     * @param \Eloquent $item   The instance to retrieve information from for filling the `FormItem`.
     *
     * @return FormItem The created `FormItem`.
     */
    public function getFormItem(Model $model, Column $column, $item) {
        $result = null;

        if (ends_with($column->getName(), '_id')) {
            $result = $this->createSelectAssociationFormItem($model, $column, $item);
        } else {
            $result = $this->createFromType($model, $column, $item);
        }

        return $result;
    }

    /**
     * Returns a `SelectFormItem` which is filled with instances of the model given `Column` represents.
     *
     * @param Model     $model  The `Model` which contains given `Column`.
     * @param Column    $column The `Column` which represents a related model.
     * @param \Eloquent $item   The instance to retrieve information from for filling the `SelectFormItem`.
     *
     * @return SelectFormItem The created `SelectFormItem`.
     */
    private function createSelectAssociationFormItem(Model $model, Column $column, $item) {
        $result = new SelectFormItem();

        $relations = $this->aujaConfigurator->getRelationsForModel($model);
        $relatedModel = null;
        foreach ($relations as $relation) {
            $rightModel = $relation->getRight();
            if (starts_with($column->getName(), camel_case($rightModel->getName()))) {
                $relatedModel = $rightModel;
            }
        }

        if ($relatedModel != null) {
            $displayName = $this->aujaConfigurator->getDisplayName($relatedModel);
            $result->setName($displayName);

            $result->setValue($item->id);

            $items = call_user_func(array($relatedModel->getName(), 'all'));
            $displayField = $this->aujaConfigurator->getDisplayField($relatedModel);
            foreach ($items as $item) {
                $label = isset($item->$displayField) ? $item->$displayField : '';
                $value = $item->id;

                $option = new SelectOption($label, $value);
                $result->addOption($option);
            }
        }

        return $result;
    }

    /**
     * Returns a `FormItem` based on the type of the `Column`.
     *
     * @param Model     $model  The `Model` which contains given `Column`.
     * @param Column    $column The `Column` to create a `FormItem` for.
     * @param \Eloquent $item   The instance to retrieve information from for filling the `FormItem`.
     *
     * @return FormItem The created `FormItem`.
     */
    private function createFromType(Model $model, Column $column, $item) {
        $result = null;
        switch ($column->getType()) {
            case Type::TEXT:
            case Type::TARRAY:
            case Type::SIMPLE_ARRAY:
            case Type::JSON_ARRAY:
            case Type::OBJECT:
            case Type::BLOB:
                $result = new TextAreaFormItem();
                break;
            case Type::INTEGER:
            case Type::SMALLINT:
            case Type::BIGINT:
                $result = new IntegerFormItem();
                break;
            case Type::DECIMAL:
            case Type::FLOAT:
                $result = new NumberFormItem();
                break;
            case Type::BOOLEAN:
                $result = new CheckboxFormItem();
                break;
            case Type::DATE:
                $result = new DateFormItem();
                break;
            case Type::DATETIME:
            case Type::DATETIMETZ:
                $result = new DateTimeFormItem();
                break;
            case Type::TIME:
                $result = new TimeFormItem();
                break;
            case Type::STRING:
            case Type::GUID:
            default:
                $result = new TextFormItem();
                break;
        }

        $columnName = $column->getName();
        $result->setName($columnName);
        $result->setLabel(Lang::trans($this->aujaConfigurator->getColumnDisplayName($model, $columnName)));

        if ($item != null && isset($item->$columnName)) {
            $result->setValue($item->$columnName);
        }

        return $result;
    }
}