<?php
/**
 * Copyright 2010-2017 Horde LLC (http://www.horde.org/)
 *
 * See the enclosed file COPYING for license information (LGPL-2). If you
 * did not receive this file, see http://www.horde.org/licenses/lgpl.
 *
 * @category  Horde
 * @copyright 2010-2017 Horde LLC
 * @license   http://www.horde.org/licenses/lgpl LGPL-2
 * @package   Horde
 */

/**
 * Horde external API interface.
 *
 * This file defines Horde's external API interface. Other
 * applications can interact with Horde through this API.
 *
 * @category  Horde
 * @copyright 2010-2017 Horde LLC
 * @license   http://www.horde.org/licenses/lgpl LGPL-2
 * @package   Horde
 */
class Horde_Api extends Horde_Registry_Api
{
    /**
     * Returns a list of adminstrative links.
     *
     * @return array  Keys are link labels, values are array with these keys:
     *   - icon: (string) Graphic for page.
     *   - link: (string) Registry encoded link to page.
     *   - name: (string) Gettext label for page.
     */
    public function admin_list()
    {
        $admin = array(
            'configuration' => array(
                'link' => '%application%/admin/config/',
                'name' => _("_Configuration"),
                'icon' => 'config'
            ),
            'users' => array(
                'link' => '%application%/admin/user.php',
                'name' => _("_Users"),
                'icon' => 'user'
            ),
            'groups' => array(
                'link' => '%application%/admin/groups.php',
                'name' => _("_Groups"),
                'icon' => 'group'
            ),
            'perms' => array(
                'link' => '%application%/admin/perms/',
                'name' => _("_Permissions"),
                'icon' => 'perms'
            ),
            'locks' => array(
                'link' => '%application%/admin/locks.php',
                'name' => _("_Locks"),
                'icon' => 'locked'
            ),
            'alarms' => array(
                'link' => '%application%/admin/alarms.php',
                'name' => _("_Alarms"),
                'icon' => 'alarm'
            ),
            'sessions' => array(
                'link' => '%application%/admin/sessions.php',
                'name' => _("Sessions"),
                'icon' => 'user'
            ),
            'phpshell' => array(
                'link' => '%application%/admin/phpshell.php',
                'name' => _("P_HP Shell"),
                'icon' => 'php'
            ),
            'sqlshell' => array(
                'link' => '%application%/admin/sqlshell.php',
                'name' => _("S_QL Shell"),
                'icon' => 'sql'
            ),
            'cmdshell' => array(
                'link' => '%application%/admin/cmdshell.php',
                'name' => _("_CLI"),
                'icon' => 'shell'
            )
        );

        if (!empty($GLOBALS['conf']['activesync']['enabled'])) {
            $admin['activesync'] = array(
                'link' => '%application%/admin/activesync.php',
                'name' => _("ActiveSync Devices"),
                'icon' => 'mobile'
            );
        }

        return $admin;
    }

    /**
     * Returns a list of the installed and registered applications.
     *
     * @param array $filter  An array of the statuses that should be returned.
     *                       Defaults to non-hidden.
     *
     * @return array  List of apps registered with Horde. If no applications
     *                are defined returns an empty array.
     */
    public function listApps($filter = null)
    {
        return $GLOBALS['registry']->listApps($filter);
    }

    /**
     * Returns all available registry APIs.
     *
     * @return array  The API list.
     */
    public function listAPIs()
    {
        return $GLOBALS['registry']->listAPIs();
    }

    /* Blocks. */

    /**
     * Returns a Horde_Block's title.
     *
     * @param string $app    The block application name.
     * @param string $name   The block name (NOT the class name).
     * @param array $params  Block parameters.
     *
     * @return string  The block title.
     */
    public function blockTitle($app, $name, $params = array())
    {
        $class = $app . '_Block_' . basename($name);
        try {
            return $GLOBALS['injector']->getInstance('Horde_Core_Factory_BlockCollection')->create()->getBlock($app, $class, $params)->getTitle();
        } catch (Horde_Exception $e) {
            return $e->getMessage();
        }
    }

