<?php

namespace SimpleUser;

use Silex\Application;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Security\Core\Exception\DisabledException;
use InvalidArgumentException;
use JasonGrimes\Paginator;

/**
 * Controller with actions for handling form-based authentication and user management.
 *
 * @package SimpleUser
 */
class UserController
{
    /** @var UserManager */
    protected $userManager;

    protected $templates = array(
        'layout' => '@user/layout.twig',
        'register' => '@user/register.twig',
        'register-confirmation-sent' => '@user/register-confirmation-sent.twig',
        'login' => '@user/login.twig',
        'login-confirmation-needed' => '@user/login-confirmation-needed.twig',
        'forgot-password' => '@user/forgot-password.twig',
        'reset-password' => '@user/reset-password.twig',
        'view' => '@user/view.twig',
        'edit' => '@user/edit.twig',
        'list' => '@user/list.twig',
    );

    // Custom fields to support in the editAction().
    protected $editCustomFields = array();

    protected $isUsernameRequired = false;
    protected $isEmailConfirmationRequired = false;
    protected $isPasswordResetEnabled = true;

    /**
     * Constructor.
     *
     * @param UserManager $userManager
     * @param array $deprecated - Deprecated. No longer used.
     */
    public function __construct(UserManager $userManager, $deprecated = null)
    {
        $this->userManager = $userManager;
    }

    /**
     * @param string $key
     * @param string $template
     */
    public function setTemplate($key, $template)
    {
        $this->templates[$key] = $template;
    }

    /**
     * @param array $templates
     */
    public function setTemplates(array $templates)
    {
        foreach ($templates as $key => $val) {
            $this->setTemplate($key, $val);
        }
    }

    /**
     * @param string $key
     * @return string|null
     */
    public function getTemplate($key)
    {
        return $this->templates[$key];
    }

    /**
     * Register action.
     *
     * @param Application $app
     * @param Request $request
     * @return Response
     */
    public function registerAction(Application $app, Request $request)
    {
        if ($request->isMethod('POST')) {
            try {
                $user = $this->createUserFromRequest($request);
                if ($error = $this->userManager->validatePasswordStrength($user, $request->request->get('password'))) {
                    throw new InvalidArgumentException($error);
                }
                if ($this->isEmailConfirmationRequired) {
                    $user->setEnabled(false);
                    $user->setConfirmationToken($app['user.tokenGenerator']->generateToken());
                }
                $this->userManager->insert($user);

                if ($this->isEmailConfirmationRequired) {
                    // Send email confirmation.
                    $app['user.mailer']->sendConfirmationMessage($user);

                    // Render the "go check your email" page.
                    return $app['twig']->render($this->getTemplate('register-confirmation-sent'), array(
                        'layout_template' => $this->getTemplate('layout'),
                        'email' => $user->getEmail(),
                    ));
                } else {
                    // Log the user in to the new account.
                    $this->userManager->loginAsUser($user);

                    $app['session']->getFlashBag()->set('alert', 'Account created.');

                    // Redirect to user's new profile page.
                    return $app->redirect($app['url_generator']->generate('user.view', array('id' => $user->getId())));
                }

            } catch (InvalidArgumentException $e) {
                $error = $e->getMessage();
            }
        }

        return $app['twig']->render($this->getTemplate('register'), array(
            'layout_template' => $this->getTemplate('layout'),
            'error' => isset($error) ? $error : null,
            'name' => $request->request->get('name'),
            'email' => $request->request->get('email'),
            'username' => $request->request->get('username'),
            'isUsernameRequired' => $this->isUsernameRequired,
        ));
    }

    /**
     * Action to handle email confirmation links.
     *
     * @param Application $app
     * @param Request $request
     * @param string $token
     * @return \Symfony\Component\HttpFoundation\RedirectResponse
     * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
     */
    public function confirmEmailAction(Application $app, Request $request, $token)
    {
        $user = $this->userManager->findOneBy(array('confirmationToken' => $token));
        if (!$user) {
            $app['session']->getFlashBag()->set('alert', 'Sorry, your email confirmation link has expired.');

            return $app->redirect($app['url_generator']->generate('user.login'));
        }

        $user->setConfirmationToken(null);
        $user->setEnabled(true);
        $this->userManager->update($user);

        $this->userManager->loginAsUser($user);

        $app['session']->getFlashBag()->set('alert', 'Thank you! Your account has been activated.');

        return $app->redirect($app['url_generator']->generate('user.view', array('id' => $user->getId())));
    }

    /**
     * Login action.
     *
     * @param Application $app
     * @param Request $request
     * @return Response
     */
    public function loginAction(Application $app, Request $request)
    {
        $authException = $app['user.last_auth_exception']($request);

        if ($authException instanceof DisabledException) {
            // This exception is thrown if (!$user->isEnabled())
            // Warning: Be careful not to disclose any user information besides the email address at this point.
            // The Security system throws this exception before actually checking if the password was valid.
            $user = $this->userManager->refreshUser($authException->getUser());

            return $app['twig']->render($this->getTemplate('login-confirmation-needed'), array(
                'layout_template' => $this->getTemplate('layout'),
                'email' => $user->getEmail(),
                'fromAddress' => $app['user.mailer']->getFromAddress(),
                'resendUrl' => $app['url_generator']->generate('user.resend-confirmation'),
            ));
        }

        return $app['twig']->render($this->getTemplate('login'), array(
            'layout_template' => $this->getTemplate('layout'),
            'error' => $authException ? $authException->getMessageKey() : null,
            'last_username' => $app['session']->get('_security.last_username'),
            'allowRememberMe' => isset($app['security.remember_me.response_listener']),
            'allowPasswordReset' => $this->isPasswordResetEnabled(),
        ));
    }

    /**
     * Action to resend an email confirmation message.
     *
     * @param Application $app
     * @param Request $request
     * @return mixed
     * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
     */
    public function resendConfirmationAction(Application $app, Request $request)
    {
        $email = $request->request->get('email');
        $user = $this->userManager->findOneBy(array('email' => $email));
        if (!$user) {
            throw new NotFoundHttpException('No user account was found with that email address.');
        }

        if (!$user->getConfirmationToken()) {
            $user->setConfirmationToken($app['user.tokenGenerator']->generateToken());
            $this->userManager->update($user);
        }

        $app['user.mailer']->sendConfirmationMessage($user);

        // Render the "go check your email" page.
        return $app['twig']->render($this->getTemplate('register-confirmation-sent'), array(
            'layout_template' => $this->getTemplate('layout'),
            'email' => $user->getEmail(),
        ));
    }

    /**
     * @param Application $app
     * @param Request $request
     * @return \Symfony\Component\HttpFoundation\RedirectResponse
     * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
     */
    public function forgotPasswordAction(Application $app, Request $request)
    {
        if (!$this->isPasswordResetEnabled()) {
            throw new NotFoundHttpException('Password resetting is not enabled.');
        }

        $error = null;

        if ($request->isMethod('POST')) {
            $email = $request->request->get('email');
            $user = $this->userManager->findOneBy(array('email' => $email));
            if ($user) {
                // Initialize and send the password reset request.
                $user->setTimePasswordResetRequested(time());
                if (!$user->getConfirmationToken()) {
                    $user->setConfirmationToken($app['user.tokenGenerator']->generateToken());
                }
                $this->userManager->update($user);

                $app['user.mailer']->sendResetMessage($user);
                $app['session']->getFlashBag()->set('alert', 'Instructions for resetting your password have been emailed to you.');
                $app['session']->set('_security.last_username', $email);

                return $app->redirect($app['url_generator']->generate('user.login'));
            }
            $error = 'No user account was found with that email address.';

        } else {
            $email = $request->request->get('email') ?: ($request->query->get('email') ?: $app['session']->get('_security.last_username'));
            if (!filter_var($email, FILTER_VALIDATE_EMAIL)) $email = '';
        }

        return $app['twig']->render($this->getTemplate('forgot-password'), array(
            'layout_template' => $this->getTemplate('layout'),
            'email' => $email,
            'fromAddress' => $app['user.mailer']->getFromAddress(),
            'error' => $error,
        ));
    }

    /**
     * @param Application $app
     * @param Request $request
     * @param string $token
     * @return \Symfony\Component\HttpFoundation\RedirectResponse
     * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
     */
    public function resetPasswordAction(Application $app, Request $request, $token)
    {
        if (!$this->isPasswordResetEnabled()) {
            throw new NotFoundHttpException('Password resetting is not enabled.');
        }

        $tokenExpired = false;

        $user = $this->userManager->findOneBy(array('confirmationToken' => $token));
        if (!$user) {
            $tokenExpired = true;
        } else if ($user->isPasswordResetRequestExpired($app['user.options']['passwordReset']['tokenTTL'])) {
            $tokenExpired = true;
        }

        if ($tokenExpired) {
            $app['session']->getFlashBag()->set('alert', 'Sorry, your password reset link has expired.');
            return $app->redirect($app['url_generator']->generate('user.login'));
        }

        $error = '';
        if ($request->isMethod('POST')) {
            // Validate the password
            $password = $request->request->get('password');
            if ($password != $request->request->get('confirm_password')) {
                $error = 'Passwords don\'t match.';
            } else if ($error = $this->userManager->validatePasswordStrength($user, $password)) {
                ;
            } else {
                // Set the password and log in.
                $this->userManager->setUserPassword($user, $password);
                $user->setConfirmationToken(null);
                $user->setEnabled(true);
                $this->userManager->update($user);

                $this->userManager->loginAsUser($user);

                $app['session']->getFlashBag()->set('alert', 'Your password has been reset and you are now signed in.');

                return $app->redirect($app['url_generator']->generate('user.view', array('id' => $user->getId())));
            }
        }

        return $app['twig']->render($this->getTemplate('reset-password'), array(
            'layout_template' => $this->getTemplate('layout'),
            'user' => $user,
            'token' => $token,
            'error' => $error,
        ));
    }

    /**
     * @param Request $request
     * @return User
     * @throws InvalidArgumentException
     */
    protected function createUserFromRequest(Request $request)
    {
        if ($request->request->get('password') != $request->request->get('confirm_password')) {
            throw new InvalidArgumentException('Passwords don\'t match.');
        }

        $user = $this->userManager->createUser(
            $request->request->get('email'),
            $request->request->get('password'),
            $request->request->get('name') ?: null);

        if ($username = $request->request->get('username')) {
            $user->setUsername($username);
        }

        $errors = $this->userManager->validate($user);
        if (!empty($errors)) {
            throw new InvalidArgumentException(implode("\n", $errors));
        }

        return $user;
    }

