<?php
/**
 * @package Dotclear
 * @subpackage Backend
 *
 * @copyright Olivier Meunier & Association Dotclear
 * @copyright GPL-2.0-only
 */
if (!defined('DC_RC_PATH')) {
    return;
}

/**
 * dcFavorites -- Favorites handling facilities
 */
class dcFavorites
{
    /** @var dcCore dotclear core instance */
    protected $core;

    /** @var ArrayObject list of favorite definitions  */
    protected $fav_defs;

    /** @var dcWorkspace current favorite landing workspace */
    protected $ws;

    /** @var array list of user-defined favorite ids */
    protected $local_prefs;

    /** @var array list of globally-defined favorite ids */
    protected $global_prefs;

    /** @var array list of user preferences (either one of the 2 above, or not!) */
    protected $user_prefs;

    /**
     * Class constructor
     *
     * @param dcCore   $core   dotclear core
     *
     * @access public
     */
    public function __construct($core)
    {
        $this->core       = $core;
        $this->fav_defs   = new ArrayObject();
        $this->ws         = $core->auth->user_prefs->addWorkspace('dashboard');
        $this->user_prefs = [];

        if ($this->ws->prefExists('favorites')) {
            $this->local_prefs  = $this->ws->getLocal('favorites');
            $this->global_prefs = $this->ws->getGlobal('favorites');
            // Since we never know what user puts through user:preferences ...
            if (!is_array($this->local_prefs)) {
                $this->local_prefs = [];
            }
            if (!is_array($this->global_prefs)) {
                $this->global_prefs = [];
            }
        } else {
            // No favorite defined ? Huhu, let's go for a migration
            $this->migrateFavorites();
        }
    }

    /**
     * setup - sets up favorites, fetch user favorites (against his permissions)
     *            This method is to be called after loading plugins
     *
     * @access public
     */
    public function setup()
    {
        defaultFavorites::initDefaultFavorites($this->core, $this);
        $this->legacyFavorites();
        $this->core->callBehavior('adminDashboardFavorites', $this->core, $this);
        $this->setUserPrefs();
    }

    /**
     * getFavorite - retrieves a favorite (complete description) from its id.
     *
     * @param mixed  $p   the favorite id, or an array having 1 key 'name' set to id, ther keys are merged to favorite.
     *
     * @access public
     *
     * @return mixed    array the favorite, false if not found (or not permitted)
     */
    public function getFavorite($p)
    {
        if (is_array($p)) {
            $fname = $p['name'];
            if (!isset($this->fav_defs[$fname])) {
                return false;
            }
            $fattr = $p;
            unset($fattr['name']);
            $fattr = array_merge($this->fav_defs[$fname], $fattr);
        } else {
            if (!isset($this->fav_defs[$p])) {
                return false;
            }
            $fattr = $this->fav_defs[$p];
        }
        $fattr = array_merge(['id' => null, 'class' => null], $fattr);
        if (isset($fattr['permissions'])) {
            if (is_bool($fattr['permissions']) && !$fattr['permissions']) {
                return false;
            }
            if (!$this->core->auth->check($fattr['permissions'], $this->core->blog->id)) {
                return false;
            }
        } elseif (!$this->core->auth->isSuperAdmin()) {
            return false;
        }

        return $fattr;
    }

    /**
     * getFavorites - retrieves a list of favorites.
     *
     * @param array  $ids   an array of ids, as defined in getFavorite.
     *
     * @access public
     *
     * @return array array of favorites, can be empty if ids are not found (or not permitted)
     */
    public function getFavorites($ids)
    {
        $prefs = [];
        foreach ($ids as $id) {
            $f = $this->getFavorite($id);
            if ($f !== false) {
                $prefs[$id] = $f;
            }
        }

        return $prefs;
    }