    /**
     * Returns a Horde_Block's content.
     *
     * @param string $app    The block application name.
     * @param string $name   The block name (NOT the classname).
     * @param array $params  Block parameters.
     *
     * @return string  The block content.
     */
    public function blockContent($app, $name, $params = array())
    {
        $class = $app . '_Block_' . basename($name);
        try {
            return $GLOBALS['injector']->getInstance('Horde_Core_Factory_BlockCollection')->create()->getBlock($app, $class, $params)->getContent();
        } catch (Horde_Exception $e) {
            return $e->getMessage();
        }
    }

    /**
     * Returns a pretty printed list of all available blocks.
     *
     * @return array  A hash with block IDs as keys and application plus block
     *                block names as values.
     */
    public function blocks()
    {
        return $GLOBALS['injector']->getInstance('Horde_Core_Factory_BlockCollection')->create()->getBlocksList();
    }

    /* User data. */

    /**
     * Returns the value of the requested preference.
     *
     * @param string $app   The application of the preference to retrieve.
     * @param string $pref  The name of the preference to retrieve.
     *
     * @return string  The value of the preference, null if it doesn't exist.
     */
    public function getPreference($app, $pref)
    {
        $pushed = $GLOBALS['registry']->pushApp($app);
        $GLOBALS['registry']->loadPrefs($app);
        $value = $GLOBALS['prefs']->getValue($pref);
        if ($pushed) {
            $GLOBALS['registry']->popApp();
        }

        return $value;
    }

    /**
     * Sets a preference to the specified value, if the preference is allowed
     * to be modified.
     *
     * @param string $app   The application of the preference to modify.
     * @param string $pref  The name of the preference to modify.
     * @param string $val   The new value for this preference.
     */
    public function setPreference($app, $pref, $value)
    {
        $pushed = $GLOBALS['registry']->pushApp($app);
        $GLOBALS['registry']->loadPrefs($app);
        $value = $GLOBALS['prefs']->setValue($pref, $value);
        if ($pushed) {
            $GLOBALS['registry']->popApp();
        }
    }

    /**
     * Removes user data.
     *
     * @param string $user  Name of user to remove data for.
     * @param string $app   Remove data from this application. If boolean
     *                      true, removes all applications. If boolean false,
     *                      removes only base Horde data.
     *
     * @throws Horde_Exception
     */
    public function removeUserData($user, $app = false)
    {
        if ($app === true) {
            $app = null;
        } elseif ($app === false || !strlen($app)) {
            $app = false;
        }

        $GLOBALS['registry']->removeUserData($user, $app);
    }

    /* Groups. */

    /**
     * Adds a group to the groups system.
     *
     * @param string $name  The group's name.
     *
     * @return mixed  The group's ID.
     * @throws Horde_Exception
     */
    public function addGroup($name)
    {
        if (!$GLOBALS['registry']->isAdmin()) {
            throw new Horde_Exception(_("You are not allowed to add groups."));
        }

        return $GLOBALS['injector']
            ->getInstance('Horde_Group')
            ->create($name);
    }

    /**
     * Removes a group from the groups system.
     *
     * @param mixed $group  The group ID.
     *
     * @throws Horde_Exception
     */
    public function removeGroup($group)
    {
        if (!$GLOBALS['registry']->isAdmin()) {
            throw new Horde_Exception(_("You are not allowed to delete groups."));
        }

        $GLOBALS['injector']->getInstance('Horde_Group')->remove($group);
    }

    /**
     * Adds a user to a group.
     *
     * @param mixed $group  The group ID.
     * @param string $user  The user to add.
     *
     * @throws Horde_Exception
     */
    public function addUserToGroup($group, $user)
    {
        if (!$GLOBALS['registry']->isAdmin()) {
            throw new Horde_Exception(_("You are not allowed to change groups."));
        }

        $GLOBALS['injector']
            ->getInstance('Horde_Group')
            ->addUser($group, $user);
    }

