<?php
declare(strict_types=1);

namespace Entities\Administrators\Model;

use Infrastructure\Database\DataMappers\EntityMapper;
use Exceptions;
use Infrastructure\Database\Queries\QueryBuilder;
use Infrastructure\Database\Queries\SelectBuilder;
use Entities\Events\EventsTableMapper;
use Infrastructure\Database\Postgres;
use Infrastructure\Security\Authorization\AuthorizationService;
use Infrastructure\Security\Authentication\AuthenticationService;
use Entities\Roles\Model\RolesTableMapper;

// Singleton
final class AdministratorsEntityMapper extends EntityMapper
{
    private $administratorsTableMapper;

    const TABLE_NAME = 'administrators';
    const ROLES_TABLE_NAME = 'roles';
    const ADM_ROLES_TABLE_NAME = 'administrator_roles';

    const SELECT_COLUMNS = [
        'id' => self::TABLE_NAME . '.id',
        'name' => self::TABLE_NAME . '.name',
        'username' => self::TABLE_NAME . '.username',
        'passwordHash' => self::TABLE_NAME . '.password_hash',
        'roleId' => self::ROLES_TABLE_NAME . '.id AS role_id',
        'roles' => self::ROLES_TABLE_NAME . '.role',
        'active' => self::TABLE_NAME . '.active',
        'created' => self::TABLE_NAME . '.created',
    ];

    const ORDER_BY_COLUMN_NAME = 'name';

    public static function getInstance()
    {
        static $instance = null;
        if ($instance === null) {
            $instance = new AdministratorsEntityMapper();
        }
        return $instance;
    }

    protected function __construct()
    {
        $this->administratorsTableMapper = AdministratorsTableMapper::getInstance();
        parent::setDefaultSelectColumnsString();
    }

    public function getListViewTitle(): string
    {
        return $this->administratorsTableMapper->getFormalTableName();
    }

    public function getInsertTitle(): string
    {
        return "Insert Administrator";
    }

    public function getUpdateTitle(): string
    {
        return "Update Administrator";
    }

    public function getUpdateColumnName(): ?string
    {
        return $this->administratorsTableMapper->getUpdateColumnName();
    }

    public function getListViewSortColumn(): ?string
    {
        return $this->administratorsTableMapper->getOrderByColumnName();
    }

    public function getListViewSortAscending(): bool
    {
        return $this->administratorsTableMapper->getOrderByAsc();
    }

    /** any validation should be done prior */
    public function create(string $name, string $username, string $passwordClear, array $roleIds, bool $active): int
    {
        // insert administrator then administrator_roles in a transaction
        pg_query("BEGIN");

        try {
            $administratorId = $this->administratorsTableMapper->callInsert($name, $username, $passwordClear, $active);
        } catch (\Exception $e) {
            pg_query("ROLLBACK");
            throw $e;
        }

        try {
            $this->insertAdministratorRoles((int) $administratorId, $roleIds);
        } catch (\Exception $e) {
            pg_query("ROLLBACK");
            throw $e;
        }

        pg_query("COMMIT");
        return $administratorId;
    }

    private function insertAdministratorRoles(int $administratorId, array $roleIds): array
    {
        $administratorRoleIds = [];
        foreach ($roleIds as $roleId) {
            $administratorRoleIds[] = $this->doInsertAdministratorRole($administratorId, (int) $roleId);
        }
        return $administratorRoleIds;
    }

    private function doInsertAdministratorRole(int $administratorId, int $roleId)
    {
        $q = new QueryBuilder("INSERT INTO ".self::ADM_ROLES_TABLE_NAME." (administrator_id, role_id) VALUES($1, $2)", $administratorId, $roleId);
        return $q->executeWithReturnField('id');
    }

    protected function getFromClause(): string
    {
        return "FROM ".self::TABLE_NAME." JOIN ".self::ADM_ROLES_TABLE_NAME." ON ".self::TABLE_NAME.".id = ".self::ADM_ROLES_TABLE_NAME.".administrator_id JOIN ".self::ROLES_TABLE_NAME." ON ".self::ADM_ROLES_TABLE_NAME.".role_id = ".self::ROLES_TABLE_NAME.".id";
    }

    protected function getOrderBy(): string
    {
        return SELF::TABLE_NAME . "." . SELF::ORDER_BY_COLUMN_NAME . ", " . SELF::ADM_ROLES_TABLE_NAME . ".role_id";
    }

    private function getObject(array $whereColumnsInfo): ?Administrator
    {
        $q = new SelectBuilder("SELECT ".$this->defaultSelectColumnsString, $this->getFromClause(), $whereColumnsInfo, $this->getOrderBy());

        $pgResults = $q->execute();
        if (pg_numrows($pgResults) > 0) {
            // there will be 1 record for each role
            $roles = [];
            $rolesTableMapper = RolesTableMapper::getInstance();
            while ($row = pg_fetch_assoc($pgResults)) {
                $roles[] = $rolesTableMapper->getObjectById((int) $row['role_id']);
                $lastRow = $row;
            }

            return $this->buildAdministrator((int) $lastRow['id'], $lastRow['name'], $lastRow['username'], $lastRow['password_hash'], Postgres::convertPostgresBoolToBool($lastRow['active']), new \DateTimeImmutable($lastRow['created']), $roles);
        } else {
            return null;
        }
    }

    public function buildAdministrator(int $id, string $name, string $username, string $passwordHash, bool $active, \DateTimeImmutable $created, array $roles, ?AuthenticationService $authentication = null, ?AuthorizationService $authorization = null)
    {
        return new Administrator($id, $name, $username, $passwordHash, $roles, $active, $created, $authentication, $authorization);
    }

    public function getObjectById(int $id): ?Administrator
    {
        $whereColumnsInfo = [
            'administrators.id' => [
                'operators' => ["="],
                'values' => [$id]
            ]
        ];
        return $this->getObject($whereColumnsInfo);
    }

    public function getObjectByUsername(string $username, bool $activeOnly = true): ?Administrator
    {
        $whereColumnsInfo = [
            'administrators.username' => [
                'operators' => ["="],
                'values' => [$username]
            ]
        ];
        if ($activeOnly) {
            $whereColumnsInfo['administrators.active'] = [
                'operators' => ["="],
                'values' => [Postgres::BOOLEAN_TRUE]
            ];
        }
        return $this->getObject($whereColumnsInfo);
    }

    /** return array of records or null */
    public function select(?string $columns = "*", ?array $whereColumnsInfo = null, ?string $orderBy = null): ?array
    {
        if ($whereColumnsInfo != null) {
            $this->validateWhere($whereColumnsInfo);
        }

        /** simply adding to the where clause below with the roles field will yield incomplete results, as not all roles for an administrator will be selected, so the subquery fn is called */
        if (is_array($whereColumnsInfo) && array_key_exists('roles.role', $whereColumnsInfo)) {
            return $this->selectWithRoleSubquery($columns, $whereColumnsInfo, $orderBy);
        }

        $columns = $columns ?? $this->defaultSelectColumnsString;
        $orderBy = $orderBy ?? $this->getOrderBy();

        $q = new SelectBuilder("SELECT $columns", $this->getFromClause(), $whereColumnsInfo, $orderBy);
        $pgResult = $q->execute();
        if (!$results = pg_fetch_all($pgResult)) {
            $results = null;
        }
        pg_free_result($pgResult);
        return $results;
    }

    /** to filter the administrators with certain roles and return all the roles the administrators have */
    private function selectWithRoleSubquery(?string $columns = null, array $whereColumnsInfo = null, string $orderBy = null)
    {
        $columns = $columns ?? $this->defaultSelectColumnsString;

        /** start subquery */
        $q = new QueryBuilder("SELECT $columns ".$this->getFromClause()." WHERE administrators.id IN (SELECT administrators.id FROM administrators JOIN administrator_roles ON administrators.id=administrator_roles.administrator_id JOIN roles ON administrator_roles.role_id=roles.id WHERE");

        /** build subquery */
        $opCount = 0;
        foreach ($whereColumnsInfo['roles.role']['operators'] as $op) {
            $sqlVarCount = $opCount + 1;
            if ($opCount > 0) {
                $q->add(" OR ");
            }
            $q->add(" roles.role $op $$sqlVarCount", $whereColumnsInfo['roles.role']['values'][$opCount]);
            ++$opCount;
        }
        $q->add(") ORDER BY " . $this->getOrderBy());

        return $q->execute();
    }

    /* returns key of results array for matching 'id' key, null if not found
     * note, careful when checking return value as 0 can be returned (evaluates to false)
     */
    private function getAdministratorsArrayKeyForId(array $administratorsArray, int $id): ?int
    {
        foreach ($administratorsArray as $key => $administrator) {
            if ($administrator['id'] == $id) {
                return $key;
            }
        }

        return null;
    }

    /** returns array of results instead of recordset */
    private function selectArray(?string $selectColumns = null, array $whereColumnsInfo = null, string $orderBy = null): array
    {
        $columns = $selectColumns ?? $this->defaultSelectColumnsString;

        $administratorsArray = []; // populate with 1 entry per administrator with an array of roles

        if(null !== $records = $this->select($columns, $whereColumnsInfo, $orderBy)) {
            $rolesTableMapper = RolesTableMapper::getInstance();
            foreach ($records as $record) {
                // either add new administrator or just new role based on whether administrator already exists
                if (null === $key = $this->getAdministratorsArrayKeyForId($administratorsArray, (int) $record['id'])) {
                    $administratorsArray[] = [
                        'id' => (int) $record['id'],
                        'name' => $record['name'],
                        'username' => $record['username'],
                        'passwordHash' => $record['password_hash'],
                        'roles' => [$rolesTableMapper->getObjectById((int) $record['role_id'])],
                        'active' => Postgres::convertPostgresBoolToBool($record['active']),
                        'created' => new \DateTimeImmutable($record['created']),
                    ];
                } else {
                    array_push($administratorsArray[$key]['roles'], $rolesTableMapper->getObjectById((int) $record['role_id']));
                }
            }
        }

        return $administratorsArray;
    }

