<?php
/**
 * Copyright 2019 Cloud Creativity Limited
 *
 * 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 CloudCreativity\JsonApi\Testing\Concerns;

use CloudCreativity\JsonApi\Testing\Assert;

/**
 * Trait HasDocumentAssertions
 *
 * @package CloudCreativity\JsonApi\Testing
 */
trait HasDocumentAssertions
{

    /**
     * Assert that the value at the pointer has the expected JSON API resource.
     *
     * @param string $type
     *      the expected resource object type.
     * @param string $id
     *      the expected resource object id.
     * @param string $pointer
     *      the JSON pointer to where the resource object is expected in the document.
     * @param string $message
     * @return $this
     */
    public function assertResource(
        string $type,
        string $id,
        string $pointer = '/data',
        string $message = ''
    ): self
    {
        Assert::assertResource($this, $type, $id, $pointer, $message);

        return $this;
    }

    /**
     * Assert that the value at the pointer has the expected JSON API resource identifier.
     *
     * @param string $type
     *      the expected resource object type.
     * @param string $id
     *      the expected resource object id.
     * @param string $pointer
     *      the JSON pointer to where the resource object is expected in the document.
     * @param string $message
     * @return $this
     */
    public function assertIdentifier(
        string $type,
        string $id,
        string $pointer = '/data',
        string $message = ''
    ): self
    {
        Assert::assertIdentifier($this, $type, $id, $pointer, $message);

        return $this;
    }

    /**
     * Assert that the expected value is in the document at the specified path.
     *
     * @param array|null $expected
     *      the expected value.
     * @param string $pointer
     *      the JSON pointer to where the object is expected to exist within the document.
     * @param bool $strict
     *      whether strict comparison should be used.
     * @param string $message
     * @return $this
     */
    public function assertExact(
        $expected,
        string $pointer = '/data',
        bool $strict = true,
        string $message = ''
    ): self
    {
        Assert::assertExact($this, $expected, $pointer, $strict, $message);

        return $this;
    }

    /**
     * Assert that the value at the specified path is not the expected value.
     *
     * @param $expected
     * @param string $pointer
     * @param bool $strict
     * @param string $message
     * @return $this
     */
    public function assertNotExact(
        $expected,
        string $pointer = '/data',
        bool $strict = true,
        string $message = ''
    ): self
    {
        Assert::assertNotExact($this, $expected, $pointer, $strict, $message);

        return $this;
    }

    /**
     * Assert that the expected hash is in the document at the specified path.
     *
     * @param array $expected
     *      the expected resource object.
     * @param string $pointer
     *      the JSON pointer to where the object is expected to exist within the document.
     * @param bool $strict
     *      whether strict comparison should be used.
     * @param string $message
     * @return $this
     */
    public function assertHash(
        array $expected,
        string $pointer = '/data',
        bool $strict = true,
        string $message = ''
    ): self
    {
        Assert::assertHash($this, $expected, $pointer, $strict, $message);

        return $this;
    }

    /**
     * Assert that the member contains a null value.
     *
     * @param string $pointer
     * @param string $message
     * @return $this
     */
    public function assertNull(string $pointer = '/data', string $message = ''): self
    {
        Assert::assertNull($this, $pointer, $message);

        return $this;
    }

    /**
     * Assert that the member contains an empty list.
     *
     * @param string $pointer
     * @param string $message
     * @return $this
     */
    public function assertListEmpty(string $pointer = '/data', string $message = ''): self
    {
        Assert::assertListEmpty($this, $pointer, $message);

        return $this;
    }

    /**
     * Assert that the member does not contain an empty list.
     *
     * @param string $pointer
     * @param string $message
     * @return $this
     */
    public function assertListNotEmpty(string $pointer = '/data', string $message = ''): self
    {
        Assert::assertListNotEmpty($this, $pointer, $message);

        return $this;
    }

    /**
     * Assert that list in the document only contains the specified hashes.
     *
     * This assertion does not check that the expected and actual lists are in the same order.
     * To assert the order, use `assertListInOrder`.
     *
     * @param array $expected
     * @param string $pointer
     * @param bool $strict
     * @param string $message
     * @return $this
     */
    public function assertList(
        array $expected,
        string $pointer = '/data',
        bool $strict = true,
        string $message = ''
    ): self
    {
        Assert::assertList($this, $expected, $pointer, $strict, $message);

        return $this;
    }

    /**
     * Assert that a list in the document only contains the specified values.
     *
     * This assertion does not check that the expected and actual lists are in the same order.
     * To assert the order, use `assertExactListInOrder`.
     *
     * @param array $expected
     * @param string $pointer
     * @param bool $strict
     * @param string $message
     * @return $this
     */
    public function assertExactList(
        array $expected,
        string $pointer = '/data',
        bool $strict = true,
        string $message = ''
    ): self
    {
        Assert::assertExactList($this, $expected, $pointer, $strict, $message);

        return $this;
    }

    /**
     * Assert that a list in the document contains the hashes in the specified order.
     *
     * @param array $expected
     * @param string $pointer
     * @param bool $strict
     * @param string $message
     * @return $this
     */
    public function assertListInOrder(
        array $expected,
        string $pointer = '/data',
        bool $strict = true,
        string $message = ''
    ): self
    {
        Assert::assertListInOrder($this, $expected, $pointer, $strict, $message);

        return $this;
    }

    /**
     * Assert that an array in the document contains the values in the specified order.
     *
     * @param array $expected
     * @param string $pointer
     * @param bool $strict
     * @param string $message
     * @return $this
     */
    public function assertExactListInOrder(
        array $expected,
        string $pointer = '/data',
        bool $strict = true,
        string $message = ''
    ): self
    {
        Assert::assertExactListInOrder($this, $expected, $pointer, $strict, $message);

        return $this;
    }

    /**
     * Assert that list in the document only contains the specified hashes.
     *
     * Asserting that a list contains only identifiers will fail if any of the items in the
     * list is a resource object. I.e. to pass as an identifier, it must not contain
     * `attributes` and/or `relationships` members.
     *
     * This assertion does not check that the expected and actual lists are in the same order.
     * To assert the order, use `assertListInOrder`.
     *
     * @param array $expected
     * @param string $pointer
     * @param bool $strict
     * @param string $message
     * @return $this
     */
    public function assertIdentifiersList(
        array $expected,
        string $pointer = '/data',
        bool $strict = true,
        string $message = ''
    ): self
    {
        Assert::assertIdentifiersList($this, $expected, $pointer, $strict, $message);

        return $this;
    }

    /**
     * Assert that a list in the document contains the identifiers in the specified order.
     *
     * Asserting that a list contains only identifiers will fail if any of the items in the
     * list is a resource object. I.e. to pass as an identifier, it must not contain
     * `attributes` and/or `relationships` members.
     *
     * @param array $expected
     * @param string $pointer
     * @param bool $strict
     * @param string $message
     * @return $this
     */
    public function assertIdentifiersListInOrder(
        array $expected,
        string $pointer = '/data',
        bool $strict = true,
        string $message = ''
    ): self
    {
        Assert::assertIdentifiersListInOrder($this, $expected, $pointer, $strict, $message);

        return $this;
    }

    /**
     * Assert that the document has a list containing the expected resource.
     *
     * @param string $type
     *      the expected resource object type.
     * @param string $id
     *      the expected resource object id.
     * @param string $pointer
     *      the JSON pointer to where the array is expected in the document.
     * @param string $message
     * @return $this
     */
    public function assertListContainsResource(
        string $type,
        string $id,
        string $pointer = '/data',
        string $message = ''
    ): self
    {
        Assert::assertListContainsResource($this, $type, $id, $pointer, $message);

        return $this;
    }

    /**
     * Assert that the document has a list containing the expected resource identifier.
     *
     * @param string $type
     *      the expected resource object type.
     * @param string $id
     *      the expected resource object id.
     * @param string $pointer
     *      the JSON pointer to where the array is expected in the document.
     * @param string $message
     * @return $this
     */
    public function assertListContainsIdentifier(
        string $type,
        string $id,
        string $pointer = '/data',
        string $message = ''
    ): self
    {
        Assert::assertListContainsIdentifier($this, $type, $id, $pointer, $message);

        return $this;
    }

    /**
     * Assert that a list in the document at the specified path contains the expected hash.
     *
     * @param array $expected
     * @param string $pointer
     * @param bool $strict
     * @param string $message
     * @return $this
     */
    public function assertListContainsHash(
        array $expected,
        string $pointer = '/data',
        bool $strict = true,
        string $message = ''
    ): self
    {
        Assert::assertListContainsHash($this, $expected, $pointer, $strict, $message);

        return $this;
    }

    /**
     * Assert that a list in the document at the specified path contains the expected value.
     *
     * @param array $expected
     * @param string $pointer
     * @param bool $strict
     * @param string $message
     * @return $this
     */
    public function assertListContainsExact(
        array $expected,
        string $pointer = '/data',
        bool $strict = true,
        string $message = ''
    ): self
    {
        Assert::assertListContainsExact($this, $expected, $pointer, $strict, $message);

        return $this;
    }

    /**
     * Assert that the document's included member matches the expected array.
     *
     * This does not assert the order of the included member because there is no significance to
     * the order of resources in the included member.
     *
     * @param array $expected
     * @param bool $strict
     * @param string $message
     * @return $this
     */
    public function assertIncluded(array $expected, bool $strict = true, string $message = ''): self
    {
        Assert::assertIncluded($this, $expected, $strict, $message);

        return $this;
    }

    /**
     * Assert that the expected identifier is included in the document.
     *
     * @param string $type
     * @param string $id
     * @param string $message
     * @return $this
     */
    public function assertIncludedContainsResource(string $type, string $id, string $message = ''): self
    {
        Assert::assertIncludedContainsResource($this, $type, $id, $message);

        return $this;
    }

    /**
     * Assert that the included member contains the supplied hash.
     *
     * @param array $expected
     * @param bool $strict
     * @param string $message
     * @return $this
     */
    public function assertIncludedContainsHash(array $expected, bool $strict = true, string $message = ''): self
    {
        Assert::assertIncludedContainsHash($this, $expected, $strict, $message);

        return $this;
    }

    /**
     * Assert that the top-level meta matches the expected values.
     *
     * @param array $expected
     * @param bool $strict
     * @param string $message
     * @return $this
     */
    public function assertMeta(array $expected, bool $strict = true, string $message = ''): self
    {
        Assert::assertHash($this, $expected, '/meta', $strict, $message);

        return $this;
    }

