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

/**
 * Advanced search. This search script only works with javascript enabled
 * browsers. All other browsers are limited to the basic search only.
 * Usable in both basic and dynamic views.
 *
 * @author    Chuck Hagenbuch <chuck@horde.org>
 * @author    Michael Slusarz <slusarz@horde.org>
 * @category  Horde
 * @copyright 1999-2017 Horde LLC
 * @license   http://www.horde.org/licenses/gpl GPL
 * @package   IMP
 */
class IMP_Basic_Search extends IMP_Basic_Base
{
    /**
     * URL Parameters:
     * <pre>
     *   - criteria_form: (string) JSON representation of the search query.
     *   - edit_query: (integer) If true, edit a search query (contained in
     *                 'mailbox' parameter).
     *   - edit_query_filter: (string) The name of the filter being edited.
     *   - edit_query_vfolder: (string) The name of the virtual folder being
     *                         edited.
     *   - mailbox: (string) Mailbox parameter.
     *   - mailbox_list: (array) A list of mailboxes to process (base64url
     *                   encoded) If empty, uses 'mailbox' parameter.
     *   - mboxes_form: (string) JSON representation of the list of mailboxes
     *                  for the query. Hash containing 2 keys: mbox &
     *                  subfolder. Both values are base64url encoded.
     *   - search_label: (string) The label to use when saving the search.
     *   - search_type: (string) The type of saved search ('filter',
     *                 'vfolder'). If empty, the search should not be saved.
     *   - subfolder: (boolean) If set, search mailbox will default to
     *                subfolder search.
     * </pre>
     */
    protected function _init()
    {
        global $injector, $notification, $page_output, $prefs, $registry;

        /* Define the criteria list. */
        $criteria = array(
            'from' => array(
                'label' => _("From"),
                'type' => 'header'
            ),
            'recip' => array(
                'label' => _("Recipients (To/Cc/Bcc)"),
                'type' => 'header'
            ),
            'to' => array(
                'label' => _("To"),
                'type' => 'header'
            ),
            'cc' => array(
                'label' => _("Cc"),
                'type' => 'header'
            ),
            'bcc' => array(
                'label' => _("Bcc"),
                'type' => 'header'
            ),
            'subject' => array(
                'label' => _("Subject"),
                'type' => 'header'
            ),
            'customhdr' => array(
                'label' => _("Custom Header"),
                'type' => 'customhdr'
            ),
            'body' => array(
                'label' => _("Body"),
                'type' => 'text'
            ),
            'text' => array(
                'label' => _("Entire Message"),
                'type' => 'text'
            ),
            'date_range' => array(
                'label' => _("Date"),
                'type' => 'date'
            ),
            'older' => array(
                'label' => _("Older Than"),
                'type' => 'within'
            ),
            'younger' => array(
                'label' => _("Younger Than"),
                'type' => 'within'
            ),
            // Displayed in KB, but stored internally in bytes
            'size_smaller' => array(
                'label' => _("Size (KB) <"),
                'type' => 'size'
            ),
            // Displayed in KB, but stored internally in bytes
            'size_larger' => array(
                'label' => _("Size (KB) >"),
                'type' => 'size'
            ),
        );

        $filters = array(
            'bulk' => array(
                'label' => _("Bulk Messages"),
                'type' => 'filter'
            ),
            'attach' => array(
                'label' => _("Contains Attachment(s)"),
                'type' => 'filter'
            ),
            'mailinglist' => array(
                'label' => _("Mailing List Messages"),
                'type' => 'filter'
            ),
            'personal' => array(
                'label' => _("Personal Messages"),
                'type' => 'filter'
            ),
        );

        /* Define some constants. */
        $constants = array(
            'within' => array(
                'd' => IMP_Search_Element_Within::INTERVAL_DAYS,
                'm' => IMP_Search_Element_Within::INTERVAL_MONTHS,
                'y' => IMP_Search_Element_Within::INTERVAL_YEARS
            )
        );

        if (isset($this->vars->mailbox_list)) {
            if (is_array($this->vars->mailbox_list)) {
                $default_mailbox = IMP_Mailbox::get('INBOX');
                $search_mailbox = IMP_Mailbox::formFrom($this->vars->mailbox_list);
            } else {
                $default_mailbox = IMP_Mailbox::formFrom($this->vars->mailbox_list);
                $search_mailbox = array($default_mailbox);
            }
        } elseif (isset($this->vars->mailbox)) {
            $default_mailbox = IMP_Mailbox::formFrom($this->vars->mailbox);
            $search_mailbox = array($default_mailbox);
        } else {
            $default_mailbox = IMP_Mailbox::get('INBOX');
            $search_mailbox = array($default_mailbox);
        }

        /* Check that searching is allowed. */
        if (!$injector->getInstance('IMP_Factory_Imap')->create()->access(IMP_Imap::ACCESS_SEARCH)) {
            $default_mailbox->url('mailbox')->redirect();
        }

        $imp_flags = $injector->getInstance('IMP_Flags');
        $imp_search = $injector->getInstance('IMP_Search');

        $dynamic_view = ($registry->getView() == Horde_Registry::VIEW_DYNAMIC);
        $js_vars = array();

        /* Generate the search query if 'criteria_form' is present in the form
         * data. */
        if ($this->vars->criteria_form) {
            $c_list = array();

            foreach (json_decode($this->vars->criteria_form) as $val) {
                switch ($val->t) {
                case 'from':
                case 'to':
                case 'cc':
                case 'bcc':
                case 'subject':
                    $c_list[] = new IMP_Search_Element_Header(
                        $val->v,
                        $val->t,
                        $val->n
                    );
                    break;

                case 'recip':
                    $c_list[] = new IMP_Search_Element_Recipient(
                        $val->v,
                        $val->n
                    );
                    break;

                case 'customhdr':
                    $c_list[] = new IMP_Search_Element_Header(
                        $val->v->s,
                        $val->v->h,
                        $val->n
                    );
                    break;

                case 'body':
                case 'text':
                    $c_list[] = new IMP_Search_Element_Text(
                        $val->v,
                        ($val->t == 'body'),
                        $val->n
                    );
                    break;

                case 'date_range':
                    $c_list[] = new IMP_Search_Element_Daterange(
                        $val->b ? new DateTime($val->b) : 0,
                        $val->e ? new DateTime($val->e) : 0,
                        $val->n
                    );
                    break;

                case 'older':
                case 'younger':
                    $c_list[] = new IMP_Search_Element_Within(
                        $val->v->v,
                        $constants['within'][$val->v->l],
                        ($val->t == 'older')
                    );
                    break;

                case 'size_smaller':
                case 'size_larger':
                    $c_list[] = new IMP_Search_Element_Size(
                        $val->v,
                        ($val->t == 'size_larger')
                    );
                    break;

                case 'or':
                    $c_list[] = new IMP_Search_Element_Or();
                    break;

                case 'bulk':
                    $c_list[] = new IMP_Search_Element_Bulk(
                        $val->n
                    );
                    break;

                case 'attach':
                    $c_list[] = new IMP_Search_Element_Attachment(
                        $val->n
                    );
                    break;

                case 'mailinglist':
                    $c_list[] = new IMP_Search_Element_Mailinglist(
                        $val->n
                    );
                    break;

                case 'personal':
                    $c_list[] = new IMP_Search_Element_Personal(
                        $val->n
                    );
                    break;

                case 'flag':
                    /* Flag search. */
                    $formdata = $imp_flags->parseFormId(rawurldecode($val->v));
                    $c_list[] = new IMP_Search_Element_Flag(
                        $formdata['flag'],
                        ($formdata['set'] && !$val->n)
                    );
                    break;
                }
            }

            $redirect_dynamic = true;
            $redirect_target = false;

            switch ($this->vars->search_type) {
            case 'filter':
                $q_ob = $imp_search->createQuery($c_list, array(
                    'id' => IMP_Mailbox::formFrom($this->vars->edit_query_filter),
                    'label' => $this->vars->search_label,
                    'type' => IMP_Search::CREATE_FILTER
                ));

                if ($this->vars->edit_query_filter) {
                    $notification->push(sprintf(_("Filter \"%s\" edited successfully."), $this->vars->search_label), 'horde.success');
                    $redirect_dynamic = false;
                    $redirect_target = 'prefs';
                } else {
                    $notification->push(sprintf(_("Filter \"%s\" created succesfully."), $this->vars->search_label), 'horde.success');
                }
                break;

            case 'vfolder':
                $form = json_decode($this->vars->mboxes_form);
                $q_ob = $imp_search->createQuery($c_list, array(
                    'id' => IMP_Mailbox::formFrom($this->vars->edit_query_vfolder),
                    'label' => $this->vars->search_label,
                    'mboxes' => IMP_Mailbox::formFrom($form->mbox),
                    'subfolders' => IMP_Mailbox::formFrom($form->subfolder),
                    'type' => IMP_Search::CREATE_VFOLDER
                ));

                if ($this->vars->edit_query_vfolder) {
                    $notification->push(sprintf(_("Virtual Folder \"%s\" edited successfully."), $this->vars->search_label), 'horde.success');
                    $redirect_target = 'prefs';
                } else {
                    $notification->push(sprintf(_("Virtual Folder \"%s\" created succesfully."), $this->vars->search_label), 'horde.success');
                    $redirect_target = 'mailbox';
                }
                break;

            default:
                $form = json_decode($this->vars->mboxes_form);
                $q_ob = $imp_search->createQuery($c_list, array(
                    'mboxes' => IMP_Mailbox::formFrom($form->mbox),
                    'subfolders' => IMP_Mailbox::formFrom($form->subfolder)
                ));
                $redirect_target = 'mailbox';
                break;
            }

            /* Redirect to the mailbox page. */
            if ($redirect_target) {
                if ($dynamic_view && $redirect_dynamic) {
                    echo '<html><head>' .
                        Horde::wrapInlineScript(array('window.parent.DimpBase.go("mbox", "' . $q_ob->mbox_ob->form_to . '")')) .
                        '</head></html>';
                    exit;
                }

                switch ($redirect_target) {
                case 'mailbox':
                    $q_ob->mbox_ob->url('mailbox')->redirect();
                    break;

                case 'prefs':
                    $registry->getServiceLink('prefs', 'imp')->add('group', 'searches')->redirect();
                    break;
                }
                exit;
            }
        }

        /* Prepare the search view. */
        $view = new Horde_View(array(
            'templatePath' => IMP_TEMPLATES . '/search'
        ));
        $view->addHelper('Text');
        $view->addHelper('Tag');
        $view->addHelper('FormTag');

        $view->action = self::url();

        /* Determine if we are editing a search query. */
        $q_ob = $default_mailbox->getSearchOb();
        if ($this->vars->edit_query && $default_mailbox->search) {
            if ($default_mailbox->vfolder) {
                if (!$default_mailbox->editvfolder) {
                    $notification->push(_("Built-in Virtual Folders cannot be edited."), 'horde.error');
                    $registry->getServiceLink('prefs', 'imp')->add('group', 'searches')->redirect();
                }
                $view->edit_query = true;
                $view->edit_query_vfolder = $default_mailbox->form_to;
            } elseif ($imp_search->isFilter($q_ob)) {
                if (!$imp_search->isFilter($q_ob, true)) {
                    $notification->push(_("Built-in Filters cannot be edited."), 'horde.error');
                    $registry->getServiceLink('prefs', 'imp')->add('group', 'searches')->redirect();
                }
                $view->edit_query = true;
                $view->edit_query_filter = $default_mailbox->form_to;
            }

            if ($view->edit_query) {
                $view->search_label = $q_ob->label;
                $js_vars['ImpSearch.prefsurl'] = strval($registry->getServiceLink('prefs', 'imp')->add('group', 'searches')->setRaw(true));
            }
        } else {
            /* Process list of recent searches. */
            $iterator = IMP_Search_IteratorFilter::create(
                IMP_Search_IteratorFilter::QUERY
            );
            $rs = array();

            foreach ($iterator as $val) {
                $rs[$val->formid] = array(
                    'c' => $val->criteria,
                    'f' => array(
                        'm' => IMP_Mailbox::formTo($val->all ? array(IMP_Search_Query::ALLSEARCH) : array_map('strval', $val->mbox_list)),
                        's' => IMP_Mailbox::formTo(array_map('strval', $val->subfolder_list))
                    ),
                    'l' => Horde_String::truncate($val->querytext)
                );
            }

            if (!empty($rs)) {
                $js_vars['ImpSearch.i_recent'] = $rs;
            }

            $s_mboxes = IMP_Mailbox::formTo($search_mailbox);
            $js_vars['ImpSearch.i_mboxes'] = array(
                'm' => $this->vars->subfolder ? array() : $s_mboxes,
                's' => $this->vars->subfolder ? $s_mboxes : array()
            );
        }

        if ($default_mailbox->search) {
            $js_vars['ImpSearch.i_criteria'] = $q_ob->criteria;
            $js_vars['ImpSearch.i_mboxes'] = array(
                'm' => IMP_Mailbox::formTo($q_ob->all ? array(IMP_Search_Query::ALLSEARCH) : $q_ob->mbox_list),
                's' => IMP_Mailbox::formTo($q_ob->subfolder_list)
            );
        }

        /* Create the criteria list. */
        $c_list = $types = array();
        foreach ($criteria as $key => $val) {
            $c_list[] = array(
                'l' => $val['label'],
                'v' => $key
            );
            $types[$key] = $val['type'];
        }
        $view->clist = $c_list;

        /* Create the filter list. These are all-or-nothing searches. */
        $f_list = array();
        foreach ($filters as $key => $val) {
            $f_list[] = array(
                'l' => $val['label'],
                'v' => $key
            );
            $types[$key] = 'filter';
        }
        $view->filterlist = $f_list;

        /* Create the flag list. */
        $flist = $imp_flags->getList(array(
            'imap' => true,
            'mailbox' => $default_mailbox
        ));
        $flag_set = array();

        foreach ($flist as $val) {
            $flag_set[] = array(
                'l' => $val->label,
                'v' => rawurlencode($val->form_set)
            );
            $types[rawurlencode($val->form_set)] = 'flag';
        }
        $view->flist = $flag_set;

        /* Generate master mailbox list. */
        $mbox_list = array();
        if (!$view->edit_query_filter) {
            $js_vars['ImpSearch.allsearch'] = IMP_Mailbox::formTo(IMP_Search_Query::ALLSEARCH);
            $ob = $injector->getInstance('IMP_Search_Ui')->getSearchMboxList();
            $mbox_list = $ob->mbox_list;
            $view->subscribe = $prefs->getValue('subscribe');
            $view->tree = $ob->tree->getTree();
        }

        /* Prepare the topbar. */
        if (!$dynamic_view) {
            $injector->getInstance('Horde_View_Topbar')->subinfo =
                $injector->getInstance('IMP_View_Subinfo')->render();
        }

        Horde_Core_Ui_JsCalendar::init();
        $page_output->addScriptFile('hordecore.js', 'horde');
        $page_output->addScriptFile('horde.js', 'horde');
        $page_output->addScriptFile('search.js');

        $page_output->addInlineJsVars(array_merge($js_vars, array(
            /* Javascript data for this page. */
            'ImpSearch.data' => array(
                'constants' => $constants,
                'dynamic_view' => $dynamic_view,
                'inbox' => IMP_Mailbox::get('INBOX')->form_to,
                'mbox_list' => $mbox_list,
                'months' => Horde_Core_Ui_JsCalendar::months(),
                'searchmbox' => $default_mailbox->form_to,
                'types' => $types
            ),
            /* Gettext strings for this page. */
            'ImpSearch.text' => array(
                'and' => _("and"),
                'customhdr' => _("Custom Header:"),
                'datereset' => _("Date Reset"),
                'dateselection' => _("Date Selection"),
                'flag' => _("Flag:"),
                'loading' => _("Loading..."),
                'need_criteria' => _("Please select at least one search criteria."),
                'need_date' => _("Need at least one date in the date range search."),
                'need_mbox' => _("Please select at least one mailbox to search."),
                'need_label' => _("Saved searches require a label."),
                'not_match' => _("Do NOT Match"),
                'or' => _("OR"),
                'search_all' => _("Search All Mailboxes"),
                'search_term' => _("Search Term:"),
                'subfolder_search' => _("Search all subfolders?"),
                'to' => _("to")
            )
        )), array('onload' => true));

        if ($dynamic_view) {
            if (!$this->vars->edit_query) {
                $view->return_mailbox_val = sprintf(_("Return to %s"), $default_mailbox->display_html);
            }
            $page_output->topbar = $page_output->sidebar = false;
            $this->header_params = array(
                'html_id' => 'htmlAllowScroll'
            );
        }

        $this->title = _("Search");
        $this->output = $view->render('search');
    }

    /**
     */
    static public function url(array $opts = array())
    {
        return Horde::url('basic.php')
            ->add('page', 'search')
            ->setRaw(!empty($opts['full']));
    }

}