    /**
     * setUserPrefs - get user favorites from settings. These are complete favorites, not ids only
     *                 returned favorites are the first non-empty list from :
     *                 * user-defined favorites
     *                 * globally-defined favorites
     *                 * a failback list "new post" (shall never be empty)
     *                This method is called by ::setup()
     *
     * @access protected
     */
    protected function setUserPrefs()
    {
        $this->user_prefs = $this->getFavorites($this->local_prefs);
        if (!count($this->user_prefs)) {
            $this->user_prefs = $this->getFavorites($this->global_prefs);
        }
        if (!count($this->user_prefs)) {
            $this->user_prefs = $this->getFavorites(['new_post']);
        }
        $uri = explode('?', $_SERVER['REQUEST_URI']);
        // take only last part of the URI, all plugins work like that
        $uri[0] = preg_replace('#(.*?)([^/]+)$#', '$2', $uri[0]);
        // Loop over prefs to enable active favorites
        foreach ($this->user_prefs as $k => &$v) {
            // duplicate request URI on each loop as it takes previous pref value ?!
            $u = $uri;
            if (isset($v['active_cb']) && is_callable($v['active_cb'])) {
                // Use callback if defined to match whether favorite is active or not
                $v['active'] = call_user_func($v['active_cb'], $u[0], $_REQUEST);
            } else {
                // Failback active detection. We test against URI name & parameters
                $v['active'] = true; // true until something proves it is false
                $u           = explode('?', $v['url'], 2);
                if (!preg_match('/' . preg_quote($u[0], '/') . '/', $_SERVER['REQUEST_URI'])) {
                    $v['active'] = false; // no URI match
                }
                if (count($u) == 2) {
                    parse_str($u[1], $p);
                    // test against each request parameter.
                    foreach ($p as $k2 => $v2) {
                        if (!isset($_REQUEST[$k2]) || $_REQUEST[$k2] !== $v2) {
                            $v['active'] = false;
                        }
                    }
                }
            }
        }
    }

    /**
     * migrateFavorites - migrate dc < 2.6 favorites to new format
     *
     * @access protected
     */
    protected function migrateFavorites()
    {
        $fav_ws             = $this->core->auth->user_prefs->addWorkspace('favorites');
        $this->local_prefs  = [];
        $this->global_prefs = [];
        foreach ($fav_ws->dumpPrefs() as $k => $v) {
            $fav = @unserialize($v['value']);
            if (is_array($fav)) {
                if ($v['global']) {
                    $this->global_prefs[] = $fav['name'];
                } else {
                    $this->local_prefs[] = $fav['name'];
                }
            }
        }
        $this->ws->put('favorites', $this->global_prefs, 'array', 'User favorites', true, true);
        $this->ws->put('favorites', $this->local_prefs);
        $this->user_prefs = $this->getFavorites($this->local_prefs);
    }

    /**
     * legacyFavorites - handle legacy favorites using adminDashboardFavs behavior
     *
     * @access protected
     */
    protected function legacyFavorites()
    {
        $f = new ArrayObject();
        $this->core->callBehavior('adminDashboardFavs', $this->core, $f);
        foreach ($f as $k => $v) {
            $fav = [
                'title'       => __($v[1]),
                'url'         => $v[2],
                'small-icon'  => $v[3],
                'large-icon'  => $v[4],
                'permissions' => $v[5],
                'id'          => $v[6],
                'class'       => $v[7],
            ];
            $this->register($v[0], $fav);
        }
    }

    /**
     * getUserFavorites - returns favorites that correspond to current user
     *   (may be local, global, or failback favorites)
     *
     * @access public
     *
     * @return array array of favorites (enriched)
     */
    public function getUserFavorites()
    {
        return $this->user_prefs;
    }

    /**
     * getFavoriteIDs - returns user-defined or global favorites ids list
     *                    shall not be called outside preferences.php...
     *
     * @param boolean  $global   if true, retrieve global favs, user favs otherwise
     *
     * @access public
     *
     * @return array array of favorites ids (only ids, not enriched)
     */
    public function getFavoriteIDs($global = false)
    {
        return $global ? $this->global_prefs : $this->local_prefs;
    }

    /**
     * setFavoriteIDs - stores user-defined or global favorites ids list
     *                    shall not be called outside preferences.php...
     *
     * @param array  $ids   list of fav ids
     * @param boolean  $global   if true, retrieve global favs, user favs otherwise
     *
     * @access public
     */
    public function setFavoriteIDs($ids, $global = false)
    {
        $this->ws->put('favorites', $ids, 'array', null, true, $global);
    }

    /**
     * getAvailableFavoritesIDs - returns all available fav ids
     *
     * @access public
     *
     * @return array array of favorites ids (only ids, not enriched)
     */
    public function getAvailableFavoritesIDs()
    {
        return array_keys($this->fav_defs->getArrayCopy()); // @phpstan-ignore-line
    }

    /**
     * appendMenuTitle - adds favorites section title to sidebar menu
     *                    shall not be called outside admin/prepend.php...
     *
     * @param array|ArrayObject  $menu   admin menu
     *
     * @access public
     */
    public function appendMenuTitle($menu)
    {
        $menu['Favorites']        = new dcMenu('favorites-menu', 'My favorites');
        $menu['Favorites']->title = __('My favorites');
    }