    /**
     * Removes a user from a group.
     *
     * @param mixed $group  The group ID.
     * @param string $user  The user to add.
     *
     * @throws Horde_Exception
     */
    public function removeUserFromGroup($group, $user)
    {
        if (!$GLOBALS['registry']->isAdmin()) {
            throw new Horde_Exception(_("You are not allowed to change groups."));
        }

        $GLOBALS['injector']
            ->getInstance('Horde_Group')
            ->removeUser($group, $user);
    }

    /**
     * Returns a list of users that are part of this group (and only this
     * group).
     *
     * @param mixed $group  The group ID.
     *
     * @return array  The user list.
     * @throws Horde_Exception
     */
    public function listUsersOfGroup($group)
    {
        if (!$GLOBALS['registry']->isAdmin()) {
            throw new Horde_Exception(_("You are not allowed to list users of groups."));
        }

        return $GLOBALS['injector']
            ->getInstance('Horde_Group')
            ->listUsers($group);
    }

    /* Shares. */

    /**
     * Adds a share to the shares system.
     *
     * @param string $scope       The name of the share root, e.g. the
     *                            application that the share belongs to.
     * @param string $shareName   The share's name.
     * @param string $shareTitle  The share's human readable title.
     * @param string $userName    The share's owner.
     *
     * @throws Horde_Exception
     */
    public function addShare($scope, $shareName, $shareTitle, $userName)
    {
        if (!$GLOBALS['registry']->isAdmin()) {
            throw new Horde_Exception(_("You are not allowed to add shares."));
        }

        $shares = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Share')->create($scope);
        $share = $shares->newShare($GLOBALS['registry']->getAuth(), $shareName, $shareTitle);
        $share->set('owner', $userName);
        $shares->addShare($share);
    }

    /**
     * Removes a share from the shares system permanently.
     *
     * @param string $scope      The name of the share root, e.g. the
     *                           application that the share belongs to.
     * @param string $shareName  The share's name.
     *
     * @throws Horde_Exception
     */
    public function removeShare($scope, $shareName)
    {
        if (!$GLOBALS['registry']->isAdmin()) {
            throw new Horde_Exception(_("You are not allowed to delete shares."));
        }

        $shares = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Share')->create($scope);
        $share = $shares->getShare($shareName);
        $shares->removeShare($share);
    }

    /**
     * Returns an array of all shares that $userName is the owner of.
     *
     * @param string $scope      The name of the share root, e.g. the
     *                           application that the share belongs to.
     * @param string $userName   The share's owner.
     *
     * @return array  The list of shares.
     * @throws Horde_Exception
     */
    public function listSharesOfOwner($scope, $userName)
    {
        if (!$GLOBALS['registry']->isAdmin()) {
            throw new Horde_Exception(_("You are not allowed to list shares."));
        }

        $shares = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Share')->create($scope);

        $share_list = $shares->listShares($userName,
                                          array('perm' => Horde_Perms::SHOW,
                                                'attributes' => $userName));
        $myshares = array();
        foreach ($share_list as $share) {
            $myshares[] = $share->getName();
        }

        return $myshares;
    }

    /**
     * Gives a user certain privileges for a share.
     *
     * @param string $scope       The name of the share root, e.g. the
     *                            application that the share belongs to.
     * @param string $shareName   The share's name.
     * @param string $userName    The user's name.
     * @param array $permissions  A list of permissions (show, read, edit,
     *                            delete).
     *
     * @throws Horde_Exception
     */
    public function addUserPermissions($scope, $shareName, $userName,
                                       $permissions)
    {
        if (!$GLOBALS['registry']->isAdmin()) {
            throw new Horde_Exception(_("You are not allowed to change shares."));
        }

        $share = $GLOBALS['injector']
            ->getInstance('Horde_Core_Factory_Share')
            ->create($scope)
            ->getShare($shareName);
        $perm = $share->getPermission();
        foreach ($permissions as $permission) {
            $permission = Horde_String::upper($permission);
            if (defined('Horde_Perms::' . $permission)) {
                $perm->addUserPermission($userName, constant('Horde_Perms::' . $permission), false);
            }
        }
        $share->setPermission($perm);
    }