    public function getObjects(array $whereColumnsInfo = null, string $orderBy = null, AuthenticationService $authentication, AuthorizationService $authorization): array
    {
        $administrators = [];
        foreach ($this->selectArray(null, $whereColumnsInfo, $orderBy) as $administratorArray) {
            $administrators[] = $this->buildAdministrator($administratorArray['id'], $administratorArray['name'], $administratorArray['username'], $administratorArray['passwordHash'], $administratorArray['active'], $administratorArray['created'], $administratorArray['roles'], $authentication, $authorization);
        }

        return $administrators;
    }

    // instead of having roles returned as an array, it will be returned as a string
    public function selectArrayWithRolesString(string $columns = "*", array $whereColumnsInfo = null): array
    {
        $administrators = [];
        $results = $this->selectArray($columns, $whereColumnsInfo);
        foreach ($results as $index => $administrator) {
            $administrators[$index] = $administrator;
            $administrators[$index]['roles'] = implode(", ", $administrators[$index]['roles']);
        }
        return $administrators;
    }

    /** does validation for delete then deletes and returns deleted username */
    public function delete(int $id, AuthenticationService $authentication, AuthorizationService $authorization): string
    {
        // make sure there is an administrator for the primary key
        if (null === $administrator = $this->getObjectById($id)) {
            throw new Exceptions\QueryResultsNotFoundException();
        }

        $administrator->setAuth($authentication, $authorization);

        if (!$administrator->isDeletable()) {
            throw new Exceptions\UnallowedActionException($administrator->getNotDeletableReason());
        }

        $this->doDeleteTransaction($id);

        $username = $administrator->getUsername();
        unset($administrator);

        return $username;
    }

    // any necessary validation should be performed prior to calling
    private function doDeleteTransaction(int $administratorId)
    {
        pg_query("BEGIN");
        try {
            $this->doDeleteAdministratorRoles($administratorId);
        } catch (\Exception $e) {
            pg_query("ROLLBACK");
            throw $e;
        }
        try {
            $this->administratorsTableMapper->delete($administratorId);
        } catch (\Exception $e) {
            pg_query("ROLLBACK");
            throw $e;
        }
        pg_query("COMMIT");
    }

    /** deletes the record(s) in the join table */
    private function doDeleteAdministratorRoles(int $administratorId)
    {
        $q = new QueryBuilder("DELETE FROM ".self::ADM_ROLES_TABLE_NAME." WHERE administrator_id = $1", $administratorId);
        $q->execute();
    }

    /** deletes a record in the join table and returns the id */
    /** returns null if not found */
    private function doDeleteAdministratorRole(int $administratorId, int $roleId): ?int
    {
        $q = new QueryBuilder("DELETE FROM ".self::ADM_ROLES_TABLE_NAME." WHERE administrator_id = $1 AND role_id = $2", $administratorId, $roleId);
        try {
            $deletedId = $q->executeWithReturnField('id');
        } catch (Exceptions\QueryResultsNotFoundException $e) {
            return null;
        }
        return (int) $deletedId;
    }

    public function doUpdate(int $administratorId, array $changedFields)
    {
        $changedAdministratorFields = $this->administratorsTableMapper->getChangedFields($changedFields);

        pg_query("BEGIN");
        if (count($changedAdministratorFields) > 0) {
            try {
                $this->administratorsTableMapper->updateByPrimaryKey($changedAdministratorFields, $administratorId, false);
            } catch (\Exception $e) {
                pg_query("ROLLBACK");
                throw $e;
            }
        }
        if (isset($changedFields['roles']['add'])) {
            foreach ($changedFields['roles']['add'] as $addRoleId) {
                try {
                    $this->doInsertAdministratorRole($administratorId, (int) $addRoleId);
                } catch (\Exception $e) {
                    pg_query("ROLLBACK");
                    throw $e;
                }
            }
        }
        if (isset($changedFields['roles']['remove'])) {
            foreach ($changedFields['roles']['remove'] as $deleteRoleId) {
                try {
                    $roleDeleteResult = $this->doDeleteAdministratorRole($administratorId, (int) $deleteRoleId);
                } catch (\Exception $e) {
                    pg_query("ROLLBACK");
                    throw $e;
                }
                if ($roleDeleteResult === null) {
                    pg_query("ROLLBACK");
                    throw new \Exception("Role not found for administrator during delete attempt");
                }
            }
        }
        pg_query("COMMIT");
    }
}


<?php
declare(strict_types=1);

namespace Templates\Admin\Lists;

use Slim\Router;

abstract class ListTemplate
{
    protected $title;
    protected $router;
    protected $columnCount;
    protected $deletesPermitted;
    protected $displayItems;
    protected $insertLink;
    protected $sortColumn;
    protected $sortByAsc;
    protected $filterFormActionRoute;
    protected $filterOpsList;
    protected $filterValue;
    protected $filterErrorMessage;
    protected $filterFieldName;
    protected $isFiltered;
    protected $resetFilterRoute;
    protected $csrfNameKey;
    protected $csrfName;
    protected $csrfValueKey;
    protected $csrfValue;
    protected $updatesPermitted;
    protected $updateColumn;
    protected $updateRoute;
    protected $deleteRoute;

    protected $headerFields;

    protected $numResults;

    public function __construct(string $title, Router $router, int $columnCount, bool $deletesPermitted, ?array $displayItems, ?array $insertLinkInfo, string $sortColumn, bool $sortByAsc, string $filterFormActionRoute, string $filterOpsList, string $filterValue, string $filterErrorMessage, string $filterFieldName, bool $isFiltered, string $resetFilterRoute, string $csrfNameKey, string $csrfName, string $csrfValueKey, string $csrfValue, bool $updatesPermitted, ?string $updateColumn, ?string $updateRoute, ?string $deleteRoute, array $headerFields)
    {
        $this->title = $title;
        $this->router = $router;
        $this->columnCount = $columnCount;
        $this->deletesPermitted = $deletesPermitted;
        $this->displayItems = $displayItems;
        $this->insertLinkInfo = $insertLinkInfo;
        $this->sortColumn = $sortColumn;
        $this->sortByAsc = $sortByAsc;
        $this->filterFormActionRoute = $filterFormActionRoute;
        $this->filterOpsList = $filterOpsList;
        $this->filterValue = $filterValue;
        $this->filterErrorMessage = $filterErrorMessage;
        $this->filterFieldName = $filterFieldName;
        $this->isFiltered = $isFiltered;
        $this->resetFilterRoute = $resetFilterRoute;
        $this->csrfNameKey = $csrfNameKey;
        $this->csrfName = $csrfName;
        $this->csrfValueKey = $csrfValueKey;
        $this->csrfValue = $csrfValue;
        $this->updatesPermitted = $updatesPermitted;
        $this->updateColumn = $updateColumn;
        $this->updateRoute = $updateRoute;
        $this->deleteRoute = $deleteRoute;

        $this->headerFields = $headerFields;

        $this->numResults = $displayItems === null ? 0 : count($this->displayItems);
    }

    public function getStartMain(): string
    {
        $startMain = <<< EOT

EOT;

$colspan = $this->columnCount;
if ($this->deletesPermitted) {
    $colspan++;
}

$insertLinkHtml = ($this->insertLinkInfo != null) ? 'router->pathFor($this->insertLinkInfo['route']).'">'.$this->insertLinkInfo['text'].'' : '';
$filterForm = $this->getFilterForm();

$startMain .= <<< EOT

                        $this->title ($this->numResults)
                        $insertLinkHtml
                        $filterForm

EOT;

if ($this->numResults > 0) {
    $startMain .= '
';
    foreach ($this->headerFields as $headerKey) {
        $sortClass = ($this->sortByAsc) ? 'sorttable_sorted' : 'sorttable_sorted_reverse';
        $thClass = ($headerKey == $this->sortColumn) ? $sortClass : '';
        $startMain .= ''.$headerKey.'	';
    }

    if ($this->deletesPermitted) {
        $startMain .= 'X	';
    }
    $startMain .= '
';
}

$startMain .= <<< EOT

EOT;
        return $startMain;
    }

    private function getFilterForm(): string
    {
        $ffAction = $this->router->pathFor($this->filterFormActionRoute);
        $ffError = (mb_strlen($this->filterErrorMessage) > 0) ? ''.$this->filterErrorMessage.'' : '';
        $ffReset = ($this->isFiltered) ? 'router->pathFor($this->resetFilterRoute).'">reset' : '';

        $filterValue = htmlspecialchars($this->filterValue, ENT_QUOTES|ENT_HTML5);
        $filterForm = <<< EOT
<form name="filter" method="post" style="display: inline" action="$ffAction">
    SELECT WHERE
    <input type="text" name="$this->filterFieldName" value="$filterValue" size="58" maxlength="500" placeholder="field1:op:val1[,field2...] op in [$this->filterOpsList]" required>
    <input type="submit" value="Filter">
    $ffError
    $ffReset
    <input type="hidden" name="$this->csrfNameKey" value="$this->csrfName">
    <input type="hidden" name="$this->csrfValueKey" value="$this->csrfValue">
</form>
EOT;
        return $filterForm;
    }

    public function getResultsRows(): string
    {
        $resultsRows = '';
        $rowCount = 0;

        if ($this->numResults > 0) {
            foreach ($this->displayItems as $row) {
                // row is either results array or object
                $rowCount++;
                $resultsRows .= $this->getBodyRow($row, $rowCount);
            }
        } else {
            $resultsRows .= '
No results
';
        }

        return $resultsRows;
    }