    /**
     * View user action.
     *
     * @param Application $app
     * @param Request $request
     * @param int $id
     * @return Response
     * @throws NotFoundHttpException if no user is found with that ID.
     */
    public function viewAction(Application $app, Request $request, $id)
    {
        $user = $this->userManager->getUser($id);

        if (!$user) {
            throw new NotFoundHttpException('No user was found with that ID.');
        }

        if (!$user->isEnabled() && !$app['security']->isGranted('ROLE_ADMIN')) {
            throw new NotFoundHttpException('That user is disabled (pending email confirmation).');
        }

        return $app['twig']->render($this->getTemplate('view'), array(
            'layout_template' => $this->getTemplate('layout'),
            'user' => $user,
            'imageUrl' => $this->getGravatarUrl($user->getEmail()),
        ));

    }

    /**
     * @param Application $app
     * @return \Symfony\Component\HttpFoundation\RedirectResponse
     */
    public function viewSelfAction(Application $app) {
        if (!$app['user']) {
            return $app->redirect($app['url_generator']->generate('user.login'));
        }

        return $app->redirect($app['url_generator']->generate('user.view', array('id' => $app['user']->getId())));
    }

    /**
     * @param string $email
     * @param int $size
     * @return string
     */
    protected function getGravatarUrl($email, $size = 80)
    {
        // See https://en.gravatar.com/site/implement/images/ for available options.
        return '//www.gravatar.com/avatar/' . md5(strtolower(trim($email))) . '?s=' . $size . '&d=identicon';
    }

    /**
     * Edit user action.
     *
     * @param Application $app
     * @param Request $request
     * @param int $id
     * @return Response
     * @throws NotFoundHttpException if no user is found with that ID.
     */
    public function editAction(Application $app, Request $request, $id)
    {
        $errors = array();

        $user = $this->userManager->getUser($id);
        if (!$user) {
            throw new NotFoundHttpException('No user was found with that ID.');
        }

        $customFields = $this->editCustomFields ?: array();

        if ($request->isMethod('POST')) {
            $user->setName($request->request->get('name'));
            $user->setEmail($request->request->get('email'));
            if ($request->request->has('username')) {
                $user->setUsername($request->request->get('username'));
            }
            if ($request->request->get('password')) {
                if ($request->request->get('password') != $request->request->get('confirm_password')) {
                    $errors['password'] = 'Passwords don\'t match.';
                } else if ($error = $this->userManager->validatePasswordStrength($user, $request->request->get('password'))) {
                    $errors['password'] = $error;
                } else {
                    $this->userManager->setUserPassword($user, $request->request->get('password'));
                }
            }
            if ($app['security']->isGranted('ROLE_ADMIN') && $request->request->has('roles')) {
                $user->setRoles($request->request->get('roles'));
            }

            foreach (array_keys($customFields) as $customField) {
                if ($request->request->has($customField)) {
                    $user->setCustomField($customField, $request->request->get($customField));
                }
            }

            $errors += $this->userManager->validate($user);

            if (empty($errors)) {
                $this->userManager->update($user);
                $msg = 'Saved account information.' . ($request->request->get('password') ? ' Changed password.' : '');
                $app['session']->getFlashBag()->set('alert', $msg);
            }
        }

        return $app['twig']->render($this->getTemplate('edit'), array(
            'layout_template' => $this->getTemplate('layout'),
            'error' => implode("\n", $errors),
            'user' => $user,
            'available_roles' => array('ROLE_USER', 'ROLE_ADMIN'),
            'image_url' => $this->getGravatarUrl($user->getEmail()),
            'customFields' => $customFields,
            'isUsernameRequired' => $this->isUsernameRequired,
        ));
    }

    /**
     * Specify custom fields to support in the editAction().
     *
     * @param array $editCustomFields
     */
    public function setEditCustomFields(array $editCustomFields)
    {
        $this->editCustomFields = $editCustomFields;
    }

    public function listAction(Application $app, Request $request)
    {
        $order_by = $request->get('order_by') ?: 'name';
        $order_dir = $request->get('order_dir') == 'DESC' ? 'DESC' : 'ASC';
        $limit = (int)($request->get('limit') ?: 50);
        $page = (int)($request->get('page') ?: 1);
        $offset = ($page - 1) * $limit;

        $criteria = array();
        if (!$app['security']->isGranted('ROLE_ADMIN')) {
            $criteria['isEnabled'] = true;
        }

        $users = $this->userManager->findBy($criteria, array(
            'limit' => array($offset, $limit),
            'order_by' => array($order_by, $order_dir),
        ));
        $numResults = $this->userManager->findCount($criteria);

        $paginator = new Paginator($numResults, $limit, $page,
            $app['url_generator']->generate('user.list') . '?page=(:num)&limit=' . $limit . '&order_by=' . $order_by . '&order_dir=' . $order_dir
        );

        foreach ($users as $user) {
            $user->imageUrl = $this->getGravatarUrl($user->getEmail(), 40);
        }

        return $app['twig']->render($this->getTemplate('list'), array(
            'layout_template' => $this->getTemplate('layout'),
            'users' => $users,
            'paginator' => $paginator,

            // The following variables are no longer used in the default template,
            // but are retained for backward compatibility.
            'numResults' => $paginator->getTotalItems(),
            'nextUrl' => $paginator->getNextUrl(),
            'prevUrl' => $paginator->getPrevUrl(),
            'firstResult' => $paginator->getCurrentPageFirstItem(),
            'lastResult' => $paginator->getCurrentPageLastItem(),
        ));
    }

    /**
     * @param boolean $passwordResetEnabled
     */
    public function setPasswordResetEnabled($passwordResetEnabled)
    {
        $this->isPasswordResetEnabled = (bool) $passwordResetEnabled;
    }

    /**
     * @return boolean
     */
    public function isPasswordResetEnabled()
    {
        return $this->isPasswordResetEnabled;
    }

    /**
     * @param bool $isUsernameRequired
     */
    public function setUsernameRequired($isUsernameRequired)
    {
        $this->isUsernameRequired = (bool) $isUsernameRequired;
    }

    public function setEmailConfirmationRequired($isRequired)
    {
        $this->isEmailConfirmationRequired = (bool) $isRequired;
    }

    // ---------------------------------------------------------------------------
    //
    // Deprecated methods.
    //
    // Retained for backwards compatibility.
    //
    // ---------------------------------------------------------------------------

    /**
     * @param string $layoutTemplate
     * @deprecated Use setTemplate() or setTemplates() instead.
     */
    public function setLayoutTemplate($layoutTemplate)
    {
        $this->setTemplate('layout', $layoutTemplate);
    }

    /**
     * @deprecated Use setTemplate() or setTemplates() instead.
     * @param string $editTemplate
     */
    public function setEditTemplate($editTemplate)
    {
        $this->setTemplate('edit', $editTemplate);
    }

    /**
     * @deprecated Use setTemplate() or setTemplates() instead.
     * @param string $listTemplate
     */
    public function setListTemplate($listTemplate)
    {
        $this->setTemplate('list', $listTemplate);
    }

    /**
     * @deprecated Use setTemplate() or setTemplates() instead.
     * @param string $loginTemplate
     */
    public function setLoginTemplate($loginTemplate)
    {
        $this->setTemplate('login', $loginTemplate);
    }

    /**
     * @deprecated Use setTemplate() or setTemplates() instead.
     * @param string $registerTemplate
     */
    public function setRegisterTemplate($registerTemplate)
    {
        $this->setTemplate('register', $registerTemplate);
    }

    /**
     * @deprecated Use setTemplate() or setTemplates() instead.
     * @param string $viewTemplate
     */
    public function setViewTemplate($viewTemplate)
    {
        $this->setTemplate('view', $viewTemplate);
    }
}


<?php

namespace SimpleUser;

use Symfony\Component\Security\Core\User\AdvancedUserInterface;

/**
 * A simple User model.
 *
 * @package SimpleUser
 */
class User implements AdvancedUserInterface, \Serializable
{
    protected $id;
    protected $email;
    protected $password;
    protected $salt;
    protected $roles = array();
    protected $name = '';
    protected $timeCreated;
    protected $username;
    protected $isEnabled = true;
    protected $confirmationToken;
    protected $timePasswordResetRequested;

    protected $customFields = array();

    /**
     * Constructor.
     *
     * @param string $email
     */
    public function __construct($email)
    {
        $this->email = $email;
        $this->timeCreated = time();
        $this->salt = base_convert(sha1(uniqid(mt_rand(), true)), 16, 36);
    }

    /**
     * Returns the roles granted to the user. Note that all users have the ROLE_USER role.
     *
     * @return array A list of the user's roles.
     */
    public function getRoles()
    {
        $roles = $this->roles;

        // Every user must have at least one role, per Silex security docs.
        $roles[] = 'ROLE_USER';

        return array_unique($roles);
    }

    /**
     * Set the user's roles to the given list.
     *
     * @param array $roles
     */
    public function setRoles(array $roles)
    {
        $this->roles = array();

        foreach ($roles as $role) {
            $this->addRole($role);
        }
    }

    /**
     * Test whether the user has the given role.
     *
     * @param string $role
     * @return bool
     */
    public function hasRole($role)
    {
        return in_array(strtoupper($role), $this->getRoles(), true);
    }

    /**
     * Add the given role to the user.
     *
     * @param string $role
     */
    public function addRole($role)
    {
        $role = strtoupper($role);

        if ($role === 'ROLE_USER') {
            return;
        }

        if (!$this->hasRole($role)) {
            $this->roles[] = $role;
        }
    }

    /**
     * Remove the given role from the user.
     *
     * @param string $role
     */
    public function removeRole($role)
    {
        if (false !== $key = array_search(strtoupper($role), $this->roles, true)) {
            unset($this->roles[$key]);
            $this->roles = array_values($this->roles);
        }
    }

    /**
     * Set the user ID.
     *
     * @param int $id
     */
    public function setId($id)
    {
        $this->id = $id;
    }

    /**
     * Get the user ID.
     *
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Get the encoded password used to authenticate the user.
     *
     * On authentication, a plain-text password will be salted,
     * encoded, and then compared to this value.
     *
     * @return string The encoded password.
     */
    public function getPassword()
    {
        return $this->password;
    }

    /**
     * Set the encoded password.
     *
     * @param string $password
     */
    public function setPassword($password)
    {
        $this->password = $password;
    }

    /**
     * Set the salt that should be used to encode the password.
     *
     * @param string $salt
     */
    public function setSalt($salt)
    {
        $this->salt = $salt;
    }

    /**
     * Returns the salt that was originally used to encode the password.
     *
     * This can return null if the password was not encoded using a salt.
     *
     * @return string The salt
     */
    public function getSalt()
    {
        return $this->salt;
    }