    /**
     * Assert that the top-level meta is exactly the expected meta.
     *
     * @param array $expected
     * @param bool $strict
     * @param string $message
     * @return $this
     */
    public function assertExactMeta(array $expected, bool $strict = true, string $message = ''): self
    {
        Assert::assertExact($this, $expected, '/meta', $strict, $message);

        return $this;
    }

    /**
     * Assert that the top-level links match the expected values.
     *
     * @param array $expected
     * @param bool $strict
     * @param string $message
     * @return $this
     */
    public function assertLinks(array $expected, bool $strict = true, string $message = ''): self
    {
        Assert::assertHash($this, $expected, '/links', $strict, $message);

        return $this;
    }

    /**
     * Assert that the top-level links are exactly the expected links.
     *
     * @param array $expected
     * @param bool $strict
     * @param string $message
     * @return $this
     */
    public function assertExactLinks(array $expected, bool $strict = true, string $message = ''): self
    {
        Assert::assertExact($this, $expected, '/links', $strict, $message);

        return $this;
    }

    /**
     * Assert the document contains a single error that matches the supplied error.
     *
     * @param array $error
     * @param bool $strict
     * @param string $message
     * @return $this
     */
    public function assertError(array $error, bool $strict = true, string $message = ''): self
    {
        Assert::assertError($this, $error, $strict, $message);

        return $this;
    }

    /**
     * Assert the document contains a single error that exactly matches the supplied error.
     *
     * @param array $error
     * @param bool $strict
     * @param string $message
     * @return $this
     */
    public function assertExactError(array $error, bool $strict = true, string $message = ''): self
    {
        Assert::assertExactError($this, $error, $strict, $message);

        return $this;
    }

    /**
     * Assert the document contains the supplied error within its errors member.
     *
     * @param array $error
     * @param bool $strict
     * @param string $message
     * @return $this
     */
    public function assertHasError(array $error, bool $strict = true, string $message = ''): self
    {
        Assert::assertHasError($this, $error, $strict, $message);

        return $this;
    }

    /**
     * Assert the document contains the exact supplied error within its errors member.
     *
     * @param array $error
     * @param bool $strict
     * @param string $message
     * @return $this
     */
    public function assertHasExactError(array $error, bool $strict = true, string $message = ''): self
    {
        Assert::assertHasExactError($this, $error, $strict, $message);

        return $this;
    }

    /**
     * Assert the document contains the supplied errors.
     *
     * This does not assert the order of the errors, as the error order does not have any significance.
     *
     * @param array $errors
     * @param bool $strict
     * @param string $message
     * @return $this
     */
    public function assertErrors(array $errors, bool $strict = true, string $message = ''): self
    {
        Assert::assertErrors($this, $errors, $strict, $message);

        return $this;
    }

    /**
     * Assert the document contains the exact supplied errors.
     *
     * This does not assert the order of the errors, as the error order does not have any significance.
     *
     * @param array $errors
     * @param bool $strict
     * @param string $message
     * @return $this
     */
    public function assertExactErrors(array $errors, bool $strict = true, string $message = ''): self
    {
        Assert::assertExactErrors($this, $errors, $strict, $message);

        return $this;
    }
}


<?php
/**
 * Copyright 2019 Cloud Creativity Limited
 *
 * 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 CloudCreativity\JsonApi\Testing;

use CloudCreativity\JsonApi\Testing\Constraints\ExactInDocument;
use CloudCreativity\JsonApi\Testing\Constraints\ExactInList;
use CloudCreativity\JsonApi\Testing\Constraints\IdentifierInDocument;
use CloudCreativity\JsonApi\Testing\Constraints\IdentifierInList;
use CloudCreativity\JsonApi\Testing\Constraints\IdentifiersInDocument;
use CloudCreativity\JsonApi\Testing\Constraints\OnlyExactInList;
use CloudCreativity\JsonApi\Testing\Constraints\OnlyIdentifiersInList;
use CloudCreativity\JsonApi\Testing\Constraints\OnlySubsetsInList;
use CloudCreativity\JsonApi\Testing\Constraints\SubsetInDocument;
use CloudCreativity\JsonApi\Testing\Constraints\SubsetInList;
use PHPUnit\Framework\Assert as PHPUnitAssert;
use PHPUnit\Framework\Constraint\LogicalNot;

/**
 * Class Assert
 *
 * @package CloudCreativity\JsonApi\Testing
 */
class Assert
{

    /**
     * Assert that the value at the pointer has the expected JSON API resource.
     *
     * @param array|string $document
     *      the JSON API document.
     * @param string $type
     *      the expected resource object type.
     * @param string $id
     *      the expected resource object id.
     * @param string $pointer
     *      the JSON pointer to where the resource object is expected in the document.
     * @param string $message
     */
    public static function assertResource(
        $document,
        string $type,
        string $id,
        string $pointer = '/data',
        string $message = ''
    ): void
    {
        self::assertHash($document, compact('type', 'id'), $pointer, true, $message);
    }

    /**
     * Assert that the value at the pointer has the expected JSON API resource identifier.
     *
     * @param array|string $document
     *      the JSON API document.
     * @param string $type
     *      the expected resource object type.
     * @param string $id
     *      the expected resource object id.
     * @param string $pointer
     *      the JSON pointer to where the resource object is expected in the document.
     * @param string $message
     */
    public static function assertIdentifier(
        $document,
        string $type,
        string $id,
        string $pointer = '/data',
        string $message = ''
    ): void
    {
        $expected = compact('type', 'id');

        PHPUnitAssert::assertThat(
            $document,
            new IdentifierInDocument($expected, $pointer, true),
            $message
        );
    }

    /**
     * Assert that the expected value is in the document at the specified path.
     *
     * @param array|string $document
     *      the JSON API document.
     * @param mixed $expected
     *      the expected value.
     * @param string $pointer
     *      the JSON pointer to where the object is expected to exist within the document.
     * @param bool $strict
     *      whether strict comparison should be used.
     * @param string $message
     * @return void
     */
    public static function assertExact(
        $document,
        $expected,
        string $pointer = '/data',
        bool $strict = true,
        string $message = ''
    ): void
    {
        PHPUnitAssert::assertThat(
            $document,
            new ExactInDocument($expected, $pointer, $strict),
            $message
        );
    }

    /**
     * Assert that the value at the specified path is not the expected value.
     *
     * @param $document
     * @param $expected
     * @param string $pointer
     * @param bool $strict
     * @param string $message
     * @return void
     */
    public static function assertNotExact(
        $document,
        $expected,
        string $pointer = '/data',
        bool $strict = true,
        string $message = ''
    ): void
    {
        $constraint = new LogicalNot(
            new ExactInDocument($expected, $pointer, $strict)
        );

        PHPUnitAssert::assertThat($document, $constraint, $message);
    }

    /**
     * Assert that the expected hash is in the document at the specified path.
     *
     * @param array|string $document
     *      the JSON API document.
     * @param array $expected
     *      the expected resource object.
     * @param string $pointer
     *      the JSON pointer to where the object is expected to exist within the document.
     * @param bool $strict
     *      whether strict comparison should be used.
     * @param string $message
     * @return void
     */
    public static function assertHash(
        $document,
        array $expected,
        string $pointer = '/data',
        bool $strict = true,
        string $message = ''
    ): void
    {
        PHPUnitAssert::assertThat(
            $document,
            new SubsetInDocument($expected, $pointer, $strict),
            $message
        );
    }

    /**
     * Assert that the member contains a null value.
     *
     * @param $document
     * @param string $pointer,
     * @param string $message
     * @return void
     */
    public static function assertNull($document, string $pointer = '/data', string $message = ''): void
    {
        self::assertExact($document, null, $pointer, true, $message);
    }

    /**
     * Assert that the member contains an empty list.
     *
     * @param $document
     * @param string $pointer
     * @param string $message
     * @return void
     */
    public static function assertListEmpty($document, string $pointer = '/data', string $message = ''): void
    {
        self::assertExactList($document, [], $pointer, true, $message);
    }

    /**
     * Assert that the member does not contain an empty list.
     *
     * @param $document
     * @param string $pointer
     * @param string $message
     * @return void
     */
    public static function assertListNotEmpty($document, string $pointer = '/data', string $message = ''): void
    {
        self::assertNotExact($document, [], $pointer, true, $message);
    }

    /**
     * Assert that a list in the document only contains the specified hashes.
     *
     * This assertion does not check that the expected and actual lists are in the same order.
     * To assert the order, use `assertListInOrder`.
     *
     * @param $document
     * @param array $expected
     * @param string $pointer
     * @param bool $strict
     * @param string $message
     * @return void
     */
    public static function assertList(
        $document,
        array $expected,
        string $pointer = '/data',
        bool $strict = true,
        string $message = ''
    ): void
    {
        PHPUnitAssert::assertThat(
            $document,
            new OnlySubsetsInList($expected, $pointer, $strict),
            $message
        );
    }

    /**
     * Assert that a list in the document only contains the specified values.
     *
     * This assertion does not check that the expected and actual lists are in the same order.
     * To assert the order, use `assertExactListInOrder`.
     *
     * @param $document
     * @param array $expected
     * @param string $pointer
     * @param bool $strict
     * @param string $message
     * @return void
     */
    public static function assertExactList(
        $document,
        array $expected,
        string $pointer = '/data',
        bool $strict = true,
        string $message = ''
    ): void
    {
        PHPUnitAssert::assertThat(
            $document,
            new OnlyExactInList($expected, $pointer, $strict),
            $message
        );
    }

    /**
     * Assert that a list in the document contains the hashes in the specified order.
     *
     * @param $document
     * @param array $expected
     * @param string $pointer
     * @param bool $strict
     * @param string $message
     * @return void
     */
    public static function assertListInOrder(
        $document,
        array $expected,
        string $pointer = '/data',
        bool $strict = true,
        string $message = ''
    ): void
    {
        PHPUnitAssert::assertThat(
            $document,
            new SubsetInDocument($expected, $pointer, $strict),
            $message
        );
    }