    protected function getCell(string $fieldName, string $fieldValue, bool $showUpdateLink, ?string $primaryKeyValue): string
    {
        if ($showUpdateLink && $this->updateRoute == null) {
            throw new \Exception("Must have updateRoute");
        }

        if ($showUpdateLink && $primaryKeyValue === null) {
            throw new \Exception("Must have primaryKeyValue");
        }

        $fieldOutput = htmlentities($fieldValue, ENT_QUOTES | ENT_HTML5);
        // either the update link or just the value
        $cellValue = ($fieldName == $this->updateColumn && $showUpdateLink) ? 'router->pathFor($this->updateRoute, ["primaryKey" => $primaryKeyValue]).'" title="update">'.$fieldOutput.'' : $fieldOutput;

        return ''.$cellValue.'	';
    }

    /** the delete column - either delete link or blank space */
    protected function getDeleteCell(bool $showDeleteLink, ?string $primaryKeyValue): string
    {
        if ($showDeleteLink && $this->deleteRoute == null) {
            throw new \Exception("Must have deleteRoute");
        }
        if ($showDeleteLink && $primaryKeyValue === null) {
            throw new \Exception("Must have primaryKeyValue to delete");
        }

        $cellValue = ($showDeleteLink) ? 'router->pathFor($this->deleteRoute, ["primaryKey" => $primaryKeyValue]).'" title="delete" onclick="return confirm(\'Are you sure you want to delete '.$primaryKeyValue.'?\');">X' : ' ';

        return ''.$cellValue.'	';
    }

    protected function getBodyRowCommon(int $rowNumber, array $fields, bool $showUpdateLink, bool $showDeleteLink, ?string $primaryKeyValue): string
    {

        $bodyRow = '
';
        foreach ($fields as $fieldName => $fieldValue) {
            $bodyRow .= $this->getCell($fieldName, (string) $fieldValue, $showUpdateLink, $primaryKeyValue);
        }
        if ($this->deletesPermitted) {
            $bodyRow .= $this->getDeleteCell($showDeleteLink, $primaryKeyValue);
        }
        $bodyRow .= '
';

        return $bodyRow;
    }

}


<?php
declare(strict_types=1);

namespace Entities\Permissions\View;

use Infrastructure\SlimPostgres;
use Infrastructure\BaseEntity\BaseMVC\View\ObjectsListViews;
use Infrastructure\BaseEntity\BaseMVC\View\AdminListView;
use Infrastructure\BaseEntity\BaseMVC\View\InsertUpdateViews;
use Infrastructure\BaseEntity\BaseMVC\View\ResponseUtilities;
use Entities\Permissions\Model\PermissionsEntityMapper;
use Entities\Permissions\Model\PermissionsTableMapper;
use Entities\Permissions\View\Forms\PermissionInsertForm;
use Entities\Permissions\View\Forms\PermissionUpdateForm;
use Exceptions\QueryFailureException;
use Slim\Container;
use Slim\Http\Request;
use Slim\Http\Response;

class PermissionsView extends AdminListView implements ObjectsListViews, InsertUpdateViews
{
    use ResponseUtilities;

    private $permissionsEntityMapper;
    private $permissionsTableMapper;
    const FILTER_FIELDS_PREFIX = 'permissions';

    public function __construct(Container $container)
    {
        $this->routePrefix = ROUTEPREFIX_PERMISSIONS;
        $this->permissionsEntityMapper = PermissionsEntityMapper::getInstance();
        $this->permissionsTableMapper = PermissionsTableMapper::getInstance();

        parent::__construct($container, self::FILTER_FIELDS_PREFIX, ROUTE_ADMINISTRATORS_PERMISSIONS, $this->permissionsEntityMapper, ROUTE_ADMINISTRATORS_PERMISSIONS_RESET, 'Admin/Lists/objectsList.php');

        $this->setInsert();
        $this->setUpdate();
        $this->setDelete();
    }

    protected function getResource(string $which): string
    {
        switch ($which) {
            case 'insert':
                return PERMISSIONS_INSERT_RESOURCE;
                break;
            case 'update':
                return PERMISSIONS_UPDATE_RESOURCE;
                break;
            case 'delete':
                return PERMISSIONS_DELETE_RESOURCE;
                break;
            default:
                throw new \InvalidArgumentException("Undefined resource $which");
        }
    }

    /** overrides in order to get objects and send to indexView */
    public function routeIndex(Request $request, Response $response, $args)
    {
        return $this->indexViewObjects($response);
    }

    /** overrides in order to get objects and send to indexView */
    public function routeIndexResetFilter(Request $request, Response $response, $args)
    {
        // redirect to the clean url
        return $this->indexViewObjects($response, true);
    }

    public function routeGetInsert(Request $request, Response $response, $args)
    {
        return $this->insertView($request, $response, $args);
    }

    /** this can be called for both the initial get and the posted form if errors exist (from controller) */
    public function insertView(Request $request, Response $response, $args)
    {
        $formAction = $this->router->pathFor(SlimPostgres::getRouteName(true, $this->routePrefix, 'insert', 'post'));
        $fieldValues = ($request->isPost() && isset($args[SlimPostgres::USER_INPUT_KEY])) ? $args[SlimPostgres::USER_INPUT_KEY] : [];

        return $this->view->render(
            $response,
            'Admin/form.php',
            [
                'title' => $this->permissionsEntityMapper->getInsertTitle(),
                'form' => (new PermissionInsertForm($formAction, $this->container, $fieldValues))->getForm(),
                'navigationItems' => $this->navigationItems
            ]
        );
    }

    public function routeGetUpdate(Request $request, Response $response, $args)
    {
        return $this->updateView($request, $response, $args);
    }

    /** this can be called for both the initial get and the posted form if errors exist (from controller) */
    public function updateView(Request $request, Response $response, $args)
    {
        // make sure there is a permission for the primary key
        if (null === $permission = $this->permissionsEntityMapper->getObjectById((int) $args[ROUTEARG_PRIMARY_KEY])) {
            return $this->databaseRecordNotFound($response, $args[ROUTEARG_PRIMARY_KEY], PermissionsTableMapper::getInstance(), 'update');
        }

        $formAction = $this->router->pathFor(SlimPostgres::getRouteName(true, $this->routePrefix, 'update', 'put'), [ROUTEARG_PRIMARY_KEY => $args[ROUTEARG_PRIMARY_KEY]]);

        /** if input set we are redisplaying after invalid form submission */
        if ($request->isPut() && isset($args[SlimPostgres::USER_INPUT_KEY])) {
            $updateForm = new PermissionUpdateForm($formAction, $this->container, $args[SlimPostgres::USER_INPUT_KEY]);
        } else {
            $updateForm = new PermissionUpdateForm($formAction, $this->container);
            $updateForm->setFieldValuesToPermission($permission);
        }

        return $this->view->render(
            $response,
            'Admin/form.php',
            [
                'title' => $this->permissionsEntityMapper->getUpdateTitle(),
                'form' => $updateForm->getForm(),
                'primaryKey' => $args[ROUTEARG_PRIMARY_KEY],
                'navigationItems' => $this->navigationItems,
                'hideFocus' => true
            ]
        );
    }

    /** get permissions objects and send to parent indexView */
    public function indexViewObjects(Response $response, bool $resetFilter = false)
    {
        if ($resetFilter) {
            return $this->resetFilter($response, $this->indexRoute);
        }

        try {
            $permissions = $this->permissionsEntityMapper->getObjects($this->getFilterColumnsInfo());
        } catch (QueryFailureException $e) {
            $permissions = [];
            // warning event is inserted when query fails
            SlimPostgres::setAdminNotice('Query Failed', 'failure');
        }

        return $this->indexView($response, $permissions);
    }
}


<?php

namespace Whitelist;

use Whitelist\Definition\IDefinition;

/**
 * Main class for checking values against a whitelist. It provides a method to
 * set up the whitelist and a method to match arbitrary string against the
 * whitelist.
 *
 * @author Sam Stenvall <neggelandia@gmail.com>
 * @copyright Copyright © Sam Stenvall 2014-
 * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
 */
class Check
{

	/**
	 * @var IDefinition[] the whitelist definitions
	 */
	private $_definitions = array();

	/**
	 * Parses the whitelist definitions into respective objects
	 * @param array $whitelist list of definition strings
	 * @throws \InvalidArgumentException if the definition type couldn't be
	 * determined
	 */
	public function whitelist(array $whitelist)
	{
		$this->_definitions = array();

		foreach ($whitelist as $definition)
		{
			// Pre-configured object
			if (is_object($definition))
			{
				if ($definition instanceof Definition\IDefinition)
					$definitionObject = $definition;
				else
					throw new \InvalidArgumentException('Definition objects must implement IDefinition');
			}
			// IPv4 address
			elseif (preg_match('/[a-z:\/]/', $definition) === 0)
				$definitionObject = new Definition\IPv4Address($definition);
			// IPv4 CIDR notation
			elseif (preg_match('/[a-z:]/', $definition) === 0)
				$definitionObject = new Definition\IPv4CIDR($definition);
			// IPv6 address
			elseif (preg_match('/^[0-9a-f:]+$/', $definition))
				$definitionObject = new Definition\IPv6Address($definition);
			// IPv6 CIDR notation
			elseif (preg_match('/^[0-9a-f:\/]+$/', $definition))
				$definitionObject = new Definition\IPv6CIDR($definition);
			// Wildcard domain
			elseif (preg_match('/^\*\.[\w\.\-]+$/', $definition))
				$definitionObject = new Definition\WildcardDomain($definition);
			// Domain
			elseif (preg_match('/^[\w\.\-]+$/', $definition))
				$definitionObject = new Definition\Domain($definition);
			else
				throw new \InvalidArgumentException('Unable to parse definition "'.$definition.'"');

			$this->_definitions[] = $definitionObject;
		}
	}

	/**
	 * Checks the specified value against all configured definitions and
	 * returns true if at least one definition considers it a match
	 * @param string $value the value to be checked
	 * @return boolean
	 */
	public function check($value)
	{
		foreach ($this->_definitions as $definition)
			if ($definition->match($value))
				return true;

		return false;
	}

}


<?php

namespace BenTools\GuzzleHttp\Middleware\Storage;

class Counter implements \Serializable, \JsonSerializable, \Countable
{
    /**
     * @var bool
     */
    private $useMicroseconds;