    /**
     * Returns the username, if not empty, otherwise the email address.
     *
     * Email is returned as a fallback because username is optional,
     * but the Symfony Security system depends on getUsername() returning a value.
     * Use getRealUsername() to get the actual username value.
     *
     * This method is required by the UserInterface.
     *
     * @see getRealUsername
     * @return string The username, if not empty, otherwise the email.
     */
    public function getUsername()
    {
        return $this->username ?: $this->email;
    }

    /**
     * Get the actual username value that was set,
     * or null if no username has been set.
     * Compare to getUsername, which returns the email if username is not set.
     *
     * @see getUsername
     * @return string|null
     */
    public function getRealUsername()
    {
        return $this->username;
    }

    /**
     * Test whether username has ever been set (even if it's currently empty).
     *
     * @return bool
     */
    public function hasRealUsername()
    {
        return !is_null($this->username);
    }

    /**
     * @param string $username
     */
    public function setUsername($username)
    {
        $this->username = $username;
    }

    /**
     * @return string The user's email address.
     */
    public function getEmail()
    {
        return $this->email;
    }

    /**
     * @param string $email
     */
    public function setEmail($email)
    {
        $this->email = $email;
    }

    /**
     * @param string $name
     */
    public function setName($name)
    {
        $this->name = $name;
    }

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

    /**
     * Returns the name, if set, or else "Anonymous {id}".
     *
     * @return string
     */
    public function getDisplayName()
    {
        return $this->name ?: 'Anonymous ' . $this->id;
    }

    /**
     * Set the time the user was originally created.
     *
     * @param int $timeCreated A timestamp value.
     */
    public function setTimeCreated($timeCreated)
    {
        $this->timeCreated = $timeCreated;
    }

    /**
     * Set the time the user was originally created.
     *
     * @return int
     */
    public function getTimeCreated()
    {
        return $this->timeCreated;
    }

    /**
     * Removes sensitive data from the user.
     *
     * This is a no-op, since we never store the plain text credentials in this object.
     * It's required by UserInterface.
     *
     * @return void
     */
    public function eraseCredentials()
    {
    }

    /**
     * The Symfony Security component stores a serialized User object in the session.
     * We only need it to store the user ID, because the user provider's refreshUser() method is called on each request
     * and reloads the user by its ID.
     *
     * @see \Serializable::serialize()
     */
    public function serialize()
    {
        return serialize(array(
            $this->id,
        ));
    }

    /**
     * @see \Serializable::unserialize()
     */
    public function unserialize($serialized)
    {
        list (
            $this->id,
            ) = unserialize($serialized);
    }

    /**
     * Validate the user object.
     *
     * @return array An array of error messages, or an empty array if there were no errors.
     */
    public function validate()
    {
        $errors = array();

        if (!$this->getEmail()) {
            $errors['email'] = 'Email address is required.';
        } else if (!strpos($this->getEmail(), '@')) {
            // Basic email format sanity check. Real validation comes from sending them an email with a link they have to click.
            $errors['email'] = 'Email address appears to be invalid.';
        } else if (strlen($this->getEmail()) > 100) {
            $errors['email'] = 'Email address can\'t be longer than 100 characters.';
        }

        if (!$this->getPassword()) {
            $errors['password'] = 'Password is required.';
        } else if (strlen($this->getPassword()) > 255) {
            $errors['password'] = 'Password can\'t be longer than 255 characters.';
        }

        if (strlen($this->getName()) > 100) {
            $errors['name'] = 'Name can\'t be longer than 100 characters.';
        }

        // Username can't contain "@",
        // because that's how we distinguish between email and username when signing in.
        // (It's possible to sign in by providing either one.)
        if ($this->getRealUsername() && strpos($this->getRealUsername(), '@') !== false) {
            $errors['username'] = 'Username cannot contain the "@" symbol.';
        }

        return $errors;
    }

    /**
     * @param string $customField
     * @return bool
     */
    public function hasCustomField($customField)
    {
        return array_key_exists($customField, $this->customFields);
    }

    /**
     * @param string $customField
     * @return mixed|null
     */
    public function getCustomField($customField)
    {
        return $this->hasCustomField($customField) ? $this->customFields[$customField] : null;
    }

    /**
     * @param string $customField
     * @param mixed $value
     */
    public function setCustomField($customField, $value)
    {
        $this->customFields[$customField] = $value;
    }

    /**
     * @param array|null $customFields
     */
    public function setCustomFields($customFields)
    {
        $this->customFields = $customFields;
    }

    /**
     * @return array
     */
    public function getCustomFields()
    {
        return $this->customFields;
    }

    /**
     * Checks whether the user's account has expired.
     *
     * Internally, if this method returns false, the authentication system
     * will throw an AccountExpiredException and prevent login.
     *
     * @return bool    true if the user's account is non expired, false otherwise
     *
     * @see AccountExpiredException
     */
    public function isAccountNonExpired()
    {
        return true;
    }

    /**
     * Checks whether the user is locked.
     *
     * Internally, if this method returns false, the authentication system
     * will throw a LockedException and prevent login.
     *
     * @return bool    true if the user is not locked, false otherwise
     *
     * @see LockedException
     */
    public function isAccountNonLocked()
    {
        return true;
    }

    /**
     * Checks whether the user's credentials (password) has expired.
     *
     * Internally, if this method returns false, the authentication system
     * will throw a CredentialsExpiredException and prevent login.
     *
     * @return bool    true if the user's credentials are non expired, false otherwise
     *
     * @see CredentialsExpiredException
     */
    public function isCredentialsNonExpired()
    {
        return true;
    }

    /**
     * Checks whether the user is enabled.
     *
     * Internally, if this method returns false, the authentication system
     * will throw a DisabledException and prevent login.
     *
     * Users are enabled by default.
     *
     * @return bool    true if the user is enabled, false otherwise
     *
     * @see DisabledException
     */
    public function isEnabled()
    {
        return $this->isEnabled;
    }

    /**
     * Set whether the user is enabled.
     *
     * @param bool $isEnabled
     */
    public function setEnabled($isEnabled)
    {
        $this->isEnabled = (bool) $isEnabled;
    }

    public function setConfirmationToken($token)
    {
        $this->confirmationToken = $token;
    }

    public function getConfirmationToken()
    {
        return $this->confirmationToken;
    }

    /**
     * @param int|null $timestamp
     */
    public function setTimePasswordResetRequested($timestamp)
    {
        $this->timePasswordResetRequested = $timestamp ?: null;
    }

    /**
     * @return int|null
     */
    public function getTimePasswordResetRequested()
    {
        return $this->timePasswordResetRequested;
    }

    /**
     * @param int $ttl Password reset request TTL, in seconds.
     * @return bool
     */
    public function isPasswordResetRequestExpired($ttl)
    {
        $timeRequested = $this->getTimePasswordResetRequested();
        if (!$timeRequested) {
            return true;
        }

        return $timeRequested + $ttl < time();
    }
}


<?php

namespace SimpleUser;

use Silex\Application;
use Silex\ServiceProviderInterface;
use Silex\ControllerProviderInterface;
use Silex\ControllerCollection;
use Silex\ServiceControllerResolver;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authorization\Voter\RoleHierarchyVoter;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Core\SecurityContextInterface;