    /**
     * Assert that a list in the document contains the values in the specified order.
     *
     * @param $document
     * @param array $expected
     * @param string $pointer
     * @param bool $strict
     * @param string $message
     * @return void
     */
    public static function assertExactListInOrder(
        $document,
        array $expected,
        string $pointer = '/data',
        bool $strict = true,
        string $message = ''
    ): void
    {
        self::assertExact($document, $expected, $pointer, $strict, $message);
    }

    /**
     * Assert that a list in the document only contains the specified identifiers.
     *
     * Asserting that a list contains only identifiers will fail if any of the items in the
     * list is a resource object. I.e. to pass as an identifier, it must not contain
     * `attributes` and/or `relationships` members.
     *
     * This assertion does not check that the expected and actual lists are in the same order.
     * To assert the order, use `assertIdentifiersListInOrder`.
     *
     * @param $document
     * @param array $expected
     * @param string $pointer
     * @param bool $strict
     * @param string $message
     * @return void
     */
    public static function assertIdentifiersList(
        $document,
        array $expected,
        string $pointer = '/data',
        bool $strict = true,
        string $message = ''
    ): void
    {
        PHPUnitAssert::assertThat(
            $document,
            new OnlyIdentifiersInList($expected, $pointer, $strict),
            $message
        );
    }

    /**
     * Assert that a list in the document contains the identifiers in the specified order.
     *
     * Asserting that a list contains only identifiers will fail if any of the items in the
     * list is a resource object. I.e. to pass as an identifier, it must not contain
     * `attributes` and/or `relationships` members.
     *
     * @param $document
     * @param array $expected
     * @param string $pointer
     * @param bool $strict
     * @param string $message
     * @return void
     */
    public static function assertIdentifiersListInOrder(
        $document,
        array $expected,
        string $pointer = '/data',
        bool $strict = true,
        string $message = ''
    ): void
    {
        PHPUnitAssert::assertThat(
            $document,
            new IdentifiersInDocument($expected, $pointer, $strict),
            $message
        );
    }

    /**
     * Assert that the document has a list containing the expected resource.
     *
     * @param array|string $document
     *      the JSON API document.
     * @param string $type
     *      the expected resource object type.
     * @param string $id
     *      the expected resource object id.
     * @param string $pointer
     *      the JSON pointer to where the array is expected in the document.
     * @param string $message
     * @return void
     */
    public static function assertListContainsResource(
        $document,
        string $type,
        string $id,
        string $pointer = '/data',
        string $message = ''
    ): void
    {
        $expected = compact('type', 'id');

        self::assertListContainsHash($document, $expected, $pointer, true, $message);
    }

    /**
     * Assert that the document has a list containing the expected resource identifier.
     *
     * @param array|string $document
     *      the JSON API document.
     * @param string $type
     *      the expected resource object type.
     * @param string $id
     *      the expected resource object id.
     * @param string $pointer
     *      the JSON pointer to where the array is expected in the document.
     * @param string $message
     * @return void
     */
    public static function assertListContainsIdentifier(
        $document,
        string $type,
        string $id,
        string $pointer = '/data',
        string $message = ''
    ): void
    {
        $expected = compact('type', 'id');

        PHPUnitAssert::assertThat(
            $document,
            new IdentifierInList($expected, $pointer, true),
            $message
        );
    }

    /**
     * Assert that a list in the document at the specified path contains the expected hash.
     *
     * @param $document
     * @param array $expected
     * @param string $pointer
     * @param bool $strict
     * @param string $message
     * @return void
     */
    public static function assertListContainsHash(
        $document,
        array $expected,
        string $pointer = '/data',
        bool $strict = true,
        string $message = ''
    ): void
    {
        PHPUnitAssert::assertThat(
            $document,
            new SubsetInList($expected, $pointer, $strict),
            $message
        );
    }

    /**
     * Assert that a list in the document at the specified path contains the expected value.
     *
     * @param $document
     * @param array $expected
     * @param string $pointer
     * @param bool $strict
     * @param string $message
     * @return void
     */
    public static function assertListContainsExact(
        $document,
        array $expected,
        string $pointer = '/data',
        bool $strict = true,
        string $message = ''
    ): void
    {
        PHPUnitAssert::assertThat(
            $document,
            new ExactInList($expected, $pointer, $strict),
            $message
        );
    }

    /**
     * Assert that the document's included member matches the expected array.
     *
     * This does not assert the order of the included member because there is no significance to
     * the order of resources in the included member.
     *
     * @param $document
     * @param array $expected
     * @param bool $strict
     * @param string $message
     * @return void
     */
    public static function assertIncluded(
        $document,
        array $expected,
        bool $strict = true,
        string $message = ''
    ): void
    {
        self::assertList($document, $expected, '/included', $strict, $message);
    }

    /**
     * Assert that the expected identifier is included in the document.
     *
     * @param $document
     * @param string $type
     * @param string $id
     * @param string $message
     * @return void
     */
    public static function assertIncludedContainsResource(
        $document,
        string $type,
        string $id,
        string $message = ''
    ): void
    {
        self::assertListContainsResource($document, $type, $id, '/included', $message);
    }

    /**
     * Assert that the included member contains the supplied hash.
     *
     * @param $document
     * @param array $expected
     * @param bool $strict
     * @param string $message
     * @return void
     */
    public static function assertIncludedContainsHash(
        $document,
        array $expected,
        bool $strict = true,
        string $message = ''
    ): void
    {
        self::assertListContainsHash($document, $expected, '/included', $strict, $message);
    }

    /**
     * Assert the document contains a single error that matches the supplied error.
     *
     * @param $document
     * @param array $error
     * @param bool $strict
     * @param string $message
     * @return void
     */
    public static function assertError($document, array $error, bool $strict = true, string $message = ''): void
    {
        self::assertList($document, [$error], '/errors', $strict, $message);
    }

    /**
     * Assert the document contains a single error that exactly matches the supplied error.
     *
     * @param $document
     * @param array $error
     * @param bool $strict
     * @param string $message
     * @return void
     */
    public static function assertExactError($document, array $error, bool $strict = true, string $message = ''): void
    {
        self::assertExactList($document, [$error], '/errors', $strict, $message);
    }

    /**
     * Assert the document contains the supplied error within its errors member.
     *
     * @param $document
     * @param array $error
     * @param bool $strict
     * @param string $message
     * @return void
     */
    public static function assertHasError($document, array $error, bool $strict = true, string $message = ''): void
    {
        self::assertListContainsHash($document, $error, '/errors', $strict, $message);
    }

    /**
     * Assert the document contains the exact supplied error within its errors member.
     *
     * @param $document
     * @param array $error
     * @param bool $strict
     * @param string $message
     * @return void
     */
    public static function assertHasExactError(
        $document,
        array $error,
        bool $strict = true,
        string $message = ''
    ): void
    {
        self::assertListContainsExact($document, $error, '/errors', $strict, $message);
    }

    /**
     * Assert the document contains the supplied errors.
     *
     * This does not assert the order of the errors, as the error order does not have any significance.
     *
     * @param $document
     * @param array $errors
     * @param bool $strict
     * @param string $message
     * @return void
     */
    public static function assertErrors($document, array $errors, bool $strict = true, string $message = ''): void
    {
        self::assertList($document, $errors, '/errors', $strict, $message);
    }

    /**
     * Assert the document contains the supplied errors.
     *
     * This does not assert the order of the errors, as the error order does not have any significance.
     *
     * @param $document
     * @param array $errors
     * @param bool $strict
     * @param string $message
     * @return void
     */
    public static function assertExactErrors($document, array $errors, bool $strict = true, string $message = ''): void
    {
        self::assertExactList($document, $errors, '/errors', $strict, $message);
    }
}


<?php
/**
 * Copyright 2019 Cloud Creativity Limited
 *
 * 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 CloudCreativity\JsonApi\Testing;

use ArrayAccess;
use CloudCreativity\JsonApi\Testing\Concerns\HasDocumentAssertions;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Support\Arr;
use JsonSerializable;
use PHPUnit\Framework\Assert as PHPUnitAssert;

/**
 * Class Document
 *
 * @package CloudCreativity\JsonApi\Testing
 */
class Document implements Arrayable, JsonSerializable, ArrayAccess
{

    use HasDocumentAssertions;

    /**
     * @var array
     */
    private $document;

    /**
     * Safely create a document.
     *
     * @param $content
     * @return Document|null
     */
    public static function create($content): ?self
    {
        if (is_string($content)) {
            return self::fromString($content);
        }

        return $content ? self::cast($content) : null;
    }

    /**
     * Cast a document to an instance of this document class.
     *
     * @param Document|array|string $document
     * @return Document
     */
    public static function cast($document): self
    {
        if ($document instanceof self) {
            return $document;
        }

        if (is_string($document)) {
            return self::decode($document);
        }

        return self::fromIterable($document);
    }

    /**
     * Create a document from an iterable.
     *
     * @param iterable $input
     * @return Document
     */
    public static function fromIterable(iterable $input): self
    {
        return new self(collect($input)->all());
    }

    /**
     * Create a document from a string.
     *
     * @param string $json
     * @return Document|null
     */
    public static function fromString(string $json): ?self
    {
        $document = \json_decode($json, true);

        if (JSON_ERROR_NONE !== \json_last_error() || !\is_array($document)) {
            return null;
        }

        return new self($document);
    }

    /**
     * Decode a JSON string into a document.
     *
     * @param string $json
     * @return Document
     */
    public static function decode(string $json): self
    {
        if (!$document = self::fromString($json)) {
            throw new \InvalidArgumentException('Invalid JSON string.');
        }

        return $document;
    }

    /**
     * Document constructor.
     *
     * @param array $document
     */
    public function __construct(array $document)
    {
        $this->document = $document;
    }

    /**
     * @return string
     */
    public function __toString()
    {
        return $this->toString();
    }

    /**
     * @inheritDoc
     */
    public function offsetExists($offset)
    {
        return collect($this->document)->offsetExists($offset);
    }

    /**
     * @inheritDoc
     */
    public function offsetGet($offset)
    {
        return collect($this->document)->offsetGet($offset);
    }

    /**
     * @inheritDoc
     */
    public function offsetSet($offset, $value)
    {
        throw new \LogicException('Not implemented.');
    }

    /**
     * @inheritDoc
     */
    public function offsetUnset($offset)
    {
        throw new \LogicException('Not implemented.');
    }