    /**
     * @var float
     */
    private $expiresIn;

    /**
     * @var float
     */
    private $expiresAt;

    /**
     * @var int
     */
    private $counter;

    /**
     * Counter constructor.
     * @param float $expiresIn
     */
    public function __construct(float $expiresIn)
    {
        $this->useMicroseconds = intval($expiresIn) != $expiresIn;
        $this->expiresIn = $this->useMicroseconds ? $expiresIn : intval($expiresIn);
        $this->reset();
    }

    private function now()
    {
        return $this->useMicroseconds ? microtime(true) : time();
    }

    public function reset()
    {
        $this->counter = 0;
        $this->expiresAt = null;
    }

    /**
     * Increment counter.
     */
    public function increment()
    {
        $this->counter++;
        if (1 === $this->counter) {
            $this->expiresAt = $this->now() + $this->expiresIn;
        }
    }

    /**
     * @return int
     */
    public function count(): int
    {
        if ($this->isExpired()) {
            $this->reset();
        }
        return $this->counter;
    }

    /**
     * @return int|float
     */
    public function getRemainingTime()
    {
        $remainingTime = (float) max(0, $this->expiresAt - $this->now());
        if (false === $this->useMicroseconds) {
            $remainingTime = ceil($remainingTime);
        }
        return $remainingTime;
    }

    /**
     * @return bool
     */
    public function isExpired()
    {
        return null !== $this->expiresAt && 0.0 === $this->getRemainingTime();
    }

    /**
     * @inheritDoc
     */
    public function serialize()
    {
        return json_encode($this);
    }

    /**
     * @inheritDoc
     */
    public function unserialize($serialized)
    {
        $data = json_decode($serialized, true);
        $this->expiresAt = $data['e'];
        $this->expiresIn = $data['i'];
        $this->counter = $data['n'];
        $this->useMicroseconds = $data['m'];
    }

    /**
     * @inheritDoc
     */
    public function jsonSerialize()
    {
        return [
            'm' => $this->useMicroseconds,
            'i' => $this->expiresIn,
            'e' => $this->expiresAt,
            'n' => $this->counter,
        ];
    }

    public function __debugInfo()
    {
        return [
            'counter' => $this->counter,
            'microseconds' => $this->useMicroseconds,
            'expiresIn' => $this->expiresIn,
            'expiresAt' => $this->expiresAt,
            'now' => $this->now(),
            'remaining' => $this->useMicroseconds ? $this->getRemainingTime() : round($this->getRemainingTime()),
            'expired' => $this->isExpired(),
        ];
    }
}


<?php

/**
 *	This extension will automatically be applied to existing and configuration defined tag types, and will help consolidate these into fusion tags.
 *	@author Nathan Glasl <nathan@symbiote.com.au>
 */

class FusionExtension extends DataExtension {

	private static $has_one = array(
		'FusionTag' => 'FusionTag'
	);

	/**
	 *	Restrict access when deleting tags.
	 */

	public function canDelete($member) {

		return false;
	}

	/**
	 *	Hide the fusion tag.
	 */

	public function updateCMSFields(FieldList $fields) {

		$fields->removeByName('FusionTagID');
	}

	/**
	 *	Confirm that the tag has been given a title and doesn't already exist.
	 */

	public function validate(ValidationResult $result) {

		// Determine the field to use, based on the configuration defined tag types.

		$validate = 'Title';
		$class = $this->owner->ClassName;
		foreach(Config::inst()->get('FusionService', 'custom_tag_types') as $type => $field) {
			if($type === $class) {
				$validate = $field;
			}
		}

		// Confirm that the tag has been given a title and doesn't already exist.

		if($result->valid() && !$this->owner->$validate) {
			$result->error("\"{$validate}\" required!");
		}

		// Allow extension.

		$this->owner->extend('validateFusionExtension', $result);
		return $result;
	}

	/**
	 *	Update the fusion tag to reflect the change.
	 */

	public function onAfterWrite() {

		parent::onAfterWrite();

		// Determine the field to use, based on the configuration defined tag types.

		$write = 'Title';
		$class = $this->owner->ClassName;
		foreach(Config::inst()->get('FusionService', 'custom_tag_types') as $type => $field) {
			if($type === $class) {
				$write = $field;
			}
		}

		// Determine whether there's an existing fusion tag.

		$changed = $this->owner->getChangedFields();
		$existing = FusionTag::get()->filter('Title', $this->owner->$write)->first();
		if(is_null($this->owner->FusionTagID) && !$existing) {

			// There is no fusion tag, therefore instantiate one using this tag.

			$fusion = FusionTag::create();
			$fusion->Title = $this->owner->$write;
			$fusion->TagTypes = serialize(array(
				$class => $class
			));
			$fusion->write();

			// Update this tag to point to the fusion tag.

			$this->owner->FusionTagID = $fusion->ID;
			$this->owner->write();
		}
		else if(is_null($this->owner->FusionTagID) && $existing) {

			// There is a fusion tag, therefore append this tag type.

			$types = unserialize($existing->TagTypes);
			$types[$class] = $class;
			$existing->TagTypes = serialize($types);
			$existing->write();

			// Update this tag to point to the fusion tag.

			$this->owner->FusionTagID = $existing->ID;
			$this->owner->write();
		}
		else if(isset($changed[$write]) && !isset($changed['FusionTagID']) && $existing && ($existing->ID != $this->owner->FusionTagID)) {

			// Update the fusion tag to remove this tag type.

			$fusion = FusionTag::get()->byID($this->owner->FusionTagID);
			$types = unserialize($fusion->TagTypes);
			unset($types[$this->owner->ClassName]);
			$fusion->TagTypes = !empty($types) ? serialize($types) : null;
			$fusion->write();

			// There is an existing fusion tag, therefore append this tag type.

			$types = unserialize($existing->TagTypes);
			$types[$class] = $class;
			$existing->TagTypes = serialize($types);
			$existing->write();

			// Update this tag to point to the new fusion tag.

			$this->owner->FusionTagID = $existing->ID;
			$this->owner->write();
		}

		// Determine whether this tag has been updated.

		else if(isset($changed[$write]) && !isset($changed['FusionTagID']) && ($existing = FusionTag::get()->byID($this->owner->FusionTagID))) {

			// There is an update, therefore update the existing fusion tag to reflect the change.

			$existing->Title = $changed[$write]['after'];
			$existing->write();
		}
	}

	/**
	 *	Update the fusion tag to remove this tag type.
	 */

	public function onAfterDelete() {

		parent::onAfterDelete();
		$fusion = FusionTag::get()->byID($this->owner->FusionTagID);
		$types = unserialize($fusion->TagTypes);
		unset($types[$this->owner->ClassName]);
		$fusion->TagTypes = !empty($types) ? serialize($types) : null;
		$fusion->write();
	}

}


<?php

/**
 *	This extension will automatically be applied, allowing CMS searchable content tagging.
 *	@author Nathan Glasl <nathan@symbiote.com.au>
 */

class CMSMainTaggingExtension extends Extension {

	/**
	 *	Update the page filtering, allowing CMS searchable content tagging.
	 */

	public function updateSearchForm($form) {

		// Update the page filtering, allowing multiple tags.

		Requirements::javascript(FUSION_PATH . '/javascript/fusion.js');

		// Instantiate a field containing the existing tags.

		$form->Fields()->insertBefore(ListboxField::create(
			'q[Tagging]',
			'Tags',
			FusionTag::get()->map('Title', 'Title')->toArray(),
			(($filtering = $this->owner->getRequest()->getVar('q')) && isset($filtering['Tagging'])) ? $filtering['Tagging'] : array(),
			null,
			true
		), 'q[Term]');

		// Allow extension.

		$this->owner->extend('updateCMSMainTaggingExtensionSearchForm', $form);
	}

}


<?php

/**
 *	This extension will automatically be applied to pages, allowing searchable content tagging.
 *	@author Nathan Glasl <nathan@symbiote.com.au>
 */

class TaggingExtension extends DataExtension {

	/**
	 *	The tagging will be directly stored in a database field, allowing search without needing to parse the fusion tags relationship.
	 */

	private static $db = array(
		'Tagging' => 'Text'
	);

	/**
	 *	The tagging will need to use a unique relationship name, otherwise there are issues around configuration merge priority.
	 */

	private static $many_many = array(
		'FusionTags' => 'FusionTag'
	);

	/**
	 *	Update the searchable fields and model admin filtering, allowing CMS searchable content tagging.
	 */

	public function updateSearchableFields(&$fields) {

		// Instantiate a field containing the existing tags.

		$fields = array_merge(array(
			'Tagging' => array(
				'title' => 'Tags',
				'field' => ListboxField::create(
					'Tagging',
					'Tags',
					FusionTag::get()->map('Title', 'Title')->toArray(),
					(Controller::has_curr() && ($filtering = Controller::curr()->getRequest()->getVar('q')) && isset($filtering['Tagging'])) ? $filtering['Tagging'] : array(),
					null,
					true
				),
				'filter' => $this->owner->dbObject('Tagging')->stat('default_search_filter_class')
			)
		), $fields);

		// Allow extension.

		$this->owner->extend('updateTaggingExtensionSearchableFields', $fields);
	}

	/**
	 *	Display the appropriate tagging field.
	 */

	public function updateCMSFields(FieldList $fields) {

		$fields->removeByName('FusionTags');

		// Determine whether consolidated tags are found in the existing relationships.

		$types = array();
		foreach(singleton('FusionService')->getFusionTagTypes() as $type => $field) {
			$types[$type] = $type;
		}
		$types = array_intersect($this->owner->many_many(), $types);
		if(empty($types)) {

			// There are no consolidated tags found, therefore instantiate a tagging field.

			$fields->addFieldToTab('Root.Tagging', ListboxField::create(
				'FusionTags',
				'Tags',
				FusionTag::get()->map()->toArray()
			)->setMultiple(true));
		}

		// Allow extension.

		$this->owner->extend('updateTaggingExtensionCMSFields', $fields);
	}