class UserServiceProvider implements ServiceProviderInterface, ControllerProviderInterface
{
    /**
     * Registers services on the given app.
     *
     * This method should only be used to configure services and parameters.
     * It should not get services.
     *
     * @param Application $app An Application instance
     */
    public function register(Application $app)
    {
        // Default options.
        $app['user.options.default'] = array(

            // Specify custom view templates here.
            'templates' => array(
                'layout' => '@user/layout.twig',
                'register' => '@user/register.twig',
                'register-confirmation-sent' => '@user/register-confirmation-sent.twig',
                'login' => '@user/login.twig',
                'login-confirmation-needed' => '@user/login-confirmation-needed.twig',
                'forgot-password' => '@user/forgot-password.twig',
                'reset-password' => '@user/reset-password.twig',
                'view' => '@user/view.twig',
                'edit' => '@user/edit.twig',
                'list' => '@user/list.twig',
            ),

            // Configure the user mailer for sending password reset and email confirmation messages.
            'mailer' => array(
                'enabled' => true, // When false, email notifications are not sent (they're silently discarded).
                'fromEmail' => array(
                    'address' => 'do-not-reply@' . (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : gethostname()),
                    'name' => null,
                ),
            ),

            'emailConfirmation' => array(
                'required' => false, // Whether to require email confirmation before enabling new accounts.
                'template' => '@user/email/confirm-email.twig',
            ),

            'passwordReset' => array(
                'template' => '@user/email/reset-password.twig',
                'tokenTTL' => 86400, // How many seconds the reset token is valid for. Default: 1 day.
            ),

            // Set this to use a custom User class.
            'userClass' => 'SimpleUser\User',

            // Whether to require that users have a username (default: false).
            // By default, users sign in with their email address instead.
            'isUsernameRequired' => false,

            // A list of custom fields to support in the edit controller.
            'editCustomFields' => array(),

            // Override table names, if necessary.
            'userTableName' => 'users',
            'userCustomFieldsTableName' => 'user_custom_fields',

            // Override column names if necessary.
            'userColumns' => array(
                'id' => 'id',
                'email' => 'email',
                'password' => 'password',
                'salt' => 'salt',
                'roles' => 'roles',
                'name' => 'name',
                'time_created' => 'time_created',
                'username' => 'username',
                'isEnabled' => 'isEnabled',
                'confirmationToken' => 'confirmationToken',
                'timePasswordResetRequested' => 'timePasswordResetRequested',
                //Custom Fields
                'user_id' => 'user_id',
                'attribute' => 'attribute',
                'value' => 'value',
            ),
        );

        // Initialize $app['user.options'].
        $app['user.options.init'] = $app->protect(function() use ($app) {
            $options = $app['user.options.default'];
            if (isset($app['user.options'])) {
                // Merge default and configured options
                $options = array_replace_recursive($options, $app['user.options']);

                // Migrate deprecated options for backward compatibility
                if (isset($app['user.options']['layoutTemplate']) && !isset($app['user.options']['templates']['layout'])) {
                    $options['templates']['layout'] = $app['user.options']['layoutTemplate'];
                }
                if (isset($app['user.options']['loginTemplate']) && !isset($app['user.options']['templates']['login'])) {
                    $options['templates']['login'] = $app['user.options']['loginTemplate'];
                }
                if (isset($app['user.options']['registerTemplate']) && !isset($app['user.options']['templates']['register'])) {
                    $options['templates']['register'] = $app['user.options']['registerTemplate'];
                }
                if (isset($app['user.options']['viewTemplate']) && !isset($app['user.options']['templates']['view'])) {
                    $options['templates']['view'] = $app['user.options']['viewTemplate'];
                }
                if (isset($app['user.options']['editTemplate']) && !isset($app['user.options']['templates']['edit'])) {
                    $options['templates']['edit'] = $app['user.options']['editTemplate'];
                }
                if (isset($app['user.options']['listTemplate']) && !isset($app['user.options']['templates']['list'])) {
                    $options['templates']['list'] = $app['user.options']['listTemplate'];
                }
            }
            $app['user.options'] = $options;
        });

        // Token generator.
        $app['user.tokenGenerator'] = $app->share(function($app) { return new TokenGenerator($app['logger']); });

        // User manager.
        $app['user.manager'] = $app->share(function($app) {
            $app['user.options.init']();

            $userManager = new UserManager($app['db'], $app);
            $userManager->setUserClass($app['user.options']['userClass']);
            $userManager->setUsernameRequired($app['user.options']['isUsernameRequired']);
            $userManager->setUserTableName($app['user.options']['userTableName']);
            $userManager->setUserCustomFieldsTableName($app['user.options']['userCustomFieldsTableName']);
            $userManager->setUserColumns($app['user.options']['userColumns']);

            return $userManager;
        });

        // Current user.
        $app['user'] = $app->share(function($app) {
            return ($app['user.manager']->getCurrentUser());
        });

        // User controller service.
        $app['user.controller'] = $app->share(function ($app) {
            $app['user.options.init']();

            $controller = new UserController($app['user.manager']);
            $controller->setUsernameRequired($app['user.options']['isUsernameRequired']);
            $controller->setEmailConfirmationRequired($app['user.options']['emailConfirmation']['required']);
            $controller->setTemplates($app['user.options']['templates']);
            $controller->setEditCustomFields($app['user.options']['editCustomFields']);

            return $controller;
        });

        // User mailer.
        $app['user.mailer'] = $app->share(function($app) {
            $app['user.options.init']();

            $missingDeps = array();
            if (!isset($app['mailer'])) $missingDeps[] = 'SwiftMailerServiceProvider';
            if (!isset($app['url_generator'])) $missingDeps[] = 'UrlGeneratorServiceProvider';
            if (!isset($app['twig'])) $missingDeps[] = 'TwigServiceProvider';
            if (!empty($missingDeps)) {
                throw new \RuntimeException('To access the SimpleUser mailer you must enable the following missing dependencies: ' . implode(', ', $missingDeps));
            }

            $mailer = new Mailer($app['mailer'], $app['url_generator'], $app['twig']);
            $mailer->setFromAddress($app['user.options']['mailer']['fromEmail']['address'] ?: null);
            $mailer->setFromName($app['user.options']['mailer']['fromEmail']['name'] ?: null);
            $mailer->setConfirmationTemplate($app['user.options']['emailConfirmation']['template']);
            $mailer->setResetTemplate($app['user.options']['passwordReset']['template']);
            $mailer->setResetTokenTtl($app['user.options']['passwordReset']['tokenTTL']);
            if (!$app['user.options']['mailer']['enabled']) {
                $mailer->setNoSend(true);
            }

            return $mailer;
        });

        // Add a custom security voter to support testing user attributes.
        $app['security.voters'] = $app->extend('security.voters', function($voters) use ($app) {
            foreach ($voters as $voter) {
                if ($voter instanceof RoleHierarchyVoter) {
                    $roleHierarchyVoter = $voter;
                    break;
                }
            }
            $voters[] = new EditUserVoter($roleHierarchyVoter);
            return $voters;
        });

        // Helper function to get the last authentication exception thrown for the given request.
        // It does the same thing as $app['security.last_error'](),
        // except it returns the whole exception instead of just $exception->getMessage()
        $app['user.last_auth_exception'] = $app->protect(function (Request $request) {
            if ($request->attributes->has(SecurityContextInterface::AUTHENTICATION_ERROR)) {
                return $request->attributes->get(SecurityContextInterface::AUTHENTICATION_ERROR);
            }

            $session = $request->getSession();
            if ($session && $session->has(SecurityContextInterface::AUTHENTICATION_ERROR)) {
                $exception = $session->get(SecurityContextInterface::AUTHENTICATION_ERROR);
                $session->remove(SecurityContextInterface::AUTHENTICATION_ERROR);

                return $exception;
            }
        });
    }

    /**
     * Bootstraps the application.
     *
     * This method is called after all services are registered
     * and should be used for "dynamic" configuration (whenever
     * a service must be requested).
     */
    public function boot(Application $app)
    {
        // Add twig template path.
        if (isset($app['twig.loader.filesystem'])) {
            $app['twig.loader.filesystem']->addPath(__DIR__ . '/views/', 'user');
        }

        // Validate the mailer configuration.
        $app['user.options.init']();
        try {
            $mailer = $app['user.mailer'];
            $mailerExists = true;
        } catch (\RuntimeException $e) {
            $mailerExists = false;
            $mailerError = $e->getMessage();
        }
        if ($app['user.options']['emailConfirmation']['required'] && !$mailerExists) {
            throw new \RuntimeException('Invalid configuration. Cannot require email confirmation because user mailer is not available. ' . $mailerError);
        }
        if ($app['user.options']['mailer']['enabled'] && !$app['user.options']['mailer']['fromEmail']['address']) {
            throw new \RuntimeException('Invalid configuration. Mailer fromEmail address is required when mailer is enabled.');
        }
        if (!$mailerExists) {
            $app['user.controller']->setPasswordResetEnabled(false);
        }

        if (isset($app['user.passwordStrengthValidator'])) {
            $app['user.manager']->setPasswordStrengthValidator($app['user.passwordStrengthValidator']);
        }
    }

    /**
     * Returns routes to connect to the given application.
     *
     * @param Application $app An Application instance
     *
     * @return ControllerCollection A ControllerCollection instance
     * @throws \LogicException if ServiceController service provider is not registered.
     */
    public function connect(Application $app)
    {
        if (!$app['resolver'] instanceof ServiceControllerResolver) {
            // using RuntimeException crashes PHP?!
            throw new \LogicException('You must enable the ServiceController service provider to be able to use these routes.');
        }

        /** @var ControllerCollection $controllers */
        $controllers = $app['controllers_factory'];

        $controllers->get('/', 'user.controller:viewSelfAction')
            ->bind('user')
            ->before(function(Request $request) use ($app) {
                // Require login. This should never actually cause access to be denied,
                // but it causes a login form to be rendered if the viewer is not logged in.
                if (!$app['user']) {
                    throw new AccessDeniedException();
                }
            });

        $controllers->get('/{id}', 'user.controller:viewAction')
            ->bind('user.view')
            ->assert('id', '\d+');

        $controllers->method('GET|POST')->match('/{id}/edit', 'user.controller:editAction')
            ->bind('user.edit')
            ->before(function(Request $request) use ($app) {
                if (!$app['security']->isGranted('EDIT_USER_ID', $request->get('id'))) {
                    throw new AccessDeniedException();
                }
            });

        $controllers->get('/list', 'user.controller:listAction')
            ->bind('user.list');

        $controllers->method('GET|POST')->match('/register', 'user.controller:registerAction')
            ->bind('user.register');

        $controllers->get('/confirm-email/{token}', 'user.controller:confirmEmailAction')
            ->bind('user.confirm-email');

        $controllers->post('/resend-confirmation', 'user.controller:resendConfirmationAction')
            ->bind('user.resend-confirmation');

        $controllers->get('/login', 'user.controller:loginAction')
            ->bind('user.login');

        $controllers->method('GET|POST')->match('/forgot-password', 'user.controller:forgotPasswordAction')
            ->bind('user.forgot-password');

        $controllers->get('/reset-password/{token}', 'user.controller:resetPasswordAction')
            ->bind('user.reset-password');

        // login_check and logout are dummy routes so we can use the names.
        // The security provider should intercept these, so no controller is needed.
        $controllers->method('GET|POST')->match('/login_check', function() {})
            ->bind('user.login_check');
        $controllers->get('/logout', function() {})
            ->bind('user.logout');

        return $controllers;
    }
}


<?php

namespace Stem;

class LinguaStemRu
{
    var $VERSION = "0.02";
    var $Stem_Caching = 0;
    var $Stem_Cache = array();
    var $VOWEL = '/аеиоуыэюя/';
    var $PERFECTIVEGROUND = '/((ив|ивши|ившись|ыв|ывши|ывшись)|((?<=[ая])(в|вши|вшись)))$/';
    var $REFLEXIVE = '/(с[яь])$/';
    var $ADJECTIVE = '/(ее|ие|ые|ое|ими|ыми|ей|ий|ый|ой|ем|им|ым|ом|его|ого|еых|ую|юю|ая|яя|ою|ею)$/';
    var $PARTICIPLE = '/((ивш|ывш|ующ)|((?<=[ая])(ем|нн|вш|ющ|щ)))$/';
    var $VERB = '/((ила|ыла|ена|ейте|уйте|ите|или|ыли|ей|уй|ил|ыл|им|ым|ены|ить|ыть|ишь|ую|ю)|((?<=[ая])(ла|на|ете|йте|ли|й|л|ем|н|ло|но|ет|ют|ны|ть|ешь|нно)))$/';
    var $NOUN = '/(а|ев|ов|ие|ье|е|иями|ями|ами|еи|ии|и|ией|ей|ой|ий|й|и|ы|ь|ию|ью|ю|ия|ья|я)$/';
    var $RVRE = '/^(.*?[аеиоуыэюя])(.*)$/';
    var $DERIVATIONAL = '/[^аеиоуыэюя][аеиоуыэюя]+[^аеиоуыэюя]+[аеиоуыэюя].*(?<=о)сть?$/';

    function __construct() {
        mb_internal_encoding('UTF-8');
    }

    function s(&$s, $re, $to)
    {
        $orig = $s;
        $s = preg_replace($re, $to, $s);
        return $orig !== $s;
    }

    function m($s, $re)
    {
        return preg_match($re, $s);
    }