    /**
     * Get a value from using a JSON pointer.
     *
     * @param string $pointer
     * @param mixed|null $default
     * @return mixed
     */
    public function get(string $pointer, $default = null)
    {
        if (!$path = Compare::path($pointer)) {
            return $this->document;
        }

        return Arr::get($this->document, $path, $default);
    }

    /**
     * Check if an item exists using JSON pointers.
     *
     * @param string ...$pointers
     * @return bool
     */
    public function has(string ...$pointers): bool
    {
        $paths = collect($pointers)->map(function ($pointer) {
            return Compare::path($pointer);
        })->filter()->all();

        return Arr::has($this->document, $paths);
    }

    /**
     * @return string
     */
    public function toString(): string
    {
        return Compare::stringify($this->document);
    }

    /**
     * @inheritDoc
     */
    public function toArray()
    {
        return $this->document;
    }

    /**
     * @inheritDoc
     */
    public function jsonSerialize()
    {
        return Compare::sort($this->document);
    }

    /**
     * Assert that all the provided members exist.
     *
     * @param string|string[] $pointers
     * @param string $message
     * @return $this
     */
    public function assertExists($pointers, string $message = ''): self
    {
        $missing = collect((array) $pointers)->reject(function ($pointer) {
            return $this->has($pointer);
        })->implode(', ');

        PHPUnitAssert::assertEmpty($missing, $message ?: "Members [{$missing}] do not exist.");

        return $this;
    }

    /**
     * Assert that the provided members do not exist.
     *
     * @param string|string[] $pointers
     * @param string $message
     * @return Document
     */
    public function assertNotExists($pointers, string $message = ''): self
    {
        $unexpected = collect((array) $pointers)->filter(function ($pointer) {
            return $this->has($pointer);
        })->implode(', ');

        PHPUnitAssert::assertEmpty($unexpected, $message ?: "Members [{$unexpected}] exist.");

        return $this;
    }

}


<?php
/**
 * Copyright 2019 Cloud Creativity Limited
 *
 * 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 CloudCreativity\JsonApi\Testing;

use Illuminate\Contracts\Routing\UrlRoutable;
use Illuminate\Support\Arr;
use SebastianBergmann\Comparator\ComparisonFailure;

/**
 * Class Compare
 *
 * @package CloudCreativity\JsonApi\Testing
 */
class Compare
{

    /**
     * Does the actual value match the expected value?
     *
     * @param array $expected
     * @param $actual
     * @param bool $strict
     * @return bool
     */
    public static function exact($expected, $actual, bool $strict = true): bool
    {
        $expected = self::normalize($expected);
        $actual = self::normalize($actual);

        if ($strict) {
            return $expected === $actual;
        }

        return $expected == $actual;
    }

    /**
     * Does the expected subset appear within the actual value?
     *
     * @param array $expected
     * @param $actual
     * @param bool $strict
     * @return bool
     */
    public static function subset(array $expected, $actual, bool $strict = true): bool
    {
        if (!is_array($actual)) {
            return false;
        }

        $patched = self::patch($actual, $expected);

        return self::exact($patched, $actual, $strict);
    }

    /**
     * Is the supplied value a resource identifier?
     *
     * A resource identifier must have a type and id member. It must not have attributes
     * or relationship members, otherwise it is a resource object.
     *
     * - attributes
     * - relationships
     *
     * @param $value
     * @return bool
     */
    public static function resourceIdentifier($value): bool
    {
        if (!is_array($value)) {
            return false;
        }

        $members = collect($value);

        return $members->has('type') &&
            $members->has('id') &&
            !$members->has('attributes') &&
            !$members->has('relationships');
    }

    /**
     * Apply a patch to a value.
     *
     * @param array $value
     * @param array $patch
     * @return array
     */
    public static function patch(array $value, array $patch): array
    {
        return \array_replace_recursive($value, $patch);
    }

    /**
     * Is the value a JSON hash?
     *
     * @param $value
     * @return bool
     */
    public static function hash($value): bool
    {
        return is_array($value) && Arr::isAssoc($value);
    }

    /**
     * Normalize a value for comparison.
     *
     * @param $value
     * @return array
     */
    public static function normalize($value)
    {
        return is_array($value) ? self::sort($value) : $value;
    }

    /**
     * Recursively sort a JSON hash or JSON list for comparison.
     *
     * @param array $value
     * @return array
     */
    public static function sort(array $value): array
    {
        if (self::hash($value)) {
            ksort($value);
        }

        return collect($value)->map(function ($item) {
            return self::normalize($item);
        })->all();
    }

    /**
     * Stringify a value.
     *
     * @param mixed $value
     * @return string
     */
    public static function stringify($value): string
    {
        return json_encode(self::normalize($value), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
    }

    /**
     * @param $expected
     * @param $actual
     * @param bool $subset
     *      whether the expected is meant to be a subset of the actual.
     * @return ComparisonFailure
     */
    public static function failure($expected, $actual, bool $subset = false): ComparisonFailure
    {
        if ($subset && is_array($actual)) {
            $expected = self::patch($actual, $expected);
        }

        $expected = self::normalize($expected);
        $actual = self::normalize($actual);

        return new ComparisonFailure(
            $expected,
            $actual,
            self::stringify($expected),
            self::stringify($actual)
        );
    }

    /**
     * Convert a JSON pointer to a dot notation path.
     *
     * @param string $pointer
     * @return string
     */
    public static function path(string $pointer): string
    {
        if ('/' === $pointer) {
            return '';
        }

        return str_replace('/', '.', ltrim($pointer, '/'));
    }

    /**
     * Ensure the value is an array of identifiers.
     *
     * @param UrlRoutable|string|int|iterable $ids
     * @param string|null $type
     *      the type to use if $id does not already have a type.
     * @return array
     */
    public static function identifiers($ids, ?string $type): array
    {
        if (self::identifiable($ids)) {
            return [self::identifier($ids, $type)];
        }

        return collect($ids)->map(function ($id) use ($type) {
            return self::identifier($id, $type);
        })->values()->all();
    }

    /**
     * Ensure the value is a resource identifier.
     *
     * @param UrlRoutable|string|int|array $id
     * @param string|null $type
     *      the type to use if $id does not already have a type.
     * @return array
     */
    public static function identifier($id, ?string $type): array
    {
        if ($id instanceof UrlRoutable) {
            $id = (string) $id->getRouteKey();
        }

        if (is_string($id) || is_int($id)) {
            return ['type' => $type, 'id' => (string) $id];
        }

        if (!Compare::hash($id)) {
            throw new \InvalidArgumentException('Expecting a URL routable, string, integer or array hash.');
        }

        if (isset($id['id']) && $id['id'] instanceof UrlRoutable) {
            $id['id'] = (string) $id['id']->getRouteKey();
        }

        if ($type && !array_key_exists('type', $id)) {
            $id['type'] = $type;
        }

        return $id;
    }

    /**
     * Does the value identify a resource?
     *
     * @param $value
     * @return bool
     */
    public static function identifiable($value): bool
    {
        return $value instanceof UrlRoutable ||
            is_string($value) ||
            is_int($value) ||
            self::hash($value);
    }
}


<?php

namespace Crummy\Phlack\Bridge\Symfony\HttpKernel;

use Crummy\Phlack\Bot\Mainframe\Adapter\AbstractAdapter;
use Crummy\Phlack\Bot\Mainframe\Mainframe;
use Crummy\Phlack\Bridge\Symfony\HttpFoundation\RequestConverter;
use Crummy\Phlack\WebHook\Reply\Reply;
use Crummy\Phlack\WebHook\SlashCommand;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\HttpKernelInterface;

class MainframeKernel extends AbstractAdapter implements HttpKernelInterface
{
    /** @var \Crummy\Phlack\Bridge\Symfony\HttpFoundation\RequestConverter */
    protected $converter;

    /**
     * @param Mainframe        $mainframe
     * @param RequestConverter $converter
     */
    public function __construct(Mainframe $mainframe = null, RequestConverter $converter = null)
    {
        $mainframe = $mainframe ?: new Mainframe();
        $converter = $converter ?: new RequestConverter();
        parent::__construct($mainframe, $converter);
    }

    /**
     * Mediates Request handling between HttpKernelInterface and the Mainframe.
     * {@inheritdoc}
     */
    public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true)
    {
        $command = $this->converter->convert($request);
        $packet = $this->mainframe->execute($command);
        $content = $packet['output'];

        if ($command instanceof SlashCommand && $content instanceof Reply) {
            $content = $content->get('text');
        }

        return new Response($content);
    }
}


<?php

namespace Crummy\Phlack;

use Crummy\Phlack\Bridge\Guzzle\PhlackClient;
use Crummy\Phlack\Bridge\Guzzle\Response\MessageResponse;
use Crummy\Phlack\Common\Exception\UnexpectedTypeException;
use Guzzle\Common\Collection;
use JsonSerializable;

class Phlack extends Collection
{
    /**
     * Phlack Constructor.
     *
     * @param mixed $client
     *
     * @throws UnexpectedTypeException
     */
    public function __construct($client)
    {
        if (is_string($client) || is_array($client)) {
            $client = new PhlackClient($client);
        } elseif (!$client instanceof PhlackClient) {
            throw new UnexpectedTypeException($client, ['string', 'array', 'Crummy\Phlack\Bridge\Guzzle\PhlackClient']);
        }

        parent::__construct([
            'client'   => $client,
            'builders' => [],
            'commands' => [
                'send' => 'Send',
            ],
        ]);
    }

    /**
     * Phlack Factory.
     *
     * @param array|string $config
     *
     * @return Phlack
     */
    public static function factory($config = [])
    {
        return new self(new PhlackClient($config));
    }

    /**
     * @return Phlack
     */
    public static function fromConfig(array $config = [], array $defaults = [], array $required = [])
    {
        return new self(new PhlackClient($config));
    }

    /**
     * @param string|array|JsonSerializable $message
     *
     * @throws UnexpectedTypeException
     *
     * @return MessageResponse
     */
    public function send($message)
    {
        return $this['client']->getCommand($this['commands']['send'], $this->toParameters($message))->execute();
    }

    /**
     * @return Bridge\Guzzle\PhlackClient
     */
    public function getClient()
    {
        return $this['client'];
    }

    /**
     * @return Builder\MessageBuilder
     */
    public function getMessageBuilder()
    {
        if (!isset($this['builders']['message'])) {
            $this->setPath('builders/message', new Builder\MessageBuilder());
        }

        return $this['builders']['message'];
    }