	/**
	 *	Update the tagging to reflect the change, allowing searchable content.
	 */

	public function onBeforeWrite() {

		parent::onBeforeWrite();

		// Determine whether consolidated tags are found in the existing relationships.

		$types = array();
		foreach(singleton('FusionService')->getFusionTagTypes() as $type => $field) {
			$types[$type] = $type;
		}
		$types = array_intersect($this->owner->many_many(), $types);
		if(empty($types)) {

			// There are no consolidated tags found, therefore update the tagging based on the fusion tags.

			$tagging = array();
			foreach($this->owner->FusionTags() as $tag) {
				$tagging[] = $tag->Title;
			}
		}
		else {

			// Empty the fusion tags to begin.

			$this->owner->FusionTags()->removeAll();

			// There are consolidated tags found, therefore update the tagging based on these.

			$tagging = array();
			foreach($types as $relationship => $type) {
				foreach($this->owner->$relationship() as $tag) {

					// Update both the fusion tags and tagging (using the relationship causes caching issues).

					$fusion = FusionTag::get()->byID($tag->FusionTagID);
					$this->owner->FusionTags()->add($fusion);
					$tagging[] = $fusion->Title;
				}
			}
		}
		$this->owner->Tagging = implode(' ', $tagging);
	}

}


<?php

/**
 *	Tags that consolidate existing and configuration defined tag types.
 *	@author Nathan Glasl <nathan@symbiote.com.au>
 */

class FusionTag extends DataObject {

	private static $db = array(
		'Title' => 'Varchar(255)',
		'TagTypes' => 'Text'
	);

	private static $default_sort = 'Title';

	private static $dependencies = array(
		'service' => '%$FusionService'
	);

	/**
	 *	The process to automatically consolidate existing and configuration defined tag types, executed on project build.
	 */

	public function requireDefaultRecords() {

		parent::requireDefaultRecords();

		// This isn't required during testing.

		if(Controller::has_curr() && (Controller::curr() instanceof TestRunner)) {
			return;
		}

		// Retrieve existing and configuration defined tag types that have not been consolidated.

		$wrapped = array();
		foreach($this->service->getFusionTagTypes() as $type => $field) {
			if(($tags = $type::get()->filter('FusionTagID', 0)) && $tags->exists()) {
				foreach($tags as $tag) {

					// Determine whether there's an existing fusion tag.

					if(!($existing = FusionTag::get()->filter('Title', $tag->$field)->first())) {

						// There is no fusion tag, therefore instantiate one using the current tag.

						$fusion = FusionTag::create();
						$fusion->Title = $tag->$field;
						$fusion->TagTypes = serialize(array(
							$tag->ClassName => $tag->ClassName
						));
						$fusion->write();
						$fusionID = $fusion->ID;
						DB::alteration_message("\"{$tag->$field}\" Fusion Tag", 'created');
					}
					else {

						// There is a fusion tag, therefore append the current tag type.

						$types = unserialize($existing->TagTypes);
						$types[$tag->ClassName] = $tag->ClassName;
						$existing->TagTypes = serialize($types);
						$existing->write();
						$fusionID = $existing->ID;
					}

					// Update the current tag to point to this.

					$tag->FusionTagID = $fusionID;
					$tag->write();
				}
			}

			// The existing and configuration defined tag types need to be wrapped for serialised partial matching.

			$wrapped[] = "\"{$type}\"";
		}

		// Determine whether tag type exclusions have caused any fusion tags to become redundant.

		$tags = FusionTag::get()->where('TagTypes IS NOT NULL');
		if(count($wrapped)) {

			// Determine the fusion tags that only contain tag type exclusions.

			$tags = $tags->exclude('TagTypes:PartialMatch', $wrapped);
		}
		if($tags->exists()) {

			// Determine any data objects with the tagging extension.

			$objects = array();
			$configuration = Config::inst();
			$classes = ClassInfo::subclassesFor('DataObject');
			unset($classes['DataObject']);
			foreach($classes as $class) {

				// Determine the specific data object extensions.

				$extensions = $configuration->get($class, 'extensions', Config::UNINHERITED);
				if(is_array($extensions) && in_array('TaggingExtension', $extensions)) {
					$objects[] = $class;
				}
			}

			// Determine whether these fusion tags are now redundant.

			$mode = Versioned::get_reading_mode();
			Versioned::reading_stage('Stage');
			$types = array();
			$where = array();
			foreach($tags as $tag) {

				// Determine whether this fusion tag is being used.

				$ID = $tag->ID;
				foreach($objects as $object) {
					if($object::get()->filter('FusionTags.ID', $ID)->exists()) {

						// This fusion tag is being used, so keep it.

						continue 2;
					}
				}

				// These fusion tags are now redundant.

				$types = array_merge($types, unserialize($tag->TagTypes));
				$where[] = array(
					'FusionTagID' => $ID
				);
				$tag->delete();
				DB::alteration_message("\"{$tag->Title}\" Fusion Tag", 'deleted');
			}
			Versioned::set_reading_mode($mode);

			// The tag type exclusions need updating to reflect this.

			foreach($types as $exclusion) {
				$query = new SQLUpdate(
					$exclusion,
					array(
						'FusionTagID' => 0
					),
					$where
				);
				$query->useDisjunction();
				$query->execute();
			}
		}
	}

	/**
	 *	Restrict access when deleting fusion tags.
	 */

	public function canDelete($member = null) {

		return false;
	}

	/**
	 *	Display the appropriate fusion tag fields.
	 */

	public function getCMSFields() {

		$fields = parent::getCMSFields();

		// Determine whether the tag types should be displayed.

		$types = array();
		foreach($this->service->getFusionTagTypes() as $type => $field) {
			$types[$type] = $type;
		}
		if(count($types)) {

			// The serialised representation will require a custom field to display correctly.

			$fields->replaceField('TagTypes', $list = ListboxField::create(
				'Types',
				'Tag Types',
				$types
			)->setMultiple(true));

			// Disable existing tag types to prevent deletion.

			$items = is_string($this->TagTypes) ? array_keys(unserialize($this->TagTypes)) : array();
			$list->setValue($items);
			$list->setDisabledItems($items);
		}
		else {

			// There are no existing or configuration defined tag types.

			$fields->removeByName('TagTypes');
		}

		// Allow extension.

		$this->extend('updateFusionTagCMSFields', $fields);
		return $fields;
	}

	/**
	 *	Confirm that the fusion tag has been given a title and doesn't already exist.
	 */

	public function validate() {

		$result = parent::validate();
		$this->Title = strtolower($this->Title);
		if($result->valid() && !$this->Title) {
			$result->error('"Title" required!');
		}
		else if($result->valid() && FusionTag::get_one('FusionTag', array(
			'ID != ?' => $this->ID,
			'Title = ?' => $this->Title
		))) {
			$result->error('Tag already exists!');
		}

		// Allow extension.

		$this->extend('validateFusionTag', $result);
		return $result;
	}

	/**
	 *	Update the tag types with a serialised representation.
	 */

	public function onBeforeWrite() {

		parent::onBeforeWrite();

		// Determine whether new tag types exist.

		$types = $this->Types ? explode(',', $this->Types) : array();

		// Merge the new and existing tag types.

		if(is_string($this->TagTypes)) {
			$types = array_merge($types, array_keys(unserialize($this->TagTypes)));
		}
		if(!empty($types)) {
			sort($types);

			// Update the tag types with a serialised representation.

			$formatted = array();
			$existing = $this->service->getFusionTagTypes();
			foreach($types as $type) {

				// The tag type exclusions need checking.

				if(isset($existing[$type])) {
					$formatted[$type] = $type;
				}
			}
			$this->TagTypes = !empty($formatted) ? serialize($formatted) : null;

			// Update the custom field to reflect the change correctly.

			$this->Types = implode(',', $formatted);
		}
	}

	/**
	 *	Update the existing and configuration defined tag types to reflect the change.
	 */

	public function onAfterWrite() {

		parent::onAfterWrite();

		// Determine the tag types to update.

		$types = unserialize($this->TagTypes);
		$changed = $this->getChangedFields();
		foreach($this->service->getFusionTagTypes() as $type => $field) {
			if(isset($types[$type])) {

				// Determine whether new tag types exist.

				$newTypes = array();
				if(isset($changed['TagTypes'])) {
					$before = unserialize($changed['TagTypes']['before']);
					$after = unserialize($changed['TagTypes']['after']);
					$newTypes = is_array($before) ? array_diff($after, $before) : $after;
				}

				// Determine whether there's an existing tag.

				if((isset($changed['ID']) || isset($newTypes[$type])) && !($type::get()->filter($field, $this->Title)->first())) {

					// There is no tag, therefore instantiate one using this fusion tag.

					$tag = $type::create();
					$tag->$field = $this->Title;
					$tag->FusionTagID = $this->ID;
					$tag->write();
				}

				// Determine whether this fusion tag has been updated.

				else if(!isset($changed['ID']) && isset($changed['Title']) && ($existing = $type::get()->filter($field, $changed['Title']['before']))) {

					// There is an update, therefore update the existing tag/s to reflect the change.

					foreach($existing as $tag) {
						$tag->$field = $changed['Title']['after'];
						$tag->write();
					}
				}
			}
		}

		// Update the searchable content tagging for this fusion tag.

		if(!isset($changed['ID']) && isset($changed['Title'])) {
			$this->service->updateTagging($this->ID);
		}
	}

}


<?php

/**
 *	This provides functionality for the fusion module.
 *	@author Nathan Glasl <nathan@symbiote.com.au>
 */

class FusionService {

	/**
	 *	These custom tag types will be consolidated into fusion tags.
	 */

	private static $custom_tag_types = array();

	/**
	 *	These tag types will not be consolidated into fusion tags.
	 */

	private static $tag_type_exclusions = array();