    function stem_word($word)
    {
        $word = mb_strtolower($word);
        $word = str_replace('ё', 'е', $word); // замена ё на е, что бы учитывалась как одна и та же буква
        # Check against cache of stemmed words
        if ($this->Stem_Caching && isset($this->Stem_Cache[$word])) {
            return $this->Stem_Cache[$word];
        }
        $stem = $word;
        do {
          if (!preg_match($this->RVRE, $word, $p)) break;
          $start = $p[1];
          $RV = $p[2];
          if (!$RV) break;

          # Step 1
          if (!$this->s($RV, $this->PERFECTIVEGROUND, '')) {
              $this->s($RV, $this->REFLEXIVE, '');

              if ($this->s($RV, $this->ADJECTIVE, '')) {
                  $this->s($RV, $this->PARTICIPLE, '');
              } else {
                  if (!$this->s($RV, $this->VERB, ''))
                      $this->s($RV, $this->NOUN, '');
              }
          }

          # Step 2
          $this->s($RV, '/и$/', '');

          # Step 3
          if ($this->m($RV, $this->DERIVATIONAL))
              $this->s($RV, '/ость?$/', '');

          # Step 4
          if (!$this->s($RV, '/ь$/', '')) {
              $this->s($RV, '/ейше?/', '');
              $this->s($RV, '/нн$/', 'н');
          }

          $stem = $start.$RV;
        } while(false);
        if ($this->Stem_Caching) $this->Stem_Cache[$word] = $stem;
        return $stem;
    }

    /**
     * Стэмит все русские слова в тексте, оставляя пробелы и прочие знаки препинания на месте.
     * @param $text
     * @return string
     */
    function stem_text($text)
    {
        $separators_arr= array('?',' ', '.', ',', ';','!','"','\'','`',"\r","\n","\t");
        $pos = 0;
        while($pos<mb_strlen($text)){
            $min_new_pos = mb_strlen($text);
            foreach ($separators_arr as $sep) {
                $newpos_candidate = mb_strpos($text, $sep, $pos);
                if($newpos_candidate!==FALSE) {
                    $min_new_pos = ($newpos_candidate < $min_new_pos) ? $newpos_candidate : $min_new_pos;
                }
            }
            $newpos = $min_new_pos;
            $word_part = mb_substr($text, $pos, $newpos-$pos);
            $word = preg_replace("/[^АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдеёжзийклмнопрстуфхцчшщъыьэюя\x{2010}-]/u","",$word_part);
            if($word == ''){
                $pos = $newpos+1;
            }else{
                $word_stemmed = $this->stem_word($word);
                $word_stemmed_part = str_replace($word,$word_stemmed,$word_part);

                $text = mb_substr($text,0,$pos) . $word_stemmed_part . mb_substr($text, $newpos);

                $pos = $newpos - (mb_strlen($word)-mb_strlen($word_stemmed));
            }
        }
        return $text;
    }

    function stem_caching($parm_ref)
    {
        $caching_level = @$parm_ref['-level'];
        if ($caching_level) {
            if (!$this->m($caching_level, '/^[012]$/')) {
                die(__CLASS__ . "::stem_caching() - Legal values are '0','1' or '2'. '$caching_level' is not a legal value");
            }
            $this->Stem_Caching = $caching_level;
        }
        return $this->Stem_Caching;
    }

    function clear_stem_cache()
    {
        $this->Stem_Cache = array();
    }
}


<?php

namespace Equip;

use Equip\Action;
use Equip\Exception\DirectoryException;
use Equip\Structure\Dictionary;

class Directory extends Dictionary
{
    const ANY = '*';
    const GET = 'GET';
    const POST = 'POST';
    const PUT = 'PUT';
    const PATCH = 'PATCH';
    const HEAD = 'HEAD';
    const DELETE = 'DELETE';
    const OPTIONS = 'OPTIONS';

    /**
     * @var string
     */
    private $prefix = '';

    /**
     * Set the directory path prefix.
     *
     * @param string $prefix
     *
     * @return static
     */
    public function withPrefix($prefix)
    {
        $copy = clone $this;
        $copy->prefix = '/' . trim($prefix, '/');

        return $copy;
    }

    /**
     * Remove the directory path prefix.
     *
     * @return static
     */
    public function withoutPrefix()
    {
        $copy = clone $this;
        $copy->prefix = '';

        return $copy;
    }

    /**
     * Add the prefix to a path.
     *
     * @param string $path
     *
     * @return string
     */
    public function prefix($path)
    {
        return $this->prefix . $path;
    }

    /**
     * @param string $path
     * @param string|Action $domainOrAction
     *
     * @return static
     */
    public function any($path, $domainOrAction)
    {
        return $this->action(self::ANY, $path, $domainOrAction);
    }

    /**
     * @param string $path
     * @param string|Action $domainOrAction
     *
     * @return static
     */
    public function get($path, $domainOrAction)
    {
        return $this->action(self::GET, $path, $domainOrAction);
    }

    /**
     * @param string $path
     * @param string|Action $domainOrAction
     *
     * @return static
     */
    public function post($path, $domainOrAction)
    {
        return $this->action(self::POST, $path, $domainOrAction);
    }

    /**
     * @param string $path
     * @param string|Action $domainOrAction
     *
     * @return static
     */
    public function put($path, $domainOrAction)
    {
        return $this->action(self::PUT, $path, $domainOrAction);
    }

    /**
     * @param string $path
     * @param string|Action $domainOrAction
     *
     * @return static
     */
    public function patch($path, $domainOrAction)
    {
        return $this->action(self::PATCH, $path, $domainOrAction);
    }

    /**
     * @param string $path
     * @param string|Action $domainOrAction
     *
     * @return static
     */
    public function head($path, $domainOrAction)
    {
        return $this->action(self::HEAD, $path, $domainOrAction);
    }

    /**
     * @param string $path
     * @param string|Action $domainOrAction
     *
     * @return static
     */
    public function delete($path, $domainOrAction)
    {
        return $this->action(self::DELETE, $path, $domainOrAction);
    }

    /**
     * @param string $path
     * @param string|Action $domainOrAction
     *
     * @return static
     */
    public function options($path, $domainOrAction)
    {
        return $this->action(self::OPTIONS, $path, $domainOrAction);
    }

    /**
     * @param string $method
     * @param string $path
     * @param string|Action $domainOrAction
     *
     * @return static
     */
    public function action($method, $path, $domainOrAction)
    {
        if ($domainOrAction instanceof Action) {
            $action = $domainOrAction;
        } else {
            $action = new Action($domainOrAction);
        }

        return $this->withValue(sprintf('%s %s', $method, $path), $action);
    }

    /**
     * @inheritDoc
     *
     * @throws DirectoryException If a value is not an Action instance
     */
    protected function assertValid(array $data)
    {
        parent::assertValid($data);

        foreach ($data as $value) {
            if (!is_object($value) || !$value instanceof Action) {
                throw DirectoryException::invalidEntry($value);
            }
        }
    }
}


<?php

namespace Equip\Responder;

use Equip\Adr\PayloadInterface;
use Equip\Adr\ResponderInterface;
use Equip\Exception\FormatterException;
use Equip\Formatter\FormatterInterface;
use Equip\Formatter\JsonFormatter;
use Equip\Resolver\ResolverTrait;
use Equip\Structure\SortedDictionary;
use Negotiation\Negotiator;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Relay\ResolverInterface;

class FormattedResponder extends SortedDictionary implements ResponderInterface
{
    use ResolverTrait;

    /**
     * @var Negotiator
     */
    private $negotiator;

    /**
     * @param Negotiator $negotiator
     * @param ResolverInterface $resolver
     */
    public function __construct(
        Negotiator $negotiator,
        ResolverInterface $resolver,
        array $formatters = [
            JsonFormatter::class => 1.0,
        ]
    ) {
        $this->negotiator = $negotiator;
        $this->resolver   = $resolver;

        parent::__construct($formatters);
    }

    /**
     * @inheritDoc
     */
    public function __invoke(
        ServerRequestInterface $request,
        ResponseInterface $response,
        PayloadInterface $payload
    ) {
        if ($this->hasOutput($payload)) {
            $response = $this->format($request, $response, $payload);
        }

        return $response;
    }

    /**
     * Determine if the payload has usable output
     *
     * @param PayloadInterface $payload
     *
     * @return boolean
     */
    protected function hasOutput(PayloadInterface $payload)
    {
        return (bool) $payload->getOutput();
    }

    /**
     * Retrieve a map of accepted priorities with the responsible formatter.
     *
     * @return array
     */
    protected function priorities()
    {
        $priorities = [];

        foreach ($this as $formatter => $quality) {
            foreach ($formatter::accepts() as $type) {
                $priorities[$type] = $formatter;
            }
        }

        return $priorities;
    }

    /**
     * Retrieve the formatter to use for the current request.
     *
     * Uses content negotiation to find the best available output format for
     * the requested content type.
     *
     * @param ServerRequestInterface $request
     *
     * @return FormatterInterface
     */
    protected function formatter(ServerRequestInterface $request)
    {
        $accept = $request->getHeaderLine('Accept');
        $priorities = $this->priorities();

        if (!empty($accept)) {
            $preferred = $this->negotiator->getBest($accept, array_keys($priorities));
        }

        if (!empty($preferred)) {
            $formatter = $priorities[$preferred->getValue()];
        } else {
            $formatter = array_shift($priorities);
        }

        return $this->resolve($formatter);
    }

    /**
     * Update the response by formatting the payload.
     *
     * @param ServerRequestInterface $request
     * @param ResponseInterface $response
     * @param PayloadInterface $payload
     *
     * @return ResponseInterface
     */
    protected function format(
        ServerRequestInterface $request,
        ResponseInterface $response,
        PayloadInterface $payload
    ) {
        $formatter = $this->formatter($request);

        $response = $response->withHeader('Content-Type', $formatter->type());
        // Overwrite the body instead of making a copy and dealing with the stream.
        $response->getBody()->write($formatter->body($payload));

        return $response;
    }

    /**
     * @inheritDoc
     *
     * @throws FormatterException
     *  If $classes does not implement the correct interface,
     *  or does not have a quality value.
     */
    protected function assertValid(array $classes)
    {
        parent::assertValid($classes);

        foreach ($classes as $formatter => $quality) {
            if (!is_subclass_of($formatter, FormatterInterface::class)) {
                throw FormatterException::invalidClass($formatter);
            }

            if (!is_float($quality)) {
                throw FormatterException::needsQuality($formatter);
            }
        }
    }

    /**
     * @inheritDoc
     */
    protected function sortValues()
    {
        arsort($this->values);
    }
}


<?php

namespace Equip\Responder;

use Equip\Adr\PayloadInterface;
use Equip\Adr\ResponderInterface;
use Equip\Exception\ResponderException;
use Equip\Resolver\ResolverTrait;
use Equip\Structure\Set;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Relay\ResolverInterface;

class ChainedResponder extends Set implements ResponderInterface
{
    use ResolverTrait;