    /**
     * @return Builder\AttachmentBuilder
     */
    public function getAttachmentBuilder()
    {
        if (!isset($this['builders']['attachment'])) {
            $this->setPath('builders/attachment', new Builder\AttachmentBuilder());
        }

        return $this['builders']['attachment'];
    }

    /**
     * @param mixed $message
     *
     * @throws UnexpectedTypeException
     *
     * @return array
     */
    private function toParameters($message)
    {
        if (is_string($message)) {
            return $this->getMessageBuilder()->setText($message)->create()->jsonSerialize();
        } elseif (is_array($message)) {
            return $message;
        } elseif ($message instanceof JsonSerializable) {
            return $message->jsonSerialize();
        }

        throw new UnexpectedTypeException($message, ['string', 'array', 'JsonSerializable']);
    }
}


<?php

namespace Crummy\Phlack\Common\Formatter;

class EncodeFormatter implements FormatterInterface
{
    private $guards = ['^(', ')^'];

    /**
     * @param string $text
     *
     * @return string
     */
    public function format($text)
    {
        $replacement = implode('', [$this->guards[0], '$1', $this->guards[1]]);
        $text = preg_replace('/<(.*)>/U', $replacement, $text);
        $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8', false);

        return str_replace($this->guards, ['<', '>'], $text);
    }
}


<?php

namespace Crummy\Phlack\Builder;

use Crummy\Phlack\Message\Attachment;
use Crummy\Phlack\Message\Collection\FieldCollection;
use Crummy\Phlack\Message\Field;

class AttachmentBuilder implements BuilderInterface
{
    private $data = [];
    private $fields;
    private $parent;

    /**
     * Constructor.
     */
    public function __construct(MessageBuilder $parent = null)
    {
        $this->parent = $parent;
        $this->fields = new FieldCollection();
    }

    /**
     * @throws \LogicException When called before setFallback($fallback)
     *
     * @return Attachment
     */
    public function create()
    {
        $attachment = new Attachment($this->data + ['fields' => clone $this->fields]);
        $this->refresh();

        return $attachment;
    }

    /**
     * @param $fallback
     *
     * @return $this
     */
    public function setFallback($fallback)
    {
        return $this->setParameter('fallback', (string) $fallback);
    }

    /**
     * @param $text
     *
     * @return $this
     */
    public function setText($text)
    {
        return $this->setParameter('text', $text);
    }

    /**
     * @param $pretext
     *
     * @return $this
     */
    public function setPretext($pretext)
    {
        return $this->setParameter('pretext', $pretext);
    }

    /**
     * @param $color
     *
     * @return $this
     */
    public function setColor($color)
    {
        return $this->setParameter('color', $color);
    }

    /**
     * @param $author_name
     *
     * @return $this
     */
    public function setAuthorName($author_name)
    {
        return $this->setParameter('author_name', $author_name);
    }

    /**
     * @param $author_link
     *
     * @return $this
     */
    public function setAuthorLink($author_link)
    {
        return $this->setParameter('author_link', $author_link);
    }

    /**
     * @param $author_icon
     *
     * @return $this
     */
    public function setAuthorIcon($author_icon)
    {
        return $this->setParameter('author_icon', $author_icon);
    }

    /**
     * @param $title
     *
     * @return $this
     */
    public function setTitle($title)
    {
        return $this->setParameter('title', $title);
    }

    /**
     * @param $title_link
     *
     * @return $this
     */
    public function setTitleLink($title_link)
    {
        return $this->setParameter('title_link', $title_link);
    }

    /**
     * @param $image_url
     *
     * @return $this
     */
    public function setImageUrl($image_url)
    {
        return $this->setParameter('image_url', $image_url);
    }

    /**
     * @param $thumb_url
     *
     * @return $this
     */
    public function setThumbUrl($thumb_url)
    {
        return $this->setParameter('thumb_url', $thumb_url);
    }

    /**
     * @param $mrkdwn_in
     *
     * @return $this
     */
    public function setMrkdwnIn($mrkdwn_in)
    {
        return $this->setParameter('mrkdwn_in', $mrkdwn_in);
    }

    /**
     * Sets values on non-empty parameters.
     * Set $value to null to remove the custom value.
     *
     * @param string $name
     * @param $value
     *
     * @return $this
     */
    private function setParameter($name, $value)
    {
        if (null !== $value && empty($value)) {
            return $this;
        }

        $this->data[$name] = $value;

        return $this;
    }

    /**
     * @param string $title
     * @param string $value
     * @param bool   $isShort
     *
     * @return $this
     */
    public function addField($title, $value, $isShort)
    {
        $this->fields->add(new Field($title, $value, $isShort));

        return $this;
    }

    /**
     * Reset the attachment data and the fields collection.
     */
    protected function refresh()
    {
        $this->data = [];
        $this->fields->clear();
    }

    /**
     * @return MessageBuilder
     */
    public function end()
    {
        if ($this->parent) {
            $this->parent->addAttachment($this->create());

            return $this->parent;
        }

        return $this;
    }
}


<?php

namespace Crummy\Phlack\Bridge\Symfony\Console;

use Crummy\Phlack\Bot\BotInterface;
use Crummy\Phlack\Bot\Mainframe\Adapter\AdapterInterface;
use Crummy\Phlack\Bot\Mainframe\Mainframe;
use Crummy\Phlack\Common\Matcher\MatcherInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class BotCommand extends Command implements AdapterInterface
{
    private $adapter;

    /**
     * @param ConsoleAdapter $adapter
     * @param string         $name
     */
    public function __construct(ConsoleAdapter $adapter, $name = 'bot_command')
    {
        parent::__construct($name);

        $this->adapter = $adapter;
    }

    /**
     * @param Mainframe $mainframe
     *
     * @return self
     */
    public function setMainframe(Mainframe $mainframe)
    {
        $this->adapter->setMainframe($mainframe);

        return $this;
    }

    /**
     * @return \Crummy\Phlack\WebHook\Converter\ConverterInterface
     */
    public function getConverter()
    {
        return $this->adapter->getConverter();
    }

    /**
     * @param BotInterface              $bot
     * @param MatcherInterface|callable $matcher
     * @param int                       $priority
     *
     * @return self
     */
    public function attach(BotInterface $bot, $matcher = null, $priority = 0)
    {
        $this->adapter->attach($bot, $matcher, $priority);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $this->adapter->execute($input, $output);
    }
}


<?php

namespace Crummy\Phlack\Common\Collection;

use Crummy\Phlack\Common\Exception\UnexpectedValueException;

abstract class TypeCollection extends ArrayCollection
{
    /**
     * {@inheritdoc}
     *
     * @throws UnexpectedValueException
     */
    public function __construct(array $elements = [])
    {
        parent::__construct();

        foreach ($elements as $element) {
            $this->add($element);
        }
    }

    /**
     * {@inheritdoc}
     *
     * @throws UnexpectedValueException
     */
    public function add($value)
    {
        if (!$this->acceptsType($value)) {
            throw $this->doUnexpectedValueError($value);
        }

        return parent::add($value);
    }

    /**
     * {@inheritdoc}
     *
     * @throws UnexpectedValueException
     */
    public function set($key, $value)
    {
        if (!$this->acceptsType($value)) {
            throw $this->doUnexpectedValueError($value);
        }

        parent::set($key, $value);
    }

    /**
     * @param $element
     *
     * @return UnexpectedValueException
     */
    private function doUnexpectedValueError($element)
    {
        return new UnexpectedValueException(sprintf(
            '"%s" does not match the expected type.',
            is_object($element) ? get_class($element) : gettype($element)
        ));
    }

    /**
     * @param $value
     *
     * @return bool
     */
    abstract public function acceptsType($value);
}


<?php

namespace Crummy\Phlack\Bot\Mainframe;

use Crummy\Phlack\Bot\BotInterface;
use Crummy\Phlack\Common\Events;
use Crummy\Phlack\Common\Exception\InvalidArgumentException;
use Crummy\Phlack\Common\Executable;
use Crummy\Phlack\Common\Matcher\MatcherAggregate;
use Crummy\Phlack\Common\Matcher\MatcherInterface;
use Crummy\Phlack\WebHook\CommandInterface;
use Symfony\Component\EventDispatcher\EventDispatcher;

class Mainframe implements Executable
{
    private $cpu;

    /**
     * Constructor.
     */
    public function __construct()
    {
        $this->cpu = new EventDispatcher();

        $this->cpu->addSubscriber(new Plugin\EncoderPlugin());
    }

    /**
     * @param CommandInterface $command
     *
     * @return Packet
     */
    public function execute(CommandInterface $command)
    {
        $packet = $this->cpu->dispatch(Events::RECEIVED_COMMAND, new Packet(['command' => $command]));

        return $this->cpu->dispatch(Events::AFTER_EXECUTE_COMMAND, $packet);
    }

    /**
     * @param BotInterface              $bot
     * @param MatcherInterface|callable $matcher  If callable, it should accept a CommandInterface and return a boolean.
     * @param int                       $priority
     *
     * @return self
     */
    public function attach(BotInterface $bot, $matcher = null, $priority = 0)
    {
        if (!$matcher && $bot instanceof MatcherAggregate) {
            $matcher = $bot->getMatcher();
        } else {
            $matcher = function () {
                return true;
            };
        }

        $this->cpu->addListener(Events::RECEIVED_COMMAND, $this->getListener($bot, $matcher), $priority);

        return $this;
    }

    /**
     * @param BotInterface              $bot
     * @param MatcherInterface|callable $matcher If callable, it should accept a CommandInterface and return a boolean.
     *
     * @throws \Crummy\Phlack\Common\Exception\InvalidArgumentException When given an invalid matcher.
     *
     * @return \Closure An anonymous function to be attached to the internal cpu.
     */
    public function getListener(BotInterface $bot, $matcher)
    {
        if (!$matcher instanceof MatcherInterface && !is_callable($matcher)) {
            throw new InvalidArgumentException(sprintf(
                'The matcher must be callable, or an instance of MatcherInterface. "%s" given.',
                is_object($matcher) ? get_class($matcher) : gettype($matcher)
            ));
        }

        return function (Packet $packet) use ($bot, $matcher) {
            if ($matcher instanceof MatcherInterface) {
                $isMatch = $matcher->matches($packet['command']);
            } else {
                $isMatch = call_user_func($matcher, $packet['command']);
            }

            if ($isMatch) {
                $packet->stopPropagation();
                $packet['output'] = $bot->execute($packet['command']);
            }
        };
    }
}