	/**
	 *	Determine the existing and configuration defined tag types to consolidate.
	 *
	 *	@return array(string, string)
	 */

	public function getFusionTagTypes() {

		// Determine existing tag types.

		$types = array();
		$configuration = Config::inst();

		// The tag type exclusions need checking.

		$exclusions = $configuration->get('FusionService', 'tag_type_exclusions');
		$classes = ClassInfo::subclassesFor('DataObject');
		unset($classes['FusionTag']);
		foreach($classes as $class) {

			// Determine the tag types to consolidate, based on data objects ending with "Tag".

			if((strpos(strrev($class), strrev('Tag')) === 0) && !in_array($class, $exclusions) && !ClassInfo::classImplements($class, 'TestOnly')) {

				// Use the title field as a default.

				$types[$class] = 'Title';
			}
		}

		// Determine configuration defined tag types.

		foreach($configuration->get('FusionService', 'custom_tag_types') as $type => $field) {
			if(in_array($type, $classes) && !in_array($type, $exclusions)) {

				// Use the configuration defined field.

				$types[$type] = $field;
			}
		}
		return $types;
	}

	/**
	 *	Update the searchable content tagging for a specific fusion tag.
	 *
	 *	@parameter <{FUSION_TAG_ID}> integer
	 */

	public function updateTagging($fusionID) {

		// Determine any data objects with the tagging extension.

		$configuration = Config::inst();
		$classes = ClassInfo::subclassesFor('DataObject');
		unset($classes['DataObject']);
		foreach($classes as $class) {

			// Determine the specific data object extensions.

			$extensions = $configuration->get($class, 'extensions', Config::UNINHERITED);
			if(is_array($extensions) && in_array('TaggingExtension', $extensions)) {

				// Determine whether this fusion tag is being used.

				$mode = Versioned::get_reading_mode();
				Versioned::reading_stage('Stage');
				$objects = $class::get()->filter('FusionTags.ID', $fusionID);

				// Update the searchable content tagging for these data objects.

				if($class::has_extension($class, 'Versioned')) {

					// These data objects are versioned.

					foreach($objects as $object) {

						// Update the staging version.

						$object->writeWithoutVersion();
					}
					Versioned::reading_stage('Live');
					$objects = $class::get()->filter('FusionTags.ID', $fusionID);
					foreach($objects as $object) {

						// Update the live version.

						$object->writeWithoutVersion();
					}
				}
				else {

					// These data objects are not versioned.

					foreach($objects as $object) {
						$object->write();
					}
				}
				Versioned::set_reading_mode($mode);
			}
		}
	}

}


<?php

namespace ArgentCrusade\Flysystem\Selectel;

use League\Flysystem\Filesystem;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\ServiceProvider;
use ArgentCrusade\Selectel\CloudStorage\CloudStorage;
use ArgentCrusade\Selectel\CloudStorage\Api\ApiClient;

class SelectelServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap the application services.
     */
    public function boot()
    {
        Storage::extend('selectel', function ($app, $config) {
            $api = new ApiClient($config['username'], $config['password']);
            $api->authenticate();

            $storage = new CloudStorage($api);
            $container = $storage->getContainer($config['container']);

            if (isset($config['container_url'])) {
                $container->setUrl($config['container_url']);
            }

            return new Filesystem(new SelectelAdapter($container));
        });
    }

    /**
     * Register the application services.
     */
    public function register()
    {
        //
    }
}


<?php
/**
 * AlertManager.php
 * Definition of class AlertManager
 *
 * Created 17/12/14 05:15
 *
 * @author Rasanga Perera <rasangaperera@gmail.com>
 * @copyright (c) 2014, The MIT License (MIT)
 */

namespace Ras\Bundle\FlashAlertBundle\Model;

use Symfony\Component\HttpFoundation\Session\Session;

class AlertManager implements AlertManagerInterface
{
    /**
     * @var Session
     */
    private $session;

    /**
     * @param Session $session
     */
    public function __construct(Session $session)
    {
        $this->session = $session;
    }

    /**
     * @inheritdoc
     */
    public function addAlert(AlertInterface $alert)
    {
        $this->session->getFlashBag()->add($alert->getType(), $alert->getMessage());
    }

    /**
     * @inheritdoc
     */
    public function getAlerts()
    {
        $alerts = array();

        foreach (self::getAlertTypes() as $type) {
            $messages = $this->session->getFlashBag()->get($type);

            if (!empty($messages)) {
                $alerts = array_merge($alerts, $this->createAlertsForType($type, $messages)) ;
            }
        }

        return $alerts;
    }

    /**
     * Creates alert objects for a type
     *
     * @param string $type
     * @param array $messages
     * @return Alert[]
     */
    private function createAlertsForType($type, array $messages)
    {
        $alerts = array();

        foreach ($messages as $msg) {
            $alerts[] = new Alert($type, $msg);
        }

        return $alerts;
    }

    /**
     * Gets allowed alert types
     *
     * @return array
     */
    private static function getAlertTypes()
    {
        return array(
            AlertInterface::SUCCESS_ALERT,
            AlertInterface::ERROR_ALERT,
            AlertInterface::WARNING_ALERT,
            AlertInterface::INFO_ALERT
        );
    }
}


<?php

namespace ArgentCrusade\Flysystem\Selectel;

use League\Flysystem\Config;
use League\Flysystem\AdapterInterface;
use League\Flysystem\Adapter\Polyfill\NotSupportingVisibilityTrait;
use ArgentCrusade\Selectel\CloudStorage\Contracts\ContainerContract;
use ArgentCrusade\Selectel\CloudStorage\Exceptions\FileNotFoundException;
use ArgentCrusade\Selectel\CloudStorage\Exceptions\UploadFailedException;
use ArgentCrusade\Selectel\CloudStorage\Exceptions\ApiRequestFailedException;

class SelectelAdapter implements AdapterInterface
{
    use NotSupportingVisibilityTrait;

    /**
     * Storage container.
     *
     * @var \ArgentCrusade\Selectel\CloudStorage\Contracts\ContainerContract
     */
    protected $container;

    /**
     * Create new instance.
     *
     * @param \ArgentCrusade\Selectel\CloudStorage\Contracts\ContainerContract $container
     */
    public function __construct(ContainerContract $container)
    {
        $this->container = $container;
    }

    /**
     * Loads file from container.
     *
     * @param string $path Path to file.
     *
     * @return \ArgentCrusade\Selectel\CloudStorage\Contracts\FileContract
     */
    protected function getFile($path)
    {
        return $this->container->files()->find($path);
    }

    /**
     * Transforms internal files array to Flysystem-compatible one.
     *
     * @param array $files Original Selectel's files array.
     *
     * @return array
     */
    protected function transformFiles($files)
    {
        $result = [];

        foreach ($files as $file) {
            $result[] = [
                'type' => $file['content_type'] === 'application/directory' ? 'dir' : 'file',
                'path' => $file['name'],
                'size' => intval($file['bytes']),
                'timestamp' => strtotime($file['last_modified']),
                'mimetype' => $file['content_type'],
            ];
        }

        return $result;
    }

    /**
     * {@inheritdoc}
     */
    public function has($path)
    {
        return $this->container->files()->exists($path);
    }

    /**
     * {@inheritdoc}
     */
    public function read($path)
    {
        try {
            $file = $this->getFile($path);
        } catch (FileNotFoundException $e) {
            return false;
        }

        $contents = $file->read();

        return compact('contents');
    }

    /**
     * {@inheritdoc}
     */
    public function readStream($path)
    {
        try {
            $file = $this->getFile($path);
        } catch (FileNotFoundException $e) {
            return false;
        }

        $stream = $file->readStream();

        rewind($stream);

        return compact('stream');
    }

    /**
     * {@inheritdoc}
     */
    public function listContents($directory = '', $recursive = false)
    {
        $files = $this->container->files()->withPrefix($directory)->get();
        $result = $this->transformFiles($files);

        return $result;
    }

    /**
     * {@inheritdoc}
     */
    public function getMetadata($path)
    {
        $files = $this->listContents($path);

        return isset($files[0]) ? $files[0] : false;
    }

    /**
     * {@inheritdoc}
     */
    public function getSize($path)
    {
        return $this->getMetadata($path);
    }

    /**
     * {@inheritdoc}
     */
    public function getMimetype($path)
    {
        return $this->getMetadata($path);
    }

    /**
     * {@inheritdoc}
     */
    public function getTimestamp($path)
    {
        return $this->getMetadata($path);
    }

    /**
     * {@inheritdoc}
     */
    public function write($path, $contents, Config $config)
    {
        return $this->writeToContainer('String', $path, $contents);
    }

    /**
     * {@inheritdoc}
     */
    public function writeStream($path, $resource, Config $config)
    {
        return $this->writeToContainer('Stream', $path, $resource);
    }

    /**
     * Writes string or stream to container.
     *
     * @param string          $type    Upload type
     * @param string          $path    File path
     * @param string|resource $payload String content or Stream resource
     *
     * @return array|bool
     */
    protected function writeToContainer($type, $path, $payload)
    {
        try {
            $this->container->{'uploadFrom'.$type}($path, $payload);
        } catch (UploadFailedException $e) {
            return false;
        }

        return $this->getMetadata($path);
    }

    /**
     * {@inheritdoc}
     */
    public function update($path, $contents, Config $config)
    {
        return $this->write($path, $contents, $config);
    }

    /**
     * {@inheritdoc}
     */
    public function updateStream($path, $resource, Config $config)
    {
        return $this->writeStream($path, $resource, $config);
    }