    /**
     * @param ResolverInterface $resolver
     * @param array $responders
     */
    public function __construct(
        ResolverInterface $resolver,
        array $responders = [
            FormattedResponder::class,
            RedirectResponder::class,
            StatusResponder::class,
        ]
    ) {
        $this->resolver = $resolver;

        parent::__construct($responders);
    }

    /**
     * @inheritDoc
     */
    public function __invoke(
        ServerRequestInterface $request,
        ResponseInterface $response,
        PayloadInterface $payload
    ) {
        foreach ($this as $responder) {
            $responder = $this->resolve($responder);
            $response = $responder($request, $response, $payload);
        }

        return $response;
    }

    /**
     * @inheritDoc
     *
     * @throws ResponderException
     *  If $classes does not implement the correct interface.
     */
    protected function assertValid(array $classes)
    {
        parent::assertValid($classes);

        foreach ($classes as $responder) {
            if (!is_subclass_of($responder, ResponderInterface::class)) {
                throw ResponderException::invalidClass($responder);
            }
        }
    }
}


<?php

namespace Equip\Configuration;

use Auryn\Injector;
use Equip\Env;
use Equip\Exception\EnvException;
use josegonzalez\Dotenv\Loader;

class EnvConfiguration implements ConfigurationInterface
{
    /**
     * @var string
     */
    private $envfile;

    /**
     * @param string $envfile
     *
     * @throws EnvException If a missing or unreadable file is specified
     */
    public function __construct($envfile = null)
    {
        if (empty($envfile)) {
            $envfile = $this->detectEnvFile();
        }

        if (!is_file($envfile) || !is_readable($envfile)) {
            throw EnvException::invalidFile($envfile);
        }

        $this->envfile = $envfile;
    }

    /**
     * @inheritDoc
     */
    public function apply(Injector $injector)
    {
        $injector->define(Loader::class, [
            ':filepaths' => $this->envfile,
        ]);

        $injector->share(Env::class);

        $injector->prepare(Env::class, function (Env $env, Injector $injector) {
            $loader = $injector->make(Loader::class);
            $values = $loader->parse()->toArray();
            return $env->withValues($values);
        });
    }

    /**
     * Find a .env file by traversing up the filesystem
     *
     * @return string
     *
     * @throws EnvException If no file is found
     */
    private function detectEnvFile()
    {
        $env = DIRECTORY_SEPARATOR . '.env';
        $dir = dirname(dirname(__DIR__));

        do {
            if (is_file($dir . $env)) {
                return $dir . $env;
            }
            $dir = dirname($dir);
        } while (is_readable($dir) && dirname($dir) !== $dir);

        throw EnvException::detectionFailed();
    }
}


<?php

namespace Equip\Handler;

use Equip\Exception\HttpException;
use Exception;
use InvalidArgumentException;
use Negotiation\Negotiator;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Log\LoggerInterface;
use Relay\ResolverInterface;
use Whoops\Run as Whoops;

class ExceptionHandler
{
    /**
     * @var Negotiator
     */
    private $negotiator;

    /**
     * @var ExceptionHandlerPreferences
     */
    private $preferences;

    /**
     * @var ResolverInterface
     */
    private $resolver;

    /**
     * @var Whoops
     */
    private $whoops;

    /**
     * @var LoggerInterface|null
     */
    private $logger;

    /**
     * @param ExceptionHandlerPreferences $preferences
     * @param Negotiator $negotiator
     * @param ResolverInterface $resolver
     * @param Whoops $whoops
     * @param LoggerInterface|null $logger
     */
    public function __construct(
        ExceptionHandlerPreferences $preferences,
        Negotiator $negotiator,
        ResolverInterface $resolver,
        Whoops $whoops,
        LoggerInterface $logger = null
    ) {
        $this->preferences = $preferences;
        $this->logger = $logger;
        $this->negotiator = $negotiator;
        $this->resolver = $resolver;
        $this->whoops = $whoops;
    }

    /**
     * @param ServerRequestInterface $request
     * @param ResponseInterface $response
     * @param callable $next
     *
     * @return ResponseInterface
     */
    public function __invoke(
        ServerRequestInterface $request,
        ResponseInterface $response,
        callable $next
    ) {
        try {
            return $next($request, $response);
        } catch (Exception $e) {
            if ($this->logger) {
                $this->logger->error($e->getMessage(), [
                    'exception' => $e
                ]);
            }

            $type = $this->type($request);

            $response = $response->withHeader('Content-Type', $type);

            try {
                if (method_exists($e, 'getHttpStatus')) {
                    $code = $e->getHttpStatus();
                } else {
                    $code = $e->getCode();
                }
                $response = $response->withStatus($code);
            } catch (InvalidArgumentException $_) {
                // Exception did not contain a valid code
                $response = $response->withStatus(500);
            }

            if ($e instanceof HttpException) {
                $response = $e->withResponse($response);
            }

            if ($this->preferences->displayDebug()) {

                $handler = $this->handler($type);
                $this->whoops->pushHandler($handler);

                $body = $this->whoops->handleException($e);
                $response->getBody()->write($body);

                $this->whoops->popHandler();
            }

            return $response;
        }
    }

    /**
     * Determine the preferred content type for the current request
     *
     * @param ServerRequestInterface $request
     *
     * @return string
     */
    private function type(ServerRequestInterface $request)
    {
        $accept = $request->getHeaderLine('Accept');
        $priorities = $this->preferences->toArray();

        if (!empty($accept)) {
            $preferred = $this->negotiator->getBest($accept, array_keys($priorities));
        }

        if (!empty($preferred)) {
            return $preferred->getValue();
        }

        return key($priorities);
    }

    /**
     * Retrieve the handler to use for the given type
     *
     * @param string $type
     *
     * @return \Whoops\Handler\HandlerInterface
     */
    private function handler($type)
    {
        return call_user_func($this->resolver, $this->preferences[$type]);
    }
}


<?php

namespace Equip;

use Auryn\Injector;
use Equip\Configuration\ConfigurationSet;
use Equip\Directory;
use Equip\Middleware\MiddlewareSet;
use Relay\Relay;

class Application
{
    /**
     * Create a new application
     *
     * @param Injector $injector
     * @param ConfigurationSet $configuration
     * @param MiddlewareSet $middleware
     *
     * @return static
     */
    public static function build(
        Injector $injector = null,
        ConfigurationSet $configuration = null,
        MiddlewareSet $middleware = null
    ) {
        return new static($injector, $configuration, $middleware);
    }

    /**
     * @var Injector
     */
    private $injector;

    /**
     * @var ConfigurationSet
     */
    private $configuration;

    /**
     * @var MiddlewareSet
     */
    private $middleware;

    /**
     * @var callable|string
     */
    private $routing;

    /**
     * @param Injector $injector
     * @param ConfigurationSet $configuration
     * @param MiddlewareSet $middleware
     */
    public function __construct(
        Injector $injector = null,
        ConfigurationSet $configuration = null,
        MiddlewareSet $middleware = null
    ) {
        $this->injector = $injector ?: new Injector;
        $this->configuration = $configuration ?: new ConfigurationSet;
        $this->middleware = $middleware ?: new MiddlewareSet;
    }

    /**
     * Change configuration values
     *
     * @param array $configuration
     *
     * @return self
     */
    public function setConfiguration(array $configuration)
    {
        $this->configuration = $this->configuration->withValues($configuration);
        return $this;
    }

    /**
     * Change middleware
     *
     * @param array $middleware
     *
     * @return self
     */
    public function setMiddleware(array $middleware)
    {
        $this->middleware = $this->middleware->withValues($middleware);
        return $this;
    }

    /**
     * Change routing
     *
     * @param callable|string $routing
     *
     * @return self
     */
    public function setRouting($routing)
    {
        $this->routing = $routing;
        return $this;
    }

    /**
     * Run the application
     *
     * @param string $runner
     *
     * @return \Psr\Http\Message\ResponseInterface
     */
    public function run($runner = Relay::class)
    {
        $this->configuration->apply($this->injector);

        return $this->injector
            ->share($this->middleware)
            ->prepare(Directory::class, $this->routing)
            ->execute($runner);
    }
}


<?php

namespace Equip\Handler;

use Equip\Action;
use Equip\Adr\DomainInterface;
use Equip\Adr\InputInterface;
use Equip\Adr\PayloadInterface;
use Equip\Adr\ResponderInterface;
use Equip\Resolver\ResolverTrait;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Relay\ResolverInterface;

class ActionHandler
{
    use ResolverTrait;

    const ACTION_ATTRIBUTE = 'equip/adr:action';

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

    /**
     * @param ServerRequestInterface $request
     * @param ResponseInterface $response
     * @param callable $next
     */
    public function __invoke(
        ServerRequestInterface $request,
        ResponseInterface $response,
        callable $next
    ) {
        $action = $request->getAttribute(self::ACTION_ATTRIBUTE);
        $request = $request->withoutAttribute(self::ACTION_ATTRIBUTE);

        $response = $this->handle($action, $request, $response);

        return $next($request, $response);
    }

    /**
     * Use the action collaborators to get a response.
     *
     * @param Action $action
     * @param ServerRequestInterface $request
     * @param ResponseInterface $response
     *
     * @return ResponseInterface
     */
    private function handle(
        Action $action,
        ServerRequestInterface $request,
        ResponseInterface $response
    ) {
        $domain = $this->resolve($action->getDomain());
        $input = $this->resolve($action->getInput());
        $responder = $this->resolve($action->getResponder());

        $payload = $this->payload($domain, $input, $request);
        $response = $this->response($responder, $request, $response, $payload);

        return $response;
    }

    /**
     * Execute the domain to get a payload using input from the request.
     *
     * @param DomainInterface $domain
     * @param InputInterface $input
     * @param ServerRequestInterface $request
     *
     * @return PayloadInterface
     */
    private function payload(
        DomainInterface $domain,
        InputInterface $input,
        ServerRequestInterface $request
    ) {
        return $domain($input($request));
    }

    /**
     * Execute the responder to marshall the payload into the response.
     *
     * @param ResponderInterface $responder
     * @param ServerRequestInterface $request
     * @param ResponseInterface $response
     * @param PayloadInterface $payload
     *
     * @return ResponseInterface
     */
    private function response(
        ResponderInterface $responder,
        ServerRequestInterface $request,
        ResponseInterface $response,
        PayloadInterface $payload
    ) {
        return $responder($request, $response, $payload);
    }
}


<?php

namespace Equip\Exception;

use Equip\Action;
use InvalidArgumentException;