    /**
     * Gives a group certain privileges for a share.
     *
     * @param string $scope       The name of the share root, e.g. the
     *                            application that the share belongs to.
     * @param string $shareName   The share's name.
     * @param mixed $groupId      The group ID.
     * @param array $permissions  A list of permissions (show, read, edit,
     *                            delete).
     *
     * @throws Horde_Exception
     */
    public function addGroupPermissions($scope, $shareName, $groupId,
                                        $permissions)
    {
        if (!$GLOBALS['registry']->isAdmin()) {
            throw new Horde_Exception(_("You are not allowed to change shares."));
        }

        $share = $GLOBALS['injector']
            ->getInstance('Horde_Core_Factory_Share')
            ->create($scope)
            ->getShare($shareName);
        $perm = $share->getPermission();
        foreach ($permissions as $permission) {
            $permission = Horde_String::upper($permission);
            if (defined('Horde_Perms::' . $permission)) {
                $perm->addGroupPermission($groupId, constant('Horde_Perms::' . $permission), false);
            }
        }
        $share->setPermission($perm);
    }

    /**
     * Removes a user from a share.
     *
     * @param string $scope       The name of the share root, e.g. the
     *                            application that the share belongs to.
     * @param string $shareName   The share's name.
     * @param string $userName    The user's name.
     *
     * @throws Horde_Exception
     */
    public function removeUserPermissions($scope, $shareName, $userName)
    {
        if (!$GLOBALS['registry']->isAdmin()) {
            throw new Horde_Exception(_("You are not allowed to change shares."));
        }

        $shares = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Share')->create($scope);
        $share = $shares->getShare($shareName);
        $share->removeUser($userName);
    }

    /**
     * Removes a group from a share.
     *
     * @param string $scope      The name of the share root, e.g. the
     *                           application that the share belongs to.
     * @param string $shareName  The share's name.
     * @param mixed $groupId     The group ID.
     *
     * @throws Horde_Exception
     */
    public function removeGroupPermissions($scope, $shareName, $groupId)
    {
        if (!$GLOBALS['registry']->isAdmin()) {
            throw new Horde_Exception(_("You are not allowed to change shares."));
        }

        $shares = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Share')->create($scope);
        $share = $shares->getShare($shareName);
        $share->removeGroup($groupId);
    }

    /**
     * Returns an array of all user permissions on a share.
     *
     * @param string $scope      The name of the share root, e.g. the
     *                           application that the share belongs to.
     * @param string $shareName  The share's name.
     * @param string $userName   The user's name.
     *
     * @return array  All user permissions for this share.
     * @throws Horde_Exception
     */
    public function listUserPermissions($scope, $shareName, $userName)
    {
        if (!$GLOBALS['registry']->isAdmin()) {
            throw new Horde_Exception(_("You are not allowed to list share permissions."));
        }

        $perm_map = array(Horde_Perms::SHOW => 'show',
            Horde_Perms::READ => 'read',
            Horde_Perms::EDIT => 'edit',
            Horde_Perms::DELETE => 'delete');

        $shares = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Share')->create($scope);
        $share = $shares->getShare($shareName);
        $perm = $share->getPermission();
        $permissions = $perm->getUserPermissions();
        if (empty($permissions[$userName])) {
            return array();
        }

        $user_permissions = array();
        foreach (array_keys(Horde_Perms::integerToArray($permissions[$userName])) as $permission) {
            $user_permissions[] = $perm_map[$permission];
        }

        return $user_permissions;
    }