<?php

namespace Crummy\Phlack\Message;

use Crummy\Phlack\Common\OptionsResolver;
use Crummy\Phlack\Message\Collection\AttachmentCollection;
use Symfony\Component\OptionsResolver\Options;

class Message extends Partial implements MessageInterface
{
    protected $optional = ['text', 'channel', 'username', 'icon_emoji', 'attachments'];

    /**
     * @param $text
     * @param null $channel
     * @param null $username
     * @param null $iconEmoji
     */
    public function __construct($text, $channel = null, $username = null, $iconEmoji = null)
    {
        parent::__construct([
            'text'          => $text,
            'channel'       => $channel,
            'username'      => $username,
            'icon_emoji'    => $iconEmoji,
            'attachments'   => new AttachmentCollection(),
        ]);
    }

    /**
     * {@inheritdoc}
     */
    protected function setDefaultOptions(OptionsResolver $resolver)
    {
        parent::setDefaultOptions($resolver);

        $resolver->setTypesAllowed([
            'attachments' => '\Crummy\Phlack\Message\Collection\AttachmentCollection',
        ]);

        $resolver->setNormalizers([
            'icon_emoji' => function (Options $options, $value) {
                return empty($value) ? $value : sprintf(':%s:', trim($value, ':'));
            },
        ]);
    }

    /**
     * @param AttachmentInterface $attachment
     *
     * @return $this
     */
    public function addAttachment(AttachmentInterface $attachment)
    {
        $this['attachments']->add($attachment);

        return $this;
    }

    /**
     * @return AttachmentCollection
     */
    public function getAttachments()
    {
        return $this->data['attachments'];
    }
}


<?php

namespace Crummy\Phlack\WebHook\Converter;

use Crummy\Phlack\WebHook\SlashCommand;
use Crummy\Phlack\WebHook\WebHook;

class StringConverter implements ConverterInterface
{
    protected $webHook = [
        'token'        => '',
        'team_id'      => '',
        'team_domain'  => '',
        'service_id'   => '',
        'channel_id'   => '',
        'channel_name' => '',
        'timestamp'    => 0.000000,
        'user_id'      => '',
        'user_name'    => '',
    ];

    protected $slashCommand = [
        'token'        => '',
        'team_id'      => '',
        'channel_id'   => '',
        'channel_name' => '',
        'user_id'      => '',
        'user_name'    => '',
    ];

    /**
     * @param $command
     *
     * @return \Crummy\Phlack\WebHook\CommandInterface
     */
    public function convert($command)
    {
        if (0 !== strpos($command, '/')) {
            return new WebHook(['text' => $command] + $this->webHook);
        } else {
            list($command, $text) = explode(' ', $command, 2);

            return new SlashCommand(['command' => $command, 'text' => $text] + $this->slashCommand);
        }
    }
}


<?php

namespace Crummy\Phlack\Common\Formatter;

use Crummy\Phlack\WebHook\CommandInterface;

class Sequencer implements FormatterInterface
{
    const SEQUENCE = '<%s>';

    /**
     * @param string $text  The text to be sequenced
     * @param string $label An optional label
     *
     * @return string
     */
    public function format($text, $label = null)
    {
        return $this->sequence($text, $label);
    }

    /**
     * @param string $text
     * @param null   $label
     *
     * @return string
     */
    public static function sequence($text, $label = null)
    {
        $text = $label ? $text.'|'.$label : $text;

        return sprintf(static::SEQUENCE, $text);
    }

    /**
     * @param CommandInterface $command
     *
     * @return array
     */
    public static function command(CommandInterface $command)
    {
        return [
            'channel'   => self::sequence('#'.$command['channel_id'], $command['channel_name']),
            'user'      => self::sequence('@'.$command['user_id'], $command['user_name']),
        ];
    }

    /**
     * @param string $channel
     *
     * @return string
     */
    public static function alert($channel)
    {
        return self::sequence('!'.$channel);
    }
}


<?php

namespace Crummy\Phlack\Common\Formatter;

use Crummy\Phlack\Common\Collection\TypeCollection;

class FormatterCollection extends TypeCollection implements FormatterInterface
{
    /**
     * @param string $text
     *
     * @return string
     */
    public function format($text)
    {
        /** @var FormatterInterface $formatter */
        foreach ($this->toArray() as $formatter) {
            $text = $formatter->format($text);
        }

        return $text;
    }

    /**
     * @param $value
     *
     * @return bool
     */
    public function acceptsType($value)
    {
        return $value instanceof FormatterInterface;
    }
}


<?php

namespace Crummy\Phlack\Bridge\Guzzle\Model;

use Guzzle\Service\Resource\ResourceIterator;

class ListFilesIterator extends ResourceIterator
{
    /**
     * Send a request to retrieve the next page of results. Hook for subclasses to implement.
     *
     * @return array Returns the newly loaded resources
     */
    protected function sendRequest()
    {
        if ($this->nextToken) {
            $this->command->set('page', $this->nextToken);
        }

        $result = $this->command->execute();
        $currentToken = $result['paging']['page'];
        $this->nextToken = $result['paging']['pages'] > $currentToken ? ++$currentToken : false;

        return $result['files'];
    }
}


<?php

namespace Crummy\Phlack\Common\Matcher;

use Crummy\Phlack\WebHook\CommandInterface;
use Crummy\Phlack\WebHook\SlashCommand;

class CommandMatcher extends CommandNameMatcher
{
    /**
     * {@inheritdoc}
     */
    public function matches(CommandInterface $command)
    {
        return null === $this->commandName
            ? $command instanceof SlashCommand
            : $command instanceof SlashCommand && parent::matches($command);
    }
}


<?php

namespace Crummy\Phlack\Bot;

use Crummy\Phlack\Common\Exception\InvalidArgumentException;
use Crummy\Phlack\Common\Iterocitor;
use Crummy\Phlack\Common\Matcher;
use Crummy\Phlack\Common\Responder\ResponderInterface;
use Crummy\Phlack\Message\MessageInterface;

abstract class AbstractBot implements BotInterface, Matcher\MatcherAggregate
{
    private $matcher;
    private $responder;

    /**
     * @param Matcher\MatcherInterface $matcher
     * @param array|ResponderInterface $options
     */
    public function __construct($matcher = null, $options = [])
    {
        if (!$matcher) {
            $matcher = new Matcher\DefaultMatcher();
        }

        $this->setMatcher($matcher);
        $this->responder = $options instanceof ResponderInterface ? $options : new Iterocitor($options);
    }

    /**
     * @param Matcher\MatcherInterface $matcher
     *
     * @throws \Crummy\Phlack\Common\Exception\InvalidArgumentException When given an invalid matcher.
     *
     * @return $this
     */
    public function setMatcher($matcher)
    {
        if (!$matcher instanceof Matcher\MatcherInterface && !is_callable($matcher)) {
            throw new InvalidArgumentException(sprintf(
                'The matcher must be callable, or implement \Crummy\Phlack\Common\Matcher\MatcherInterface. "%" given.',
                is_object($matcher) ? get_class($matcher) : gettype($matcher)
            ));
        }

        $this->matcher = $matcher;

        return $this;
    }

    /**
     * @return Matcher\MatcherInterface|callable
     */
    public function getMatcher()
    {
        return $this->matcher;
    }

    /**
     * @param string $text
     *
     * @return \Crummy\Phlack\WebHook\Reply\Reply
     */
    protected function say($text)
    {
        return $this->responder->say($text);
    }

    /**
     * @param string $text
     *
     * @return \Crummy\Phlack\WebHook\Reply\Reply
     */
    protected function emote($text)
    {
        return $this->responder->emote($text);
    }

    /**
     * @param string $user The user_id to tell
     * @param string $text
     *
     * @return \Crummy\Phlack\WebHook\Reply\Reply
     */
    protected function tell($user, $text)
    {
        return $this->responder->tell($user, $text);
    }

    /**
     * @param \Crummy\Phlack\WebHook\CommandInterface $user The user_id, or a CommandInterface to inspect.
     * @param string                                  $text
     *
     * @return \Crummy\Phlack\WebHook\Reply\Reply
     */
    protected function reply($user, $text)
    {
        return $this->responder->reply($user, $text);
    }

    /**
     * @param string $text
     *
     * @return \Crummy\Phlack\WebHook\Reply\Reply
     */
    protected function shout($text)
    {
        return $this->responder->shout($text);
    }

    /**
     * @param MessageInterface $message
     *
     * @return \Crummy\Phlack\WebHook\Reply\EmptyReply
     */
    protected function send(MessageInterface $message)
    {
        return $this->responder->send($message);
    }
}


<?php

namespace Crummy\Phlack\Bot;

use Crummy\Phlack\Common\Matcher\CommandMatcher;
use Crummy\Phlack\WebHook\CommandInterface;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
use Symfony\Component\ExpressionLanguage\SyntaxError;

class ExpressionBot extends AbstractBot
{
    /** @var \Symfony\Component\ExpressionLanguage\ExpressionLanguage */
    protected $language;

    /**
     * @param string             $commandName
     * @param ExpressionLanguage $language
     */
    public function __construct($commandName = '/expr', ExpressionLanguage $language = null)
    {
        $this->language = $language ?: new ExpressionLanguage();

        parent::__construct(new CommandMatcher($commandName));
    }

    /**
     * @param CommandInterface $command
     *
     * @return \Crummy\Phlack\WebHook\Reply\Reply
     */
    public function execute(CommandInterface $command)
    {
        try {
            $response = (string) $this->evaluate($command, $this->getValues($command));
        } catch (SyntaxError $badSyntax) {
            $response = $badSyntax->getMessage();
        }

        return $this->say($response);
    }

    /**
     * @param CommandInterface $command
     * @param array            $values
     *
     * @return string
     */
    protected function evaluate(CommandInterface $command, $values = [])
    {
        return $this->language->evaluate($command['text'], $values);
    }

    /**
     * @param CommandInterface $command
     *
     * @return array
     */
    protected function getValues(CommandInterface $command)
    {
        return [];
    }
}


<?php

namespace Crummy\Phlack\Bot\Mainframe\Plugin;