class DirectoryException extends InvalidArgumentException
{
    /**
     * @param mixed $value
     *
     * @return static
     */
    public static function invalidEntry($value)
    {
        if (is_object($value)) {
            $value = get_class($value);
        }

        return new static(sprintf(
            'Directory entry `%s` is not an `%s` instance',
            $value,
            Action::class
        ));
    }
}


<?php

namespace Equip\Exception;

use Psr\Http\Message\ResponseInterface;
use RuntimeException;

class HttpException extends RuntimeException
{
    /**
     * @param string $path
     *
     * @return static
     */
    public static function notFound($path)
    {
        return new static(sprintf(
            'Cannot find any resource at `%s`',
            $path
        ), 404);
    }

    /**
     * @param string $path
     * @param string $method
     * @param array $allowed
     *
     * @return static
     */
    public static function methodNotAllowed($path, $method, array $allowed)
    {
        $error = new static(sprintf(
            'Cannot access resource `%s` using method `%s`',
            $path,
            $method
        ), 405);

        $error->allowed = $allowed;

        return $error;
    }

    /**
     * @param string $message
     *
     * @return static
     */
    public static function badRequest($message)
    {
        return new static(sprintf(
            'Cannot parse the request: %s',
            $message
        ), 400);
    }

    /**
     * @var array
     */
    private $allowed = [];

    /**
     * @param ResponseInterface $response
     *
     * @return ResponseInterface
     */
    public function withResponse(ResponseInterface $response)
    {
        if (!empty($this->allowed)) {
            $response = $response->withHeader('Allow', implode(',', $this->allowed));
        }

        return $response;
    }
}


<?php

namespace Equip\Handler;

use Equip\Directory;
use Equip\Exception\HttpException;
use Equip\Handler\ActionHandler;
use FastRoute;
use FastRoute\Dispatcher;
use FastRoute\RouteCollector;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

class DispatchHandler
{
    /**
     * @var Directory
     */
    private $directory;

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

    /**
     * @inheritDoc
     */
    public function __invoke(
        ServerRequestInterface $request,
        ResponseInterface $response,
        callable $next
    ) {
        /**
         * @var $action Equip\Action
         */
        list($action, $args) = $this->dispatch(
            $this->dispatcher(),
            $request->getMethod(),
            $request->getUri()->getPath()
        );

        $request = $request->withAttribute(ActionHandler::ACTION_ATTRIBUTE, $action);

        foreach ($args as $key => $value) {
            $request = $request->withAttribute($key, $value);
        }

        return $next($request, $response);
    }

    /**
     * @return Dispatcher
     */
    protected function dispatcher()
    {
        return FastRoute\simpleDispatcher(function (RouteCollector $collector) {
            foreach ($this->directory as $request => $action) {
                list($method, $path) = explode(' ', $request, 2);

                $collector->addRoute(
                    $method,
                    $this->directory->prefix($path),
                    $action
                );
            }
        });
    }

    /**
     * @throws HttpNotFound
     * @throws HttpMethodNotAllowed
     *
     * @param Dispatcher $dispatcher
     * @param string $method
     * @param string $path
     *
     * @return array [Action, $arguments]
     */
    private function dispatch(Dispatcher $dispatcher, $method, $path)
    {
        $route = $dispatcher->dispatch($method, $path);
        $status = array_shift($route);

        if (Dispatcher::FOUND === $status) {
            return $route;
        }

        if (Dispatcher::METHOD_NOT_ALLOWED === $status) {
            $allowed = array_shift($route);
            throw HttpException::methodNotAllowed($path, $method, $allowed);
        }

        throw HttpException::notFound($path);
    }
}


<?php

namespace Equip\Formatter;

use Equip\Adr\PayloadInterface;
use Equip\Formatter\HtmlFormatter;
use League\Plates\Engine;

class PlatesFormatter extends HtmlFormatter
{
    /**
     * @var Engine
     */
    private $engine;

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

    /**
     * @inheritDoc
     */
    public function body(PayloadInterface $payload)
    {
        return $this->render($payload);
    }

    /**
     * @param PayloadInterface $payload
     *
     * @return string
     */
    private function render(PayloadInterface $payload)
    {
        $template = $payload->getSetting('template');
        $output = $payload->getOutput();

        return $this->engine->render($template, $output);
    }
}


<?php

namespace Equip;

use Equip\Adr\InputInterface;
use Psr\Http\Message\ServerRequestInterface;

class Input implements InputInterface
{
    /**
     * Flatten all input from the request.
     *
     * @param ServerRequestInterface $request
     *
     * @return array
     */
    public function __invoke(
        ServerRequestInterface $request
    ) {
        $attrs = $request->getAttributes();
        $body = $this->body($request);
        $cookies = $request->getCookieParams();
        $query = $request->getQueryParams();
        $uploads = $request->getUploadedFiles();

        // Order matters here! Important values go last!
        return array_replace(
            $query,
            $body,
            $uploads,
            $cookies,
            $attrs
        );
    }

    /**
     * @param ServerRequestInterface $request
     *
     * @return array
     */
    private function body(ServerRequestInterface $request)
    {
        $body = $request->getParsedBody();

        if (empty($body)) {
            return [];
        }

        if (is_object($body)) {
            // Because the parsed body may also be represented as an object,
            // additional parsing is required. This is a bit dirty but works
            // very well for anonymous objects.
            $body = json_decode(json_encode($body), true);
        }

        return $body;
    }
}


<?php

namespace Equip\Middleware;

use Equip\Exception\MiddlewareException;
use Equip\Structure\Set;

class MiddlewareSet extends Set
{
    /**
     * @inheritDoc
     *
     * @throws MiddlewareException
     *  If $classes does not conform to type expectations.
     */
    protected function assertValid(array $classes)
    {
        parent::assertValid($classes);

        foreach ($classes as $middleware) {
            if (!(is_callable($middleware) || method_exists($middleware, '__invoke'))) {
                throw MiddlewareException::notInvokable($middleware);
            }
        }
    }
}


<?php

namespace Equip\Responder;

use Equip\Adr\PayloadInterface;
use Equip\Adr\ResponderInterface;
use Lukasoppermann\Httpstatus\Httpstatus;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

class StatusResponder implements ResponderInterface
{
    /**
     * @var Httpstatus
     */
    private $http_status;

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

    /**
     * @inheritDoc
     */
    public function __invoke(
        ServerRequestInterface $request,
        ResponseInterface $response,
        PayloadInterface $payload
    ) {
        if ($this->hasStatus($payload)) {
            $response = $this->status($response, $payload);
        }

        return $response;
    }

    /**
     * Determine if the payload has a status.
     *
     * @param PayloadInterface $payload
     *
     * @return boolean
     */
    private function hasStatus(PayloadInterface $payload)
    {
        return (bool) $payload->getStatus();
    }

    /**
     * Get the response with the status code from the payload.
     *
     * @param ResponseInterface $response
     * @param PayloadInterface $payload
     *
     * @return ResponseInterface
     */
    private function status(
        ResponseInterface $response,
        PayloadInterface $payload
    ) {
        $status = $payload->getStatus();
        $code = $this->http_status->getStatusCode($status);

        return $response->withStatus($code);
    }
}


<?php

namespace Equip\Configuration;

use Auryn\Injector;
use Whoops\Handler\JsonResponseHandler;
use Whoops\Handler\PlainTextHandler;
use Whoops\Run as Whoops;

class WhoopsConfiguration implements ConfigurationInterface
{
    use EnvTrait;

    /**
     * @inheritDoc
     */
    public function apply(Injector $injector)
    {
        $injector->prepare(Whoops::class, [$this, 'prepareWhoops']);
        $injector->prepare(JsonResponseHandler::class, [$this, 'prepareJsonHandler']);
        $injector->prepare(PlainTextHandler::class, [$this, 'preparePlainTextHandler']);
    }

    /**
     * @param Whoops $whoops
     *
     * @return void
     */
    public function prepareWhoops(Whoops $whoops)
    {
        if($this->env->getValue('DEBUG_STACKTRACE', false) == true) {
            set_error_handler([$whoops, Whoops::ERROR_HANDLER]);
        }

        $whoops->writeToOutput(false);
        $whoops->allowQuit(false);
    }

    /**
     * @param JsonResponseHandler $handler
     *
     * @return void
     */
    public function prepareJsonHandler(JsonResponseHandler $handler)
    {
        $handler->addTraceToOutput(true);
    }

    /**
     * @param PlainTextHandler $handler
     *
     * @return void
     */
    public function preparePlainTextHandler(PlainTextHandler $handler)
    {
        $handler->addTraceToOutput(true);
    }
}


<?php

namespace Osedea\LaravelRest\Commands\DefaultCommand;

use Illuminate\Contracts\Bus\SelfHandling;
use Osedea\LaravelRest\Commands\Command;

class ShowCommand extends Command implements SelfHandling
{
    public $modelClass;
    public $id;

    function __construct($modelClass, $id)
    {
        $this->modelClass = $modelClass;
        $this->id = $id;
    }

    /**
     * Execute the command.
     *
     * @return \Illuminate\Database\Eloquent\Model
     */
    public function handle()
    {
        $class = $this->modelClass;
        $fields = $this->getFieldsFromRequest();

        return $class::withRequestEmbed()->findOrFail($this->id, $fields);
    }
}


<?php

namespace Osedea\LaravelRest\Services;

use Illuminate\Support\Facades\Config;
use InvalidArgumentException;
use RuntimeException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

class TranslatorService
{
    /**
     * Returns a class name base on a resource mapping.
     * The mapping comes from a config file (api.php).
     *
     * Example: `users` should return `\App\Models\User`
     *
     * @param string $resource
     * @return string
     * @throws NotFoundHttpException
     */
    public function getClassFromResource($resource)
    {
        // This is the models namespace
        $modelsNamespace = Config::get('api.models_namespace', Config::get('api.app_namespace'));

        // This array contains mapping between resources and Model classes
        $mapping = Config::get('api.mapping');

        if (!is_array($mapping)) {
            throw new RuntimeException('The config api.mapping needs to be an array.');
        }

        if (!isset($mapping[$resource])) {
            throw new NotFoundHttpException;
        }

        return implode('\\', [$modelsNamespace, $mapping[$resource]]);
    }