    /**
     * Returns an array of all group permissions on a share.
     *
     * @param string $scope       The name of the share root, e.g. the
     *                            application that the share belongs to.
     * @param string $shareName   The share's name.
     * @param string $groupName   The group's name.
     *
     * @return array  All group permissions for this share.
     * @throws Horde_Exception
     */
    public function listGroupPermissions($scope, $shareName, $groupName)
    {
        if (!$GLOBALS['registry']->isAdmin()) {
            throw new Horde_Exception(_("You are not allowed to list share permissions."));
        }

        $perm_map = array(Horde_Perms::SHOW => 'show',
            Horde_Perms::READ => 'read',
            Horde_Perms::EDIT => 'edit',
            Horde_Perms::DELETE => 'delete');

        $shares = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Share')->create($scope);
        $share = $shares->getShare($shareName);
        $perm = $share->getPermission();
        $permissions = $perm->getGroupPermissions();
        if (empty($permissions[$groupName])) {
            return array();
        }

        $group_permissions = array();
        foreach (array_keys(Horde_Perms::integerToArray($permissions[$groupName])) as $permission) {
            $group_permissions[] = $perm_map[$permission];
        }

        return $group_permissions;
    }

    /**
     * Returns a list of users which have have certain permissions on a share.
     *
     * @param string $scope       The name of the share root, e.g. the
     *                            application that the share belongs to.
     * @param string $shareName   The share's name.
     * @param array $permissions  A list of permissions (show, read, edit,
     *                            delete).
     *
     * @return array  List of users with the specified permissions.
     * @throws Horde_Exception
     */
    public function listUsersOfShare($scope, $shareName, $permissions)
    {
        if (!$GLOBALS['registry']->isAdmin()) {
            throw new Horde_Exception(_("You are not allowed to list users of shares."));
        }

        $shares = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Share')->create($scope);
        $share = $shares->getShare($shareName);
        $perm = 0;
        foreach ($permissions as $permission) {
            $permission = Horde_String::upper($permission);
            if (defined('Horde_Perms::' . $permission)) {
                $perm &= constant('Horde_Perms::' . $permission);
            }
        }

        return $share->listUsers($perm);
    }

    /**
     * Returns a list of groups which have have certain permissions on a
     * share.
     *
     * @param string $scope       The name of the share root, e.g. the
     *                            application that the share belongs to.
     * @param string $shareName   The share's name.
     * @param array $permissions  A list of permissions (show, read, edit,
     *                            delete).
     *
     * @return array  List of groups with the specified permissions.
     * @throws Horde_Exception
     */
    public function listGroupsOfShare($scope, $shareName, $permissions)
    {
        if (!$GLOBALS['registry']->isAdmin()) {
            throw new Horde_Exception(_("You are not allowed to list groups of shares."));
        }

        $shares = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Share')->create($scope);
        $share = $shares->getShare($shareName);
        $perm = 0;
        foreach ($permissions as $permission) {
            $permission = Horde_String::upper($permission);
            if (defined('Horde_Perms::' . $permission)) {
                $perm &= constant('Horde_Perms::' . $permission);
            }
        }

        return $share->listGroups($perm);
    }

    /**
     * Returns a list of ActiveSync devices that are partnered with Horde.
     *
     * @param array $filter  An array of parameters to filter on:
     *  - user: Only return devices owned by user. If not present will default
     *          to returning devices for current user only. If present, but
     *          empty and current user is admin, will return all devices.
     *          Otherwise, will return devices matching the parameter (must be
     *          admin to list users other than current).
     *
     * @return array  List of device properties.
     * @since 5.2.0
     */
    public function listActiveSyncDevices($filter = array())
    {
        global $registry, $injector, $conf;

        if (empty($conf['activesync']['enabled'])) {
            return array();
        }

        $state = $injector->getInstance('Horde_ActiveSyncState');
        $state->setLogger($injector->getInstance('Horde_Log_Logger'));
        if (!isset($filter['user'])) {
            $filter['user'] = $registry->getAuth();
        } elseif (empty($filter['user'])) {
            if (!$registry->isAdmin()) {
                throw new Horde_Exception_PermissionDenied();
            }
            $filter['user'] = null;
        } elseif ($filter['user'] != $registry->getAuth() && !$registry->isAdmin()) {
            throw new Horde_Exception_PermissionDenied();
        }
        try {
            $devices = $state->listDevices($filter['user']);
        } catch (Horde_ActiveSync_Exception $e) {
            throw new Horde_Exception($e);
        }
        foreach ($devices as &$device) {
            $device['device_properties'] = unserialize($device['device_properties']);
            $device['last_synctime'] = $state->getLastSyncTimestamp($device['device_id']);
        }

        return $devices;
    }