    /**
     * {@inheritdoc}
     */
    public function rename($path, $newpath)
    {
        try {
            $this->getFile($path)->rename($newpath);
        } catch (ApiRequestFailedException $e) {
            return false;
        }

        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function copy($path, $newpath)
    {
        try {
            $this->getFile($path)->copy($newpath);
        } catch (ApiRequestFailedException $e) {
            return false;
        }

        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function delete($path)
    {
        try {
            $this->getFile($path)->delete();
        } catch (ApiRequestFailedException $e) {
            return false;
        }

        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function deleteDir($path)
    {
        try {
            $this->container->deleteDir($path);
        } catch (ApiRequestFailedException $e) {
            return false;
        }

        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function createDir($dirname, Config $config)
    {
        try {
            $this->container->createDir($dirname);
        } catch (ApiRequestFailedException $e) {
            return false;
        }

        return $this->getMetadata($dirname);
    }

    /**
     * Get full URL to given path.
     *
     * @param string $path = ''
     *
     * @return string
     */
    public function getUrl($path = '')
    {
        return $this->container->url($path);
    }
}


<?php
/**
 * AlertController.php
 * Definition of class AlertController
 *
 * Created 28/08/14 23:55
 *
 * @author Rasanga Perera <rasangaperera@gmail.com>
 * Copyright (c) 2014, The MIT License (MIT)
 */

namespace Ras\Bundle\FlashAlertBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class AlertController extends Controller
{
    /**
     * @deprecated
     *
     * @param bool $isAddStyles
     * @param bool $isAddJsAlertClose
     * @return \Symfony\Component\HttpFoundation\Response
     */
    public function displayAlertsAction($isAddStyles = true, $isAddJsAlertClose = true)
    {
        return $this->render(
            'RasFlashAlertBundle::layout.html.twig',
            array(
                'alertPublisher'        =>  $this->getAlertPublisher(),
                'isAddStyles'           =>  $isAddStyles,
                'isAddJsAlertClose'     =>  $isAddJsAlertClose
            )
        );
    }

    /**
     * @return \Ras\Bundle\FlashAlertBundle\Model\AlertPublisher
     *
     */
    private function getAlertPublisher()
    {
        return $this->get('ras_flash_alert.alert_publisher');
    }
}


<?php
/**
 * FlashAlertsHelper.php
 * Definition of class FlashAlertsHelper
 *
 * Created 16/12/14 18:50
 *
 * @author Rasanga Perera <rasangaperera@gmail.com>
 * @copyright (c) 2014, The MIT License (MIT)
 */

namespace Ras\Bundle\FlashAlertBundle\Templating\Helper;

use Ras\Bundle\FlashAlertBundle\Model\AlertPublisher;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Templating\EngineInterface;

class FlashAlertsHelper extends Helper
{
    /**
     * @var EngineInterface
     */
    private $templating;

    /**
     * @var AlertPublisher
     */
    private $alertPublisher;

    /**
     * @var array
     */
    private $options = array();

    /**
     * @param \Symfony\Component\Templating\EngineInterface $templating
     * @param \Ras\Bundle\FlashAlertBundle\Model\AlertPublisher $alertPublisher
     * @param array $options
     */
    public function __construct(EngineInterface $templating, AlertPublisher $alertPublisher, array $options)
    {
        $this->templating  = $templating;
        $this->alertPublisher = $alertPublisher;
        $this->options = $options;
    }

    /**
     * @inheritdoc
     */
    public function getName()
    {
        return 'ras_flash_alert_helper';
    }

    /**
     * Returns the HTML for the flashAlerts
     *
     * @param array $options
     * @return string A HTML string
     */
    public function renderFlashAlerts(array $options = array())
    {
        $options = $this->resolveOptions($options);

        return $this->templating->render(
            $options['template'],
            $options
        );
    }

    /**
     * Merges user-supplied options from the view
     * with base config values
     *
     * @param array $options
     * @return array
     */
    private function resolveOptions(array $options = array())
    {
        $this->options['alertPublisher'] = $this->alertPublisher;

        return array_merge($this->options, $options);
    }
}


<?php namespace Mattbrown\Laracurl;

/**
 * Laravel 4.2.x cURL Wrapper
 *
 * @author   Andreas Lutro <anlutro@gmail.com>
 * @author   Matthew Brown <bbrink68@gmail.com>
 * @license  http://opensource.org/licenses/MIT
 * @package  Laracurl
 */
class Request {

	/**
	 * ENCODING_* constants, used for specifying encoding options
	 */
	const ENCODING_QUERY = 0;
	const ENCODING_JSON = 1;
	const ENCODING_RAW = 2;

	/**
	 * The HTTP method to use. Defaults to GET.
	 *
	 * @var string
	 */
	private $method = 'get';

	/**
	 * The URL the request is sent to.
	 *
	 * @var string
	 */
	private $url = '';

	/**
	 * The headers sent with the request.
	 *
	 * @var array
	 */
	private $headers = array();

	/**
	 * POST data sent with the request.
	 *
	 * @var array
	 */
	private $data = array();

	/**
	 * Optional cURL options.
	 *
	 * @var array
	 */
	private $options = array();

	/**
	 * The type of processing to perform to encode the POST data
	 *
	 * @var boolean
	 */
	private $encoding = Request::ENCODING_QUERY;

	/**
	 * @param cURL $curl
	 */
	public function __construct(Laracurl $curl)
	{
		$this->curl = $curl;
	}

	/**
	 * Set the HTTP method of the request.
	 *
	 * @param string $method
	 */
	public function setMethod($method)
	{
		$method = strtolower($method);

		if (!array_key_exists($method, $this->curl->getAllowedMethods())) {
			throw new \InvalidArgumentException("Method [$method] not a valid HTTP method.");
		}

		$this->method = $method;

		return $this;
	}

	/**
	 * Get the HTTP method of the request.
	 *
	 * @return string
	 */
	public function getMethod()
	{
		return $this->method;
	}

	/**
	 * Set the URL of the request.
	 *
	 * @param string $url
	 */
	public function setUrl($url)
	{
		$this->url = $url;

		return $this;
	}

	/**
	 * Get the URL of the request.
	 *
	 * @return string
	 */
	public function getUrl()
	{
		return $this->url;
	}

	/**
	 * Set the headers to be sent with the request.
	 *
	 * Pass an associative array - e.g. ['Content-Type' => 'application/json']
	 * and the correct header formatting - e.g. 'Content-Type: application/json'
	 * will be done for you when the request is sent.
	 *
	 * @param array $headers
	 */
	public function setHeaders(array $headers)
	{
		$this->headers = $headers;

		return $this;
	}

	/**
	 * Get the headers to be sent with the request.
	 *
	 * @return array
	 */
	public function getHeaders()
	{
		return $this->headers;
	}

	/**
	 * Format the headers to an array of 'key: val' which can be passed to
	 * curl_setopt.
	 *
	 * @return array
	 */
	public function formatHeaders()
	{
		$headers = array();

		foreach ($this->headers as $key => $val) {
			if (is_string($key)) {
				$headers[] = $key . ': ' . $val;
			} else {
				$headers[] = $val;
			}
		}

		return $headers;
	}

	/**
	 * Set a specific header to be sent with the request.
	 *
	 * @param string $key
	 * @param mixed  $value
	 */
	public function setHeader($key, $value)
	{
		$this->headers[$key] = $value;

		return $this;
	}

	/**
	 * Get a specific header from the request.
	 *
	 * @param  string $key
	 *
	 * @return mixed
	 */
	public function getHeader($key)
	{
		return isset($this->headers[$key]) ? $this->headers[$key] : null;
	}

	/**
	 * Set the POST data to be sent with the request.
	 *
	 * @param mixed $data
	 */
	public function setData($data)
	{
		$this->data = $data;

		return $this;
	}

	/**
	 * Get the POST data to be sent with the request.
	 *
	 * @return array
	 */
	public function getData()
	{
		return $this->data;
	}

	/**
	 * Set the cURL options for the request.
	 *
	 * @param array $options
	 */
	public function setOptions(array $options)
	{
		$this->options = $options;

		return $this;
	}

	/**
	 * Get the cURL options for the request.
	 *
	 * @return array
	 */
	public function getOptions()
	{
		return $this->options;
	}

	/**
	 * Set a specific curl option for the request.
	 *
	 * @param string $key
	 * @param mixed  $value
	 */
	public function setOption($key, $value)
	{
		$this->options[$key] = $value;

		return $this;
	}

	/**
	 * Get a specific curl option from the request.
	 *
	 * @param  string $key
	 *
	 * @return mixed
	 */
	public function getOption($key)
	{
		return isset($this->options[$key]) ? $this->options[$key] : null;
	}

	/**
	 * Encode the POST data as a string.
	 *
	 * @return string
	 */
	public function encodeData()
	{
		switch ($this->encoding) {
			case Request::ENCODING_JSON:
				return json_encode($this->data);
				break;

			case Request::ENCODING_RAW:
				return (string) $this->data;
				break;

			case Request::ENCODING_QUERY:
				return http_build_query($this->data);
				break;

			default:
				throw new \UnexpectedValueException("Encoding [$encoding] not a known Request::ENCODING_* constant");
		}
	}

	/**
	 * Whether the request is JSON or not.
	 *
	 * @return boolean
	 */
	public function isJson()
	{
		return $this->encoding === Request::ENCODING_JSON;
	}

	/**
	* Set the encoding to use on the POST data, and (possibly) associated Content-Type headers
	*
	* @param int  $encoding  a Request::ENCODING_* constant
	*/
	public function setEncoding($encoding)
	{
		$encoding = intval($encoding);

		if ($encoding !== REQUEST::ENCODING_QUERY && $encoding !== REQUEST::ENCODING_JSON && $encoding !== REQUEST::ENCODING_RAW) {
			throw new \InvalidArgumentException("Encoding [$encoding] not a known Request::ENCODING_* constant");
		}

		if ($encoding === Request::ENCODING_JSON && !$this->getHeader('Content-Type')) {
			$this->setHeader('Content-Type', 'application/json');
		}

		$this->encoding = $encoding;

		return $this;
	}

	/**
	* Get the current encoding which will be used on the POST data
	*/
	public function getEncoding()
	{
		return $this->encoding;
	}

	/**
	 * Set whether the response should be JSON or not.
	 *
	 * @param boolean $toggle
	 *
	 * @deprecated Use setEncoding(Request::ENCODING_JSON)
	 */
	public function setJson($toggle)
	{
		$this->setEncoding($toggle ? Request::ENCODING_JSON : Request::ENCODING_QUERY);

		return $this;
	}

	/**
	 * Send the request.
	 *
	 * @return mixed
	 */
	public function send()
	{
		return $this->curl->sendRequest($this);
	}

}


<?php namespace Mattbrown\Laracurl;

/**
 * Laravel 4.2.x cURL Wrapper
 *
 * @author   Andreas Lutro <anlutro@gmail.com>
 * @author   Matthew Brown <bbrink68@gmail.com>
 * @link
 * @license  http://opensource.org/licenses/MIT
 * @package  Laracurl
 */
class Laracurl {

	/**
	 * The cURL resource.
	 */
	protected $ch;

	/**
	 * Allowed methods => allows postdata
	 *
	 * @var array
	 */
	protected $methods = array(
		'get'     => false,
		'post'    => true,
		'put'     => true,
		'patch'   => true,
		'delete'  => false,
		'options' => false,
	);

	/**
	 * The request class to use.
	 *
	 * @var string
	 */
	protected $requestClass = 'Mattbrown\Laracurl\Request';

	/**
	 * The response class to use.
	 *
	 * @var string
	 */
	protected $responseClass = 'Mattbrown\Laracurl\Response';

	/**
	 * Get allowed methods.
	 *
	 * @return array
	 */
	public function getAllowedMethods()
	{
		return $this->methods;
	}

	/**
	 * Set the request class.
	 *
	 * @param string $class
	 */
	public function setRequestClass($class)
	{
		$this->requestClass = $class;
	}

	/**
	 * Set the response class.
	 *
	 * @param string $class
	 */
	public function setResponseClass($class)
	{
		$this->responseClass = $class;
	}

	/**
	 * Build an URL with an optional query string.
	 *
	 * @param  string $url   the base URL without any query string
	 * @param  array  $query array of GET parameters
	 *
	 * @return string
	 */
	public function buildUrl($url, array $query)
	{
		// append the query string
		if (!empty($query)) {
			$queryString = http_build_query($query);
			$url .= '?' . $queryString;
		}

		return $url;
	}

	/**
	 * Create a new response object and set its values.
	 *
	 * @param  string  $method    get, post, etc
	 * @param  string  $url
	 * @param  mixed   $data      POST data
	 * @param  int     $encoding  Request::ENCODING_* constant specifying how to process the POST data
	 *
	 * @return mixed
	 */
	public function newRequest($method, $url, $data = array(), $encoding = Request::ENCODING_QUERY)
	{
		$class = $this->requestClass;
		$request = new $class($this);

		$request->setMethod($method);
		$request->setUrl($url);
		$request->setData($data);
		$request->setEncoding($encoding);

		return $request;
	}

	/**
	 * Create a new JSON request and set its values.
	 *
	 * @param  string $method  get, post etc
	 * @param  string $url
	 * @param  array  $data    POST data
	 *
	 * @return mixed
	 */
	public function newJsonRequest($method, $url, array $data = array())
	{
		return $this->newRequest($method, $url, $data, Request::ENCODING_JSON);
	}

	/**
	 * Create a new raw request and set its values.
	 *
	 * @param  string $method  get, post etc
	 * @param  string $url
	 * @param  array  $data    POST data
	 *
	 * @return mixed
	 */
	public function newRawRequest($method, $url, $data = '')
	{
		return $this->newRequest($method, $url, $data, Request::ENCODING_RAW);
	}

	/**
	 * Prepare the curl resource for sending a request.
	 *
	 * @param  Request $request
	 *
	 * @return void
	 */
	public function prepareRequest(Request $request)
	{
		$this->ch = curl_init();
		curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, true);
		curl_setopt($this->ch, CURLOPT_HEADER, true);
		curl_setopt($this->ch, CURLOPT_URL, $request->getUrl());

		$options = $request->getOptions();
		if (!empty($options)) {
			curl_setopt_array($this->ch, $options);
		}

		$method = $request->getMethod();
		if ($method === 'post') {
			curl_setopt($this->ch, CURLOPT_POST, 1);
		} elseif ($method !== 'get') {
			curl_setopt($this->ch, CURLOPT_CUSTOMREQUEST, strtoupper($method));
		}

		curl_setopt($this->ch, CURLOPT_HTTPHEADER, $request->formatHeaders());

		if ($this->methods[$method] === true) {
			curl_setopt($this->ch, CURLOPT_POSTFIELDS, $request->encodeData());
		}
	}

	/**
	 * Send a request.
	 *
	 * @param  Request $request
	 *
	 * @return Response
	 */
	public function sendRequest(Request $request)
	{
		$this->prepareRequest($request);

		$result = curl_exec($this->ch);

		if ($result === false) {
			throw new \RuntimeException("cURL request failed with error: " . curl_error($this->ch));
		}

		$response = $this->createResponseObject($result);

		curl_close($this->ch);

		return $response;
	}

	/**
	 * Extract the response info, header and body from a cURL response. Saves
	 * the data in variables stored on the object.
	 *
	 * @param  string $response
	 *
	 * @return Response
	 */
	protected function createResponseObject($response)
	{
		$info = curl_getinfo($this->ch);

		$headerSize = curl_getinfo($this->ch, CURLINFO_HEADER_SIZE);
		$headerText = substr($response, 0, $headerSize);
		$headers = $this->headerToArray($headerText);

		$body = substr($response, $headerSize);

		$class = $this->responseClass;
		$obj = new $class($body, $headers, $info);
		return $obj;
	}

	/**
	 * Turn a header string into an array.
	 *
	 * @param  string $header
	 *
	 * @return array
	 */
	protected function headerToArray($header)
	{
		$tmp = explode("\r\n", $header);
		$headers = array();
		foreach ($tmp as $singleHeader) {
			$delimiter = strpos($singleHeader, ': ');
			if ($delimiter !== false) {
				$key = substr($singleHeader, 0, $delimiter);
				$val = substr($singleHeader, $delimiter + 2);
				$headers[$key] = $val;
			} else {
				$delimiter = strpos($singleHeader, ' ');
				if ($delimiter !== false) {
					$key = substr($singleHeader, 0, $delimiter);
					$val = substr($singleHeader, $delimiter + 1);
					$headers[$key] = $val;
				}
			}
		}
		return $headers;
	}

	/**
	 * Handle dynamic calls to the class.
	 *
	 * @param  string $method
	 * @param  array  $args
	 *
	 * @return mixed
	 */
	public function __call($method, $args)
	{
		$method = strtolower($method);

		$encoding = Request::ENCODING_QUERY;

		if (substr($method, 0, 4) === 'json') {
			$encoding = Request::ENCODING_JSON;
			$method = substr($method, 4);
		} elseif (substr($method, 0, 3) === 'raw') {
			$encoding = Request::ENCODING_RAW;
			$method = substr($method, 3);
		}

		if (!array_key_exists($method, $this->methods)) {
			throw new \InvalidArgumentException("Method [$method] not a valid HTTP method.");
		}

		$url = $args[0];

		$allowData = $this->methods[$method];
		if ($allowData && isset($args[1])) {
			$data = $args[1];
		} else {
			$data = array();
		}

		$request = $this->newRequest($method, $url, $data, $encoding);

		return $this->sendRequest($request);
	}

}


<?php namespace Mattbrown\Laracurl;

/**
 * Laravel 4.2.x cURL Wrapper
 *
 * @author   Andreas Lutro <anlutro@gmail.com>
 * @author   Matthew Brown <bbrink68@gmail.com>
 * @license  http://opensource.org/licenses/MIT
 * @package  Laracurl
 */
class Response {

	/**
	 * The response headers.
	 *
	 * @var array
	 */
	public $headers = array();

	/**
	 * The response body.
	 *
	 * @var string
	 */
	public $body;

	/**
	 * The results of curl_getinfo on the response request.
	 *
	 * @var array|false
	 */
	public $info;

	/**
	 * @deprecated
	 * @see  $statusText, $statusCode
	 */
	public $code;

	/**
	 * The response code including text, e.g. '200 OK'.
	 *
	 * @var string
	 */
	public $statusText;

	/**
	 * The response code.
	 *
	 * @var int
	 */
	public $statusCode;

	/**
	 * @param string $body
	 * @param array  $headers
	 * @param mixed  $info
	 */
	public function __construct($body, $headers, $info = array())
	{
		$this->body = $body;
		$this->headers = $headers;
		$this->info = $info;

		if (isset($this->headers['HTTP/1.1'])) {
			$this->setCode($this->headers['HTTP/1.1']);
		} elseif (isset($this->headers['HTTP/1.0'])) {
			$this->setCode($this->headers['HTTP/1.0']);
		}
	}

	/**
	 * Set the response code.
	 *
	 * @param string $code
	 */
	protected function setCode($code)
	{
		$this->code = $code;
		$this->statusText = $code;
		list($this->statusCode, ) = explode(' ', $code);
	}

	/**
	 * Get a specific header from the response.
	 *
	 * @param  string $key
	 *
	 * @return mixed
	 */
	public function getHeader($key)
	{
		return array_key_exists($key, $this->headers) ? $this->headers[$key] : null;
	}

	/**
	 * Gets all the headers of the response.
	 *
	 * @return array
	 */
	public function getHeaders()
	{
		return $this->headers;
	}

	/**
	 * Convert the response instance to an array.
	 *
	 * @return array
	 */
	public function toArray()
	{
		return array(
			'headers' => $this->headers,
			'body' => $this->body,
			'info' => $this->info
		);
	}

	/**
	 * Convert the response object to a JSON string.
	 *
	 * @return string
	 */
	public function toJson()
	{
		return json_encode($this->toArray());
	}

	/**
	 * Convert the object to its string representation by returning the body.
	 *
	 * @return string
	 */
	public function __toString()
	{
		return $this->body;
	}

}