    /**
     * appendMenu - adds favorites items title to sidebar menu
     *                    shall not be called outside admin/prepend.php...
     *
     * @param array|ArrayObject  $menu   admin menu
     *
     * @access public
     */
    public function appendMenu($menu)
    {
        foreach ($this->user_prefs as $k => $v) {
            $menu['Favorites']->addItem(
                $v['title'],
                $v['url'],
                $v['small-icon'],
                $v['active'],
                true,
                $v['id'],
                $v['class'],
                true
            );
        }
    }

    /**
     * appendDashboardIcons - adds favorites icons to index page
     *                    shall not be called outside admin/index.php...
     *
     * @param array  $icons   dashboard icon list to enrich
     *
     * @access public
     */
    public function appendDashboardIcons($icons)
    {
        foreach ($this->user_prefs as $k => $v) {
            if (isset($v['dashboard_cb']) && is_callable($v['dashboard_cb'])) {
                $v = new ArrayObject($v);
                call_user_func($v['dashboard_cb'], $this->core, $v);
            }
            $icons[$k] = new ArrayObject([$v['title'], $v['url'], $v['large-icon']]);
            $this->core->callBehavior('adminDashboardFavsIcon', $this->core, $k, $icons[$k]);
        }
    }

    /**
     * register - registers a new favorite definition
     *
     * @param string  $id   favorite id
     * @param array  $data favorite information. Array keys are :
     *    'title' => favorite title (localized)
     *    'url' => favorite URL,
     *    'small-icon' => favorite small icon(s) (for menu)
     *    'large-icon' => favorite large icon(s) (for dashboard)
     *    'permissions' => (optional) comma-separated list of permissions for thie fav, if not set : no restriction
     *    'dashboard_cb' => (optional) callback to modify title if dynamic, if not set : title is taken as is
     *    'active_cb' => (optional) callback to tell whether current page matches favorite or not, for complex pages
     *
     * @access public
     *
     * @return dcFavorites instance
     */
    public function register($id, $data)
    {
        $this->fav_defs[$id] = $data;

        return $this;
    }

    /**
     * registerMultiple - registers a list of favorites definition
     *
     * @param array $data an array defining all favorites key is the id, value is the data.
     *                see register method for data format
     * @access public
     *
     * @return dcFavorites instance
     */
    public function registerMultiple($data)
    {
        foreach ($data as $k => $v) {
            $this->register($k, $v);
        }

        return $this;
    }

    /**
     * exists - tells whether a fav definition exists or not
     *
     * @param string $id : the fav id to test
     *
     * @access public
     *
     * @return bool true if the fav definition exists, false otherwise
     */
    public function exists($id)
    {
        return isset($this->fav_defs[$id]);
    }
}

/**
 * defaultFavorites -- default favorites definition
 */