    /**
     * Perform an administrative action on a single paired ActiveSync device.
     *
     * @param string $action    The action to perform. One of:
     *                          WIPE, CANCEL_WIPE, REMOVE.
     * @param string $deviceid  The device's deviceid.
     * @param string $user      Restrict actions to only this user's
     *                          account on the device in the case where the
     *                          device may have multiple user accounts on this
     *                          server. If empty, all users' state information
     *                          will be removed. If a non-admin calls this
     *                          method, this will always be set to the current
     *                          horde username.
     * @return boolean
     * @throws Horde_Exception
     */
    public function performActiveSyncDeviceAction($action, $deviceid, $user = null)
    {
        global $injector, $conf, $registry;

        if (empty($conf['activesync']['enabled'])) {
            throw new Horde_Exception(_("ActiveSync not activated."));
        }
        if (!in_array($action, array('WIPE', 'CANCEL_WIPE', 'REMOVE'))) {
            throw new Horde_Exception(_("Unsupported action."));
        }

        $state = $injector->getInstance('Horde_ActiveSyncState');
        $state->setLogger($injector->getInstance('Horde_Log_Logger'));
        // If not an admin, ensure the device is attached to the $user.
        // Otherwise, any user could wipe any device if the deviceid is known.
        if (!$registry->isAdmin()) {
            $user = $registry->getAuth();
            if (!$state->deviceExists($deviceid, $user)) {
                throw new Horde_Exception_PermissionDenied();
            }
        }
        switch ($action) {
        case 'WIPE':
            try {
                $state->setDeviceRWStatus($deviceid, Horde_ActiveSync::RWSTATUS_PENDING);
            } catch (Horde_ActiveSync_Exception $e) {
                throw new Horde_Exception($e);
            }
            return true;
        case 'CANCEL_WIPE':
            try {
                $state->setDeviceRWStatus($deviceid, Horde_ActiveSync::RWSTATUS_OK);
            } catch (Horde_ActiveSync_Exception $e) {
                throw new Horde_Exception($e);
            }
            return true;
        case 'REMOVE':
            try {
                $state->removeState(array(
                    'devId' => $deviceid,
                    'user' => $user)
                );
            } catch (Horde_ActiveSync_Exception $e) {
                throw new Horde_Exception($e);
            }
            return true;
        }
    }

    /**
     * Perform an admistrative action on ALL paired ActiveSync devices.
     *
     * @param string $action  The action. Currently, only 'RESET' is supported.
     *  - 'RESET': Causes all state for all devices to be removed. I.e., sets
     *             the synckey for all devices to 0.
     *
     * @return boolean
     */
    public function performBulkActiveSyncDeviceAction($action)
    {
        global $injector, $conf, $registry;

        if (!$registry->isAdmin()) {
            throw new Horde_Exception_PermissionDenied();
        }
        if (empty($conf['activesync']['enabled'])) {
            throw new Horde_Exception(_("ActiveSync not activated."));
        }
        if (!in_array($action, array('RESET'))) {
            throw new Horde_Exception(_("Unsupported action."));
        }
        try {
            $injector->getInstance('Horde_ActiveSyncState')->resetAllPolicyKeys();
        } catch (Horde_ActiveSync_Exception $e) {
            throw new Horde_Exception($e);
        }
        return true;
    }

}