use Crummy\Phlack\Bot\Mainframe\Packet;
use Crummy\Phlack\Common\Events;
use Crummy\Phlack\Common\Formatter\EncodeFormatter;
use Crummy\Phlack\Common\Formatter\FormatterCollection;
use Crummy\Phlack\Common\Formatter\FormatterInterface;
use Crummy\Phlack\Common\Formatter\LinkFormatter;

class EncoderPlugin implements PluginInterface
{
    private $formatter;

    /**
     * @param FormatterInterface $formatter
     */
    public function __construct(FormatterInterface $formatter = null)
    {
        $this->formatter = $formatter ?: new FormatterCollection([
            new LinkFormatter(),
            new EncodeFormatter(),
        ]);
    }

    /**
     * {@inheritdoc}
     */
    public static function getSubscribedEvents()
    {
        return [
           Events::AFTER_EXECUTE_COMMAND => 'onAfterExecute',
       ];
    }

    /**
     * @param Packet $packet
     *
     * @return Packet
     */
    public function onAfterExecute(Packet $packet)
    {
        if (isset($packet['output'])) {
            $packet['output']['text'] = $this->formatter->format($packet['output']['text']);
        }

        return $packet;
    }
}


<?php

namespace Crummy\Phlack\Bridge\Guzzle;

use Guzzle\Common\Collection;
use Guzzle\Service\Client;
use Guzzle\Service\Description\ServiceDescription;

class PhlackClient extends Client
{
    /** @var array */
    protected $defaultConfig = [
        'request.options' => [
            'exceptions'  => false,
        ],
    ];

    /** @var array */
    protected $legacyRequirements = ['username', 'token'];

    /**
     * @param string     $baseUrl
     * @param array|null $config
     */
    public function __construct($baseUrl = LegacyUrlPlugin::BASE_URL, $config = null)
    {
        $legacyMode = $this->isLegacyUrl($baseUrl);

        $config = Collection::fromConfig(
            $config ?: [],
            $this->defaultConfig,
            $legacyMode ? $this->legacyRequirements : []
        );

        if ($legacyMode) {
            $this->addSubscriber(new LegacyUrlPlugin($config['username'], $config['token']));
            unset($config['username'], $config['token']);
        }

        parent::__construct($baseUrl, $config);

        $this->setDescription(ServiceDescription::factory(__DIR__.'/Resources/slack.json'));
    }

    /**
     * {@inheritdoc}
     */
    public static function factory($config = [])
    {
        if (!is_array($config)) {
            return new self($config);
        }

        return new self(isset($config['url']) ? $config['url'] : null, $config);
    }

    /**
     * @param string $baseUrl
     *
     * @return bool
     */
    private function isLegacyUrl($baseUrl)
    {
        return null === $baseUrl || $baseUrl === LegacyUrlPlugin::BASE_URL;
    }
}


<?php

namespace Crummy\Phlack\Bridge\Symfony\Console;

use Crummy\Phlack\Bot\Mainframe\Mainframe;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;

class ShellBot extends Application
{
    private $bot;

    /**
     * {@inheritdoc}
     *
     * @param BotCommand $botCommand The BotCommand that executes for this ShellBot.
     */
    public function __construct($name = 'phlackbot', $version = 'UNKNOWN', BotCommand $botCommand = null)
    {
        $this->bot = $botCommand ?: new BotCommand(new ConsoleAdapter(new Mainframe()));
        parent::__construct($name, $version);
    }

    /**
     * Overrides commandName for this ShellBot.
     * {@inheritdoc}
     */
    protected function getCommandName(InputInterface $input)
    {
        return $this->bot->getName();
    }

    /**
     * Adds BotCommand as the sole command for this ShellBot.
     * {@inheritdoc}
     */
    protected function getDefaultCommands()
    {
        $defaultCommands = parent::getDefaultCommands();
        $defaultCommands[] = $this->bot;

        return $defaultCommands;
    }

    /**
     * Replaces the default InputDefinition with one containing only the 'command' argument.
     * {@inheritdoc}
     */
    public function getDefinition()
    {
        $inputDefinition = parent::getDefinition();
        $inputDefinition->setArguments([
            new InputArgument('command', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'The input command'),
        ]);

        return $inputDefinition;
    }
}


<?php

namespace Crummy\Phlack\Common;

use Crummy\Phlack\Common\Exception\LogicException;
use Guzzle\Common\Collection as GuzzleCollection;

class Hash extends GuzzleCollection implements Encodable
{
    protected $required = [];
    protected $defaults = [];
    protected $optional = [];

    /**
     * @param array $data
     */
    public function __construct($data = [])
    {
        $resolver = new OptionsResolver();
        $this->setDefaultOptions($resolver);
        parent::__construct($resolver->resolve($data));
    }

    /**
     * {@inheritdoc}
     */
    public static function fromConfig(array $config = [], array $defaults = [], array $required = [])
    {
        if (!empty($defaults) || !empty($required)) {
            throw new LogicException('Setting options on fromConfig is not allowed.');
        }

        return new static($config);
    }

    /**
     * {@inheritdoc}
     */
    public function __toString()
    {
        return json_encode($this);
    }

    /**
     * {@inheritdoc}
     */
    public function jsonSerialize()
    {
        return $this->toArray();
    }

    /**
     * @param OptionsResolver $resolver
     */
    protected function setDefaultOptions(OptionsResolver $resolver)
    {
        $resolver->setRequired($this->required);
        $resolver->setDefined($this->optional);
        $resolver->setDefaults($this->defaults);
    }
}


<?php

namespace Crummy\Phlack\Common;

use Crummy\Phlack\Bridge\Guzzle\PhlackClient;
use Crummy\Phlack\Common\Formatter\Sequencer;
use Crummy\Phlack\Common\Responder\ResponderInterface;
use Crummy\Phlack\Message\MessageInterface;
use Crummy\Phlack\WebHook\CommandInterface;
use Crummy\Phlack\WebHook\Reply\EmptyReply;
use Crummy\Phlack\WebHook\Reply\Reply;

class Iterocitor implements ResponderInterface
{
    private $client;
    private $sequencer;

