<?php
/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */

namespace Icinga\Controllers;

use Exception;
use Icinga\Application\Logger;
use Icinga\Authentication\AdmissionLoader;
use Icinga\Authentication\User\DomainAwareInterface;
use Icinga\Data\DataArray\ArrayDatasource;
use Icinga\Exception\ConfigurationError;
use Icinga\Exception\NotFoundError;
use Icinga\Forms\Config\User\CreateMembershipForm;
use Icinga\Forms\Config\User\UserForm;
use Icinga\User;
use Icinga\Web\Controller\AuthBackendController;
use Icinga\Web\Form;
use Icinga\Web\Notification;
use Icinga\Web\Url;
use Icinga\Web\Widget;

class UserController extends AuthBackendController
{
    public function init()
    {
        $this->view->title = $this->translate('Users');

        parent::init();
    }

    /**
     * List all users of a single backend
     */
    public function listAction()
    {
        $this->assertPermission('config/access-control/users');
        $this->createListTabs()->activate('user/list');
        $backendNames = array_map(
            function ($b) {
                return $b->getName();
            },
            $this->loadUserBackends('Icinga\Data\Selectable')
        );
        if (empty($backendNames)) {
            return;
        }

        $this->view->backendSelection = new Form();
        $this->view->backendSelection->setAttrib('class', 'backend-selection icinga-controls');
        $this->view->backendSelection->setUidDisabled();
        $this->view->backendSelection->setMethod('GET');
        $this->view->backendSelection->setTokenDisabled();
        $this->view->backendSelection->addElement(
            'select',
            'backend',
            array(
                'autosubmit'    => true,
                'label'         => $this->translate('User Backend'),
                'multiOptions'  => array_combine($backendNames, $backendNames),
                'value'         => $this->params->get('backend')
            )
        );

        $backend = $this->getUserBackend($this->params->get('backend'));
        if ($backend === null) {
            $this->view->backend = null;
            return;
        }

        $query = $backend->select(array('user_name'));

        $this->view->users = $query;
        $this->view->backend = $backend;

        $this->setupPaginationControl($query);
        $this->setupFilterControl($query);
        $this->setupLimitControl();
        $this->setupSortControl(
            array(
                'user_name'     => $this->translate('Username'),
                'is_active'     => $this->translate('Active'),
                'created_at'    => $this->translate('Created at'),
                'last_modified' => $this->translate('Last modified')
            ),
            $query
        );
    }

    /**
     * Show a user
     */
    public function showAction()
    {
        $this->assertPermission('config/access-control/users');
        $userName = $this->params->getRequired('user');
        $backend = $this->getUserBackend($this->params->getRequired('backend'));

        $user = $backend->select(array(
            'user_name',
            'is_active',
            'created_at',
            'last_modified'
        ))->where('user_name', $userName)->fetchRow();
        if ($user === false) {
            $this->httpNotFound(sprintf($this->translate('User "%s" not found'), $userName));
        }

        $userObj = new User($userName);
        if ($backend instanceof DomainAwareInterface) {
            $userObj->setDomain($backend->getDomain());
        }

        $memberships = $this->loadMemberships($userObj)->select();

        $this->setupFilterControl(
            $memberships,
            array('group_name' => t('User Group')),
            array('group'),
            array('user')
        );
        $this->setupPaginationControl($memberships);
        $this->setupLimitControl();
        $this->setupSortControl(
            array(
                'group_name' => $this->translate('Group')
            ),
            $memberships
        );

        if ($this->hasPermission('config/access-control/groups')) {
            $extensibleBackends = $this->loadUserGroupBackends('Icinga\Data\Extensible');
            $this->view->showCreateMembershipLink = ! empty($extensibleBackends);
        } else {
            $this->view->showCreateMembershipLink = false;
        }

        $this->view->user = $user;
        $this->view->backend = $backend;
        $this->view->memberships = $memberships;
        $this->createShowTabs($backend->getName(), $userName)->activate('user/show');

        if ($this->hasPermission('config/access-control/groups')) {
            $removeForm = new Form();
            $removeForm->setUidDisabled();
            $removeForm->setAttrib('class', 'inline');
            $removeForm->addElement('hidden', 'user_name', array(
                'isArray'       => true,
                'value'         => $userName,
                'decorators'    => array('ViewHelper')
            ));
            $removeForm->addElement('hidden', 'redirect', array(
                'value'         => Url::fromPath('user/show', array(
                    'backend'   => $backend->getName(),
                    'user'      => $userName
                )),
                'decorators'    => array('ViewHelper')
            ));
            $removeForm->addElement('button', 'btn_submit', array(
                'escape'        => false,
                'type'          => 'submit',
                'class'         => 'link-button spinner',
                'value'         => 'btn_submit',
                'decorators'    => array('ViewHelper'),
                'label'         => $this->view->icon('trash'),
                'title'         => $this->translate('Cancel this membership')
            ));
            $this->view->removeForm = $removeForm;
        }

        $admissionLoader = new AdmissionLoader();
        $admissionLoader->applyRoles($userObj);
        $this->view->userObj = $userObj;
        $this->view->allowedToEditRoles = $this->hasPermission('config/access-control/groups');
    }