class defaultFavorites
{
    /**
     * Initializes the default favorites.
     *
     * @param      dcFavorites  $favs   The favs
     */
    public static function initDefaultFavorites($core, $favs)
    {
        $favs->registerMultiple([
            'prefs' => [
                'title'      => __('My preferences'),
                'url'        => $core->adminurl->get('admin.user.preferences'),
                'small-icon' => 'images/menu/user-pref.svg',
                'large-icon' => 'images/menu/user-pref.svg', ],
            'new_post' => [
                'title'       => __('New post'),
                'url'         => $core->adminurl->get('admin.post'),
                'small-icon'  => ['images/menu/edit.svg', 'images/menu/edit-dark.svg'],
                'large-icon'  => ['images/menu/edit.svg', 'images/menu/edit-dark.svg'],
                'permissions' => 'usage,contentadmin',
                'active_cb'   => ['defaultFavorites', 'newpostActive'], ],
            'posts' => [
                'title'        => __('Posts'),
                'url'          => $core->adminurl->get('admin.posts'),
                'small-icon'   => ['images/menu/entries.svg', 'images/menu/entries-dark.svg'],
                'large-icon'   => ['images/menu/entries.svg', 'images/menu/entries-dark.svg'],
                'permissions'  => 'usage,contentadmin',
                'dashboard_cb' => ['defaultFavorites', 'postsDashboard'], ],
            'comments' => [
                'title'        => __('Comments'),
                'url'          => $core->adminurl->get('admin.comments'),
                'small-icon'   => ['images/menu/comments.svg', 'images/menu/comments-dark.svg'],
                'large-icon'   => ['images/menu/comments.svg', 'images/menu/comments-dark.svg'],
                'permissions'  => 'usage,contentadmin',
                'dashboard_cb' => ['defaultFavorites', 'commentsDashboard'], ],
            'search' => [
                'title'       => __('Search'),
                'url'         => $core->adminurl->get('admin.search'),
                'small-icon'  => ['images/menu/search.svg','images/menu/search-dark.svg'],
                'large-icon'  => ['images/menu/search.svg','images/menu/search-dark.svg'],
                'permissions' => 'usage,contentadmin', ],
            'categories' => [
                'title'       => __('Categories'),
                'url'         => $core->adminurl->get('admin.categories'),
                'small-icon'  => ['images/menu/categories.svg', 'images/menu/categories-dark.svg'],
                'large-icon'  => ['images/menu/categories.svg', 'images/menu/categories-dark.svg'],
                'permissions' => 'categories', ],
            'media' => [
                'title'       => __('Media manager'),
                'url'         => $core->adminurl->get('admin.media'),
                'small-icon'  => ['images/menu/media.svg', 'images/menu/media-dark.svg'],
                'large-icon'  => ['images/menu/media.svg', 'images/menu/media-dark.svg'],
                'permissions' => 'media,media_admin', ],
            'blog_pref' => [
                'title'       => __('Blog settings'),
                'url'         => $core->adminurl->get('admin.blog.pref'),
                'small-icon'  => ['images/menu/blog-pref.svg','images/menu/blog-pref-dark.svg'],
                'large-icon'  => ['images/menu/blog-pref.svg','images/menu/blog-pref-dark.svg'],
                'permissions' => 'admin', ],
            'blog_theme' => [
                'title'       => __('Blog appearance'),
                'url'         => $core->adminurl->get('admin.blog.theme'),
                'small-icon'  => ['images/menu/themes.svg', 'images/menu/themes-dark.svg'],
                'large-icon'  => ['images/menu/themes.svg', 'images/menu/themes-dark.svg'],
                'permissions' => 'admin', ],
            'blogs' => [
                'title'       => __('Blogs'),
                'url'         => $core->adminurl->get('admin.blogs'),
                'small-icon'  => ['images/menu/blogs.svg', 'images/menu/blogs-dark.svg'],
                'large-icon'  => ['images/menu/blogs.svg', 'images/menu/blogs-dark.svg'],
                'permissions' => 'usage,contentadmin', ],
            'users' => [
                'title'      => __('Users'),
                'url'        => $core->adminurl->get('admin.users'),
                'small-icon' => 'images/menu/users.svg',
                'large-icon' => 'images/menu/users.svg', ],
            'plugins' => [
                'title'      => __('Plugins management'),
                'url'        => $core->adminurl->get('admin.plugins'),
                'small-icon' => ['images/menu/plugins.svg', 'images/menu/plugins-dark.svg'],
                'large-icon' => ['images/menu/plugins.svg', 'images/menu/plugins-dark.svg'], ],
            'langs' => [
                'title'      => __('Languages'),
                'url'        => $core->adminurl->get('admin.langs'),
                'small-icon' => ['images/menu/langs.svg', 'images/menu/langs-dark.svg'],
                'large-icon' => ['images/menu/langs.svg', 'images/menu/langs-dark.svg'], ],
            'help' => [
                'title'      => __('Global help'),
                'url'        => $core->adminurl->get('admin.help'),
                'small-icon' => 'images/menu/help.svg',
                'large-icon' => 'images/menu/help.svg', ],
        ]);
    }

    /**
     * Helper for posts icon on dashboard
     *
     * @param      dcCore  $core   The core
     * @param      mixed   $v      { parameter_description }
     */
    public static function postsDashboard($core, $v)
    {
        $post_count  = $core->blog->getPosts([], true)->f(0);
        $str_entries = __('%d post', '%d posts', $post_count);
        $v['title']  = sprintf($str_entries, $post_count);
    }

    /**
     * Helper for new post active menu
     *
     * Take account of post edition (if id is set)
     *
     * @param  string   $request_uri    The URI
     * @param  array    $request_params The params
     * @return boolean                  Active
     */
    public static function newpostActive($request_uri, $request_params)
    {
        return 'post.php' == $request_uri && !isset($request_params['id']);
    }

    /**
     * Helper for comments icon on dashboard
     *
     * @param      dcCore  $core   The core
     * @param      mixed   $v      { parameter_description }
     */
    public static function commentsDashboard($core, $v)
    {
        $comment_count = $core->blog->getComments([], true)->f(0);
        $str_comments  = __('%d comment', '%d comments', $comment_count);
        $v['title']    = sprintf($str_comments, $comment_count);
    }
}