    /**
     * @param array $options
     */
    public function __construct($options = [])
    {
        $this->sequencer = new Sequencer();

        if ($options instanceof PhlackClient) {
            $this->client = $options;
        } else {
            $this->client = new PhlackClient($options);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function say($text)
    {
        return new Reply(['text' => $text]);
    }

    /**
     * "Emotes" a message into the channel.
     * {@inheritdoc}
     */
    public function emote($text)
    {
        return $this->important('channel', $text);
    }

    /**
     * Send a message to a user.
     *
     * @param string $user user_id
     *                     {@inheritdoc}
     */
    public function tell($user, $text)
    {
        return $this->say($this->sequencer->format('@'.$user).' '.$text);
    }

    /**
     * Respond with a reply to a user.
     *
     * @param CommandInterface|string user_id, or a CommandInterface containing user_id and user_name.
     * @param $text
     *
     * @return Reply
     */
    public function reply($user, $text)
    {
        if ($user instanceof CommandInterface) {
            $sequence = $this->sequencer->command($user);

            return $this->say($sequence['user'].' '.$text);
        }

        return $this->tell($user, $text);
    }

    /**
     * {@inheritdoc}
     */
    public function send(MessageInterface $message)
    {
        $command = $this->client->getCommand('Send', $message->jsonSerialize());

        $this->client->execute($command);

        return new EmptyReply();
    }

    /**
     * @param string $text
     *
     * @return \Crummy\Phlack\WebHook\Reply\Reply
     */
    public function shout($text)
    {
        return $this->important('everyone', $text);
    }

    /**
     * @param string $where
     * @param string $text
     *
     * @return Reply
     */
    protected function important($where, $text)
    {
        return $this->say($this->sequencer->alert($where).' '.$text);
    }
}


<?php

namespace Crummy\Phlack\Bot\Mainframe\Adapter;

use Crummy\Phlack\Bot\BotInterface;
use Crummy\Phlack\Bot\Mainframe\Mainframe;
use Crummy\Phlack\Common\Matcher\MatcherInterface;
use Crummy\Phlack\WebHook\Converter\ConverterInterface;
use Crummy\Phlack\WebHook\Converter\StringConverter;

class AbstractAdapter implements AdapterInterface
{
    /** @var Mainframe */
    protected $mainframe;

    /** @var StringConverter|ConverterInterface */
    protected $converter;

    /**
     * @param Mainframe          $mainframe
     * @param ConverterInterface $converter
     */
    public function __construct(Mainframe $mainframe, ConverterInterface $converter = null)
    {
        $this->setMainframe($mainframe);
        $this->converter = $converter ?: new StringConverter();
    }

    /**
     * @param Mainframe $mainframe
     *
     * @return self
     */
    public function setMainframe(Mainframe $mainframe)
    {
        $this->mainframe = $mainframe;

        return $this;
    }

    /**
     * @return \Crummy\Phlack\WebHook\Converter\ConverterInterface
     */
    public function getConverter()
    {
        return $this->converter;
    }

    /**
     * @param BotInterface              $bot
     * @param MatcherInterface|callable $matcher
     * @param int                       $priority
     *
     * @return self
     */
    public function attach(BotInterface $bot, $matcher = null, $priority = 0)
    {
        $this->mainframe->attach($bot, $matcher, $priority);

        return $this;
    }
}


<?php

namespace Crummy\Phlack\Common\Formatter;

class LinkFormatter implements FormatterInterface
{
    const HTML = '/ <a (?:.*?) href=[\'"](.+)[\'"] (?:.*)> (.+?) <\/a> /x';
    const MARKDOWN = '/\[(.*?)\]\((.+?)\)/';

    /**
     * @param string $text
     *
     * @return string
     */
    public function format($text)
    {
        $text = preg_replace_callback(self::HTML, $this->getMatchFormatter(), $text);
        $text = preg_replace_callback(self::MARKDOWN, $this->getMatchFormatter(2, 1), $text);

        return $text;
    }

    /**
     * @param int $linkIdx  The index of the matches containing the resource link
     * @param int $labelIdx The index of the matches containing the label (if one exists)
     *
     * @return \Closure A callable to be passed to preg_replace_callback
     */
    protected function getMatchFormatter($linkIdx = 1, $labelIdx = 2)
    {
        return function (array $matches) use ($linkIdx, $labelIdx) {
            $out = '<';
            $out .= $matches[$linkIdx];
            if (!empty($matches[$labelIdx])) {
                $out .= '|'.$matches[$labelIdx];
            }
            $out .= '>';

            return $out;
        };
    }
}


<?php

namespace Crummy\Phlack\Common;

use Symfony\Component\OptionsResolver\OptionsResolver as BaseOptionsResolver;

class OptionsResolver extends BaseOptionsResolver
{
    private $isLatestVersion;

    /**
     * Set a list of allowed types for the given option.
     *
     * @see Symfony\Component\OptionsResolver\OptionsResolver::setAllowedTypes()
     *
     * @param array $allowedTypes
     *
     * @return $this
     */
    public function setTypesAllowed($allowedTypes = null)
    {
        if (!$this->isLatest()) {
            return $this->setAllowedTypes($allowedTypes);
        }

        foreach ($allowedTypes as $option => $typesAllowed) {
            $this->setAllowedTypes($option, $typesAllowed);
        }

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function setDefined($optionNames)
    {
        if (!$this->isLatest()) {
            return $this->setOptional($optionNames);
        }

        return parent::setDefined($optionNames);
    }

    public function setNormalizers(array $normalizers)
    {
        if (!$this->isLatest()) {
            return parent::setNormalizers($normalizers);
        }

        foreach ($normalizers as $option => $normalizer) {
            parent::setNormalizer($option, $normalizer);
        }

        return $this;
    }

    /**
     * Checks whether or not the OptionsResolver version is up-to-date.
     *
     * @return bool
     */
    private function isLatest()
    {
        if (null === $this->isLatestVersion) {
            $this->isLatestVersion = method_exists(current(class_parents($this)), 'setDefined');
        }

        return $this->isLatestVersion;
    }
}


<?php

namespace Crummy\Phlack\Bot;

use Crummy\Phlack\WebHook\CommandInterface;

class RepeaterBot extends AbstractBot
{
    /**
     * @param CommandInterface $command
     *
     * @return \Crummy\Phlack\WebHook\Reply\Reply
     */
    public function execute(CommandInterface $command)
    {
        $text = preg_replace(sprintf('/^%s /', $command['command']), '', $command['text']);

        return $this->reply($command, $text);
    }
}


<?php

namespace Crummy\Phlack\Common\Matcher;

use Crummy\Phlack\WebHook\CommandInterface;
use Crummy\Phlack\WebHook\WebHookInterface;

class WebHookMatcher extends CommandNameMatcher
{
    /**
     * {@inheritdoc}
     */
    public function matches(CommandInterface $command)
    {
        return null === $this->commandName
            ? $command instanceof WebHookInterface
            : $command instanceof WebHookInterface && parent::matches($command);
    }
}


<?php

declare(strict_types=1);

namespace NotificationChannels\Authy;

use Rinvex\Authy\Token as AuthyToken;
use Illuminate\Notifications\Notification;

class AuthyChannel
{
    /**
     * The Authy token instance.
     *
     * @var \Rinvex\Authy\Token
     */
    protected $authyToken;

    /**
     * Create a new Authy channel instance.
     *
     * @param \Rinvex\Authy\Token $authyToken
     */
    public function __construct(AuthyToken $authyToken)
    {
        $this->authyToken = $authyToken;
    }

    /**
     * Send the given notification.
     *
     * @param mixed                                  $notifiable
     * @param \Illuminate\Notifications\Notification $notification
     *
     * @return bool
     */
    public function send($notifiable, Notification $notification)
    {
        if (! $authyId = $notifiable->routeNotificationFor('authy')) {
            return false;
        }

        // Prepare notification message
        $message = $notification->toAuthy($notifiable);

        // Send Authy notification and get the result
        $result = $this->authyToken->send($authyId, $message->method, $message->force, $message->action, $message->actionMessage);

        return $result->succeed();
    }
}


<?php
/**
 *
 * EPV :: The phpBB Forum Extension Pre Validator.
 *
 * @copyright (c) 2014 phpBB Limited <https://www.phpbb.com>
 * @license       GNU General Public License, version 2 (GPL-2.0)
 *
 */
namespace Phpbb\Epv\Output;

use SensioLabs\AnsiConverter\AnsiToHtmlConverter;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
use Symfony\Component\Console\Output\OutputInterface;

class HtmlOutput implements OutputInterface
{
	const TYPE_HTML = 1;
	const TYPE_BBCODE = 2;

	private $buffer = "";
	private $type;

	/**
	 * @param int $type Output type (HTML or BBCode)
	 */
	public function __construct($type = self::TYPE_HTML)
	{
		$this->type = $type;
	}

	/**
	 * Writes a message to the output.
	 *
	 * @param string|array $messages The message as an array of lines or a single string
	 * @param bool         $newline  Whether to add a newline
	 * @param int          $type     The type of output (one of the OUTPUT constants)
	 *
	 * @throws \InvalidArgumentException When unknown output type is given
	 *
	 * @api
	 */
	public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL)
	{
		if (!is_array($messages))
		{
			$messages = array($messages);
		}

		foreach ($messages as $message)
		{
			$this->buffer .= $this->parse($message);
			if ($newline)
			{
				$this->buffer .= "\n";
			}
		}
	}

	/**
	 * Writes a message to the output and adds a newline at the end.
	 *
	 * @param string|array $messages The message as an array of lines of a single string
	 * @param int          $type     The type of output (one of the OUTPUT constants)
	 *
	 * @throws \InvalidArgumentException When unknown output type is given
	 *
	 * @api
	 */
	public function writeln($messages, $type = self::OUTPUT_NORMAL)
	{
		$this->write($messages, true, $type);
	}

	/**
	 * Sets the verbosity of the output.
	 *
	 * @param int $level The level of verbosity (one of the VERBOSITY constants)
	 *
	 * @api
	 */
	public function setVerbosity($level)
	{

	}

	/**
	 * Gets the current verbosity of the output.
	 *
	 * @return int     The current level of verbosity (one of the VERBOSITY constants)
	 *
	 * @api
	 */
	public function getVerbosity()
	{

	}

	/**
	 * Sets the decorated flag.
	 *
	 * @param bool $decorated Whether to decorate the messages
	 *
	 * @api
	 */
	public function setDecorated($decorated)
	{

	}

	/**
	 * Gets the decorated flag.
	 *
	 * @return bool    true if the output will decorate messages, false otherwise
	 *
	 * @api
	 */
	public function isDecorated()
	{

	}

	/**
	 * Sets output formatter.
	 *
	 * @param OutputFormatterInterface $formatter
	 *
	 * @api
	 */
	public function setFormatter(OutputFormatterInterface $formatter)
	{

	}

	/**
	 * Returns current output formatter instance.
	 *
	 * @return  OutputFormatterInterface
	 *
	 * @api
	 */
	public function getFormatter()
	{

	}

	public function getBuffer()
	{
		if ($this->type == self::TYPE_HTML)
		{
			$formatter = new OutputFormatter(true);

			$convertor = new AnsiToHtmlConverter();

			return nl2br($convertor->convert($formatter->format($this->buffer)));
		}

		return $this->buffer;
	}

	/**
	 * Parse the code from the CLI to html.
	 *
	 * @param $message
	 *
	 * @return mixed Parsed message
	 */
	private function parse($message)
	{
		return $message;
	}

    /**
     * Returns whether verbosity is quiet (-q).
     *
     * @return bool true if verbosity is set to VERBOSITY_QUIET, false otherwise
     */
    public function isQuiet()
    {
    }

    /**
     * Returns whether verbosity is verbose (-v).
     *
     * @return bool true if verbosity is set to VERBOSITY_VERBOSE, false otherwise
     */
    public function isVerbose()
    {
    }

    /**
     * Returns whether verbosity is very verbose (-vv).
     *
     * @return bool true if verbosity is set to VERBOSITY_VERY_VERBOSE, false otherwise
     */
    public function isVeryVerbose()
    {
    }

    /**
     * Returns whether verbosity is debug (-vvv).
     *
     * @return bool true if verbosity is set to VERBOSITY_DEBUG, false otherwise
     */
    public function isDebug()
    {
    }
}


<?php
/**
 *
 * EPV :: The phpBB Forum Extension Pre Validator.
 *
 * @copyright (c) 2014 phpBB Limited <https://www.phpbb.com>
 * @license       GNU General Public License, version 2 (GPL-2.0)
 *
 */

namespace Phpbb\Events;

/**
 * This filter ignores directories and files starting with a dot.
 * It also skips some directories that do not contain events anyway,
 * such as e.g. files/, store/ and vendor/
 */
class recursive_event_filter_iterator extends \RecursiveFilterIterator
{
	protected $root_path;

	/**
	 * Construct
	 *
	 * @param \RecursiveIterator $iterator
	 * @param string             $root_path
	 */
	public function __construct(\RecursiveIterator $iterator, $root_path)
	{
		$this->root_path = str_replace(DIRECTORY_SEPARATOR, '/', $root_path);
		parent::__construct($iterator);
	}

	/**
	 * Return the inner iterator's children contained in a recursive_event_filter_iterator
	 *
	 * @return recursive_event_filter_iterator
	 */
	public function getChildren()
	{
		return new self($this->getInnerIterator()->getChildren(), $this->root_path);
	}

	/**
	 * {@inheritDoc}
	 */
	public function accept()
	{
		$relative_path = str_replace(DIRECTORY_SEPARATOR, '/', $this->current());
		$filename      = $this->current()->getFilename();

		return (substr($relative_path, -4) === '.php' || $this->current()->isDir())
		&& $filename[0] !== '.'
		&& strpos($relative_path, $this->root_path . 'cache/') !== 0
		&& strpos($relative_path, $this->root_path . 'develop/') !== 0
		&& strpos($relative_path, $this->root_path . 'docs/') !== 0
		&& strpos($relative_path, $this->root_path . 'ext/') !== 0
		&& strpos($relative_path, $this->root_path . 'files/') !== 0
		&& strpos($relative_path, $this->root_path . 'includes/utf/') !== 0
		&& strpos($relative_path, $this->root_path . 'language/') !== 0
		&& strpos($relative_path, $this->root_path . 'phpbb/db/migration/data/') !== 0
		&& strpos($relative_path, $this->root_path . 'phpbb/event/') !== 0
		&& strpos($relative_path, $this->root_path . 'store/') !== 0
		&& strpos($relative_path, $this->root_path . 'tests/') !== 0
		&& strpos($relative_path, $this->root_path . 'vendor/') !== 0;
	}
}