    /**
     * Add a user
     */
    public function addAction()
    {
        $this->assertPermission('config/access-control/users');
        $backend = $this->getUserBackend($this->params->getRequired('backend'), 'Icinga\Data\Extensible');
        $form = new UserForm();
        $form->setRedirectUrl(Url::fromPath('user/list', array('backend' => $backend->getName())));
        $form->setRepository($backend);
        $form->add()->handleRequest();

        $this->renderForm($form, $this->translate('New User'));
    }

    /**
     * Edit a user
     */
    public function editAction()
    {
        $this->assertPermission('config/access-control/users');
        $userName = $this->params->getRequired('user');
        $backend = $this->getUserBackend($this->params->getRequired('backend'), 'Icinga\Data\Updatable');

        $form = new UserForm();
        $form->setRedirectUrl(Url::fromPath('user/show', array('backend' => $backend->getName(), 'user' => $userName)));
        $form->setRepository($backend);

        try {
            $form->edit($userName)->handleRequest();
        } catch (NotFoundError $_) {
            $this->httpNotFound(sprintf($this->translate('User "%s" not found'), $userName));
        }

        $this->renderForm($form, $this->translate('Update User'));
    }

    /**
     * Remove a user
     */
    public function removeAction()
    {
        $this->assertPermission('config/access-control/users');
        $userName = $this->params->getRequired('user');
        $backend = $this->getUserBackend($this->params->getRequired('backend'), 'Icinga\Data\Reducible');

        $form = new UserForm();
        $form->setRedirectUrl(Url::fromPath('user/list', array('backend' => $backend->getName())));
        $form->setRepository($backend);

        try {
            $form->remove($userName)->handleRequest();
        } catch (NotFoundError $_) {
            $this->httpNotFound(sprintf($this->translate('User "%s" not found'), $userName));
        }

        $this->renderForm($form, $this->translate('Remove User'));
    }

    /**
     * Create a membership for a user
     */
    public function createmembershipAction()
    {
        $this->assertPermission('config/access-control/groups');
        $userName = $this->params->getRequired('user');
        $backend = $this->getUserBackend($this->params->getRequired('backend'));

        if ($backend->select()->where('user_name', $userName)->count() === 0) {
            $this->httpNotFound(sprintf($this->translate('User "%s" not found'), $userName));
        }

        $backends = $this->loadUserGroupBackends('Icinga\Data\Extensible');
        if (empty($backends)) {
            throw new ConfigurationError($this->translate(
                'You\'ll need to configure at least one user group backend first that allows to create new memberships'
            ));
        }

        $form = new CreateMembershipForm();
        $form->setBackends($backends)
            ->setUsername($userName)
            ->setRedirectUrl(Url::fromPath('user/show', array('backend' => $backend->getName(), 'user' => $userName)))
            ->handleRequest();

        $this->renderForm($form, $this->translate('Create New Membership'));
    }

    /**
     * Fetch and return the given user's groups from all user group backends
     *
     * @param   User    $user
     *
     * @return  ArrayDatasource
     */
    protected function loadMemberships(User $user)
    {
        $groups = $alreadySeen = array();
        foreach ($this->loadUserGroupBackends() as $backend) {
            try {
                foreach ($backend->getMemberships($user) as $groupName) {
                    if (array_key_exists($groupName, $alreadySeen)) {
                        continue; // Ignore duplicate memberships
                    }

                    $alreadySeen[$groupName] = null;
                    $groups[] = (object) array(
                        'group_name'    => $groupName,
                        'group'         => $groupName,
                        'backend'       => $backend
                    );
                }
            } catch (Exception $e) {
                Logger::error($e);
                Notification::warning(sprintf(
                    $this->translate('Failed to fetch memberships from backend %s. Please check your log'),
                    $backend->getName()
                ));
            }
        }

        return new ArrayDatasource($groups);
    }

    /**
     * Create the tabs to display when showing a user
     *
     * @param   string  $backendName
     * @param   string  $userName
     */
    protected function createShowTabs($backendName, $userName)
    {
        $tabs = $this->getTabs();
        $tabs->add(
            'user/show',
            array(
                'title'     => sprintf($this->translate('Show user %s'), $userName),
                'label'     => $this->translate('User'),
                'url'       => Url::fromPath('user/show', array('backend' => $backendName, 'user' => $userName))
            )
        );

        return $tabs;
    }

    /**
     * Create the tabs to display when listing users
     */
    protected function createListTabs()
    {
        $tabs = $this->getTabs();

        if ($this->hasPermission('config/access-control/roles')) {
            $tabs->add(
                'role/list',
                array(
                    'baseTarget'    => '_main',
                    'label'         => $this->translate('Roles'),
                    'title'         => $this->translate(
                        'Configure roles to permit or restrict users and groups accessing Icinga Web 2'
                    ),
                    'url'           => 'role/list'
                )
            );

            $tabs->add(
                'role/audit',
                [
                    'title'         => $this->translate('Audit a user\'s or group\'s privileges'),
                    'label'         => $this->translate('Audit'),
                    'url'           => 'role/audit',
                    'baseTarget'    => '_main'
                ]
            );
        }

        $tabs->add(
            'user/list',
            array(
                'title'     => $this->translate('List users of authentication backends'),
                'label'     => $this->translate('Users'),
                'url'       => 'user/list'
            )
        );

        if ($this->hasPermission('config/access-control/groups')) {
            $tabs->add(
                'group/list',
                array(
                    'title'     => $this->translate('List groups of user group backends'),
                    'label'     => $this->translate('User Groups'),
                    'url'       => 'group/list'
                )
            );
        }

        return $tabs;
    }
}