    /**
     * Returns a command class name based on a resource mapping.
     *
     * Examples:
     *     - getCommandFromResource('users', 'show') returns \App\Commands\UserCommand\ShowCommand
     *     - getCommandFromResource('users', 'index', 'groups') returns \App\Commands\UserCommand\GroupIndexCommand
     *
     * @param string $resource
     * @param string $action
     * @param string|null $relation
     * @return string
     * @throws NotFoundHttpException
     */
    public function getCommandFromResource($resource, $action, $relation = null)
    {
        $namespace = Config::get('api.app_namespace');
        $mapping = Config::get('api.mapping');

        // If the mapping does not exist, we consider this as a 404 error
        if (!isset($mapping[$resource])) {
            throw new NotFoundHttpException('The resource [' . $resource . '] is not mapped to any Model.');
        }

        $allowedActions = ['index', 'store', 'show', 'update', 'destroy'];

        if (!in_array($action, $allowedActions)) {
            throw new InvalidArgumentException('[' . $action . '] is not a valid action.');
        }

        // If we have a $relation parameter, then we generate a command based on it
        if ($relation) {
            if (!isset($mapping[$relation])) {
                throw new NotFoundHttpException('The resource [' . $resource . '] is not mapped to any Model.');
            }

            $command = implode('\\', [
                $namespace,
                'Commands',
                $mapping[$resource] . 'Command',
                ucfirst($mapping[$relation]) . ucfirst($action) . 'Command'
            ]);
        } else {
            $command = implode('\\', [
                $namespace,
                'Commands',
                $mapping[$resource] . 'Command',
                ucfirst($action) . 'Command'
            ]);
        }

        // If no custom command is found, then we use one of the default ones
        if (!class_exists($command)) {
            if ($relation) {
                $command = implode('\\', [
                    'Osedea',
                    'LaravelRest',
                    'Commands',
                    'DefaultCommand',
                    'Relation' . ucfirst($action) . 'Command'
                ]);
            } else {
                $command = implode('\\', [
                    'Osedea',
                    'LaravelRest',
                    'Commands',
                    'DefaultCommand',
                    ucfirst($action) . 'Command'
                ]);
            }
        }

        if (!class_exists($command)) {
            throw new NotFoundHttpException('There is no default command for this action and resource.');
        }

        return $command;
    }
}


<?php

namespace Osedea\LaravelRest\Traits;

trait CommandModel
{
    /*
         * The attributes that can be used for filtering through the API.
         *
         * @var array
         */
    public static $filterable = [];

    /**
     * This holds the maximum number of items per page fot this model.
     * @var integer
     */
    protected $perPageMax = 25;

    public function getPerPageMax()
    {
        return $this->perPage;
    }

    public function setPerPageMax($perPageMax)
    {
        $this->perPageMax = $perPageMax;
    }

    /**
     * This scope automatically sorts a query depending on the request.
     * A request looks like this:
     *      - Sort by [column ASC]:                     ?sort=column
     *      - Sort by [column DESC]:                    ?sort=-column
     *      - Sort by [column1 ASC] and [column2 DESC]: ?sort=column1,-column2
     *
     * @param $query
     * @return mixed
     */
    public function scopeWithRequestSort($query)
    {
        if (($sorts = \Request::get('sort', false))) {
            // We list all sorting options
            $sorts = explode(',', $sorts);

            foreach ($sorts as $sort) {
                $order = 'asc';

                // If the first character is a dash (-), then the sort order is reversed
                if (substr($sort, 0, 1) === '-') {
                    $order = 'desc';

                    // We need to remove the dash from the sort because it is used in the orderBy method.
                    $sort = substr($sort, 1, strlen($sort));
                }

                $query->orderBy($sort, $order);
            }
        }

        return $query;
    }

    public function scopeWithRequestEmbed($query)
    {
        if (($embeds = \Request::get('embed', false))) {
            $embeds = explode(',', $embeds);

            $query->with($embeds);
        }

        return $query;
    }
}


<?php

namespace Osedea\LaravelRest\Http\Controllers;

use Illuminate\Bus\Dispatcher;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Contracts\Pagination\Paginator;
use Illuminate\Foundation\Bus\DispatchesCommands;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Http\Response as IlluminateResponse;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Response;
use InvalidArgumentException;
use Osedea\LaravelRest\Services\TranslatorService;

abstract class Controller extends BaseController
{
    use DispatchesCommands, ValidatesRequests;

    protected $dispatcher;
    protected $translator;

    public function __construct(Dispatcher $dispatcher, TranslatorService $translator)
    {
        $this->dispatcher = $dispatcher;
        $this->translator = $translator;
    }

    /**
     * This method returns json for a successful request.
     * If $data is paginated, the pagination info will be added in headers.
     *
     * @param $data
     * @param int $code
     * @return \Symfony\Component\HttpFoundation\Response
     */
    public function success($data, $code = 200)
    {
        $headers = [];

        if ($data instanceof Paginator && $data instanceof LengthAwarePaginator) {
            $headers['X-Total-Count'] = $data->total();
            $headers['X-Total-Pages'] = $data->lastPage();
            $headers['X-Total-Current'] = $data->currentPage();

            // We only set this if the current page is not the last one
            if ($data->currentPage() < $data->lastPage()) {
                $headers['X-Page-Next'] = $data->currentPage() + 1;
            }

            // We only set this if the current page is not the first one
            if ($data->currentPage() > 1) {
                $headers['X-Page-Previous'] = $data->currentPage() - 1;
            }

            $data = $data->items();
        }

        return Response::json($data, $code, $headers, JSON_PRETTY_PRINT);
    }

    /**
     * This method returns json for an unsuccessful request.
     * The $errors parameter can be used to send back form validation.
     *
     * @param $message
     * @param int $code
     * @param null $errors
     * @return \Symfony\Component\HttpFoundation\Response
     */
    public function error($message, $code = 500, $errors = null)
    {
        $data = [
            'message' => $message
        ];

        if (is_array($errors)) {
            $data['errors'] = $errors;
        }

        return Response::json($data, $code, [], JSON_PRETTY_PRINT);
    }

    /**
     * This method is for a successful request that won't be returning a body (like a DELETE).
     *
     * @return \Symfony\Component\HttpFoundation\Response
     */
    public function ok()
    {
        return new IlluminateResponse('', 204);
    }

    protected function fireEventForResource($resource, $action, $command)
    {
        $namespace = Config::get('api.app_namespace');
        $mapping = Config::get('api.mapping');

        // If the mapping does not exist, we consider this as a 404 error
        if (!isset($mapping[$resource])) {
            throw new NotFoundHttpException('The resource [' . $resource . '] is not mapped to any Model.');
        }

        $allowedActions = ['index', 'store', 'show', 'update', 'destroy'];

        if (!in_array($action, $allowedActions)) {
            throw new InvalidArgumentException('[' . $action . '] is not a valid action.');
        }

        $event = implode('\\', [$namespace, 'Events', $mapping[$resource] . ucfirst($action) . 'Event']);

        // If no custom event is found, then do nothing.
        if (!class_exists($event)) {
            return;
        }

        Event::fire(new $event($command));
    }

    /**
     * This method can be used in a controller before dispatching a command to pipe it.
     * It will check if the command has commands to run in the command pipeline.
     *
     * @param string $command A command class name
     */
    protected function runBeforeCommands($command)
    {
        $beforeCommands = $command::beforeCommands();

        if (!is_array($beforeCommands) && strlen(trim($beforeCommands)) > 0) {
            $beforeCommands = [$beforeCommands];
        }

        $this->dispatcher->pipeThrough($beforeCommands);
    }
}


<?php

namespace Osedea\LaravelRest\Http\Controllers;

use Illuminate\Support\Facades\Event;
use Illuminate\Http\Request;

/**
 * This is the default controller for api calls.
 * All requests go through here and call the corresponding command handler.
 * Some actions have a default command handler (index, indexRelation, show and destroy).
 *
 * You can override this controller (or just one action) by adding routes with a specified resource name.
 *
 * For example: `Route::get('/pizzas', 'PizzaRestController@index');` won't get here.
 *
 * Class DefaultRestController
 * @package Osedea\LaravelRest\Http\Controllers
 */
class DefaultRestController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @param  Request $request
     * @param  string $resource
     * @return \Response
     */
    public function index(Request $request, $resource)
    {
        $command = $this->translator->getCommandFromResource($resource, 'index');

        $this->runBeforeCommands($command);

        $data = $this->dispatchFrom($command, $request, [
            'modelClass' => $this->translator->getClassFromResource($resource)
        ]);

        $this->fireEventForResource($resource, 'index', $data);

        return $this->success($data);
    }

    /**
     * Display a list of a resource relation.
     *
     * @param  Request $request
     * @param  string $resource
     * @param  int $id
     * @param  string $relation
     * @return \Response
     */
    public function indexRelation(Request $request, $resource, $id, $relation)
    {
        $command = $this->translator->getCommandFromResource($resource, 'index', $relation);

        $this->runBeforeCommands($command);

        $data = $this->dispatchFrom($command, $request, [
            'modelClass' => $this->translator->getClassFromResource($resource),
            'id' => $id,
            'relation' => $relation,
            'relationClass' => $this->translator->getClassFromResource($relation)
        ]);

        $this->fireEventForResource($resource, 'index', $data);

        return $this->success($data);
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  Request $request
     * @param  string $resource
     * @return \Response
     */
    public function store(Request $request, $resource)
    {
        $command = $this->translator->getCommandFromResource($resource, 'store');

        $this->runBeforeCommands($command);

        $data = $this->dispatchFrom($command, $request);

        $this->fireEventForResource($resource, 'store', $data);

        return $this->success($data, 201);
    }

    /**
     * Display the specified resource.
     *
     * @param  Request $request
     * @param  string $resource
     * @param  int $id
     * @return \Response
     */
    public function show(Request $request, $resource, $id)
    {
        $command = $this->translator->getCommandFromResource($resource, 'show');

        $this->runBeforeCommands($command);

        $data = $this->dispatchFrom($command, $request, [
            'modelClass' => $this->translator->getClassFromResource($resource),
            'id' => $id
        ]);

        $this->fireEventForResource($resource, 'show', $data);

        return $this->success($data);
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  Request $request
     * @param  string $resource
     * @param  int $id
     * @return \Response
     */
    public function update(Request $request, $resource, $id)
    {
        $command = $this->translator->getCommandFromResource($resource, 'update');

        $this->runBeforeCommands($command);

        $data = $this->dispatchFrom($command, $request);

        $this->fireEventForResource($resource, 'update', $data);

        return $this->success($data);
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  Request $request
     * @param  string $resource
     * @param  int $id
     * @return \Response
     */
    public function destroy(Request $request, $resource, $id)
    {
        $command = $this->translator->getCommandFromResource($resource, 'destroy');

        $this->runBeforeCommands($command);

        $this->dispatchFrom($command, $request, [
            'modelClass' => $this->translator->getClassFromResource($resource),
            'id' => $id
        ]);

        $this->fireEventForResource($resource, 'destroy', $id);

        return $this->ok();
    }

}