Files
server/usr/share/psa-horde/imp/lib/Search.php
2026-01-07 20:52:11 +01:00

582 lines
16 KiB
PHP

<?php
/**
* Copyright 2002-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 2002-2017 Horde LLC
* @license http://www.horde.org/licenses/gpl GPL
* @package IMP
*/
/**
* Object handling mailbox searching in IMP.
*
* @author Michael Slusarz <slusarz@horde.org>
* @category Horde
* @copyright 2002-2017 Horde LLC
* @license http://www.horde.org/licenses/gpl GPL
* @package IMP
*/
class IMP_Search implements ArrayAccess, IteratorAggregate, Serializable
{
/* The mailbox search prefix. */
const MBOX_PREFIX = "impsearch\0";
/* The special search mailbox names. */
const BASIC_SEARCH = 'impbsearch';
const DIMP_FILTERSEARCH = 'dimpfsearch';
const DIMP_QUICKSEARCH = 'dimpqsearch';
/* Query creation types. */
const CREATE_FILTER = 1;
const CREATE_QUERY = 2;
const CREATE_VFOLDER = 3;
/**
* Has the object data changed?
*
* @var boolean
*/
public $changed = false;
/**
* Search queries.
*
* Each subarray contains:
* Keys: mailbox IDs.
* Values: IMP_Search_Query objects.
*
* @var array
*/
protected $_search = array(
'filters' => array(),
'query' => array(),
'vfolders' => array()
);
/**
* Constructor.
*/
public function __construct()
{
$this->init();
}
/**
* Initialize session search data.
*/
public function init()
{
$this->_getFilters();
$this->_getVFolders();
}
/**
* Creates the IMAP search query in the IMP session.
*
* @param array $criteria The search criteria array.
* @param array $opts Additional options:
* - id: (string) Use as the mailbox ID.
* - label: (string) The label to use for the search results.
* - mboxes: (array) The list of mailboxes to directly search. If this
* contains the IMP_Search_Query::ALLSEARCH constant, all
* mailboxes will be searched.
* - subfolders: (array) The list of mailboxes to do subfolder searches
* on.
* - type: (integer) Query type.
*
* @return IMP_Search_Query Returns the query object.
* @throws InvalidArgumentException
*/
public function createQuery($criteria, array $opts = array())
{
global $injector;
$opts = array_merge(array(
'id' => null,
'label' => null,
'mboxes' => array(),
'subfolders' => array(),
'type' => self::CREATE_QUERY
), $opts);
/* Make sure mailbox names are not IMP_Mailbox objects. */
$opts['mboxes'] = array_map('strval', $opts['mboxes']);
$opts['subfolders'] = array_map('strval', $opts['subfolders']);
switch ($opts['type']) {
case self::CREATE_FILTER:
$cname = 'IMP_Search_Filter';
break;
case self::CREATE_QUERY:
$cname = 'IMP_Search_Query';
if (empty($opts['mboxes']) && empty($opts['subfolders'])) {
throw new InvalidArgumentException('Search query requires at least one mailbox.');
}
break;
case self::CREATE_VFOLDER:
$cname = 'IMP_Search_Vfolder';
if (empty($opts['mboxes']) && empty($opts['subfolders'])) {
throw new InvalidArgumentException('Search query requires at least one mailbox.');
}
break;
}
$ob = new $cname(array_filter(array(
'add' => $criteria,
'all' => in_array(IMP_Search_Query::ALLSEARCH, $opts['mboxes']),
'id' => $this->_strip($opts['id']),
'label' => $opts['label'],
'mboxes' => $opts['mboxes'],
'subfolders' => $opts['subfolders']
)));
switch ($opts['type']) {
case self::CREATE_FILTER:
/* This will overwrite previous value, if it exists. */
$this->_search['filters'][$ob->id] = $ob;
$this->setFilters($this->_search['filters']);
break;
case self::CREATE_QUERY:
$this->_search['query'][$ob->id] = $ob;
$ob->mbox_ob->list_ob->rebuild(true);
break;
case self::CREATE_VFOLDER:
/* This will overwrite previous value, if it exists. */
$this->_search['vfolders'][$ob->id] = $ob;
$this->setVFolders($this->_search['vfolders']);
$injector->getInstance('IMP_Mailbox_SessionCache')->expire(
array(
IMP_Mailbox_SessionCache::CACHE_DISPLAY,
IMP_Mailbox_SessionCache::CACHE_LABEL
),
$ob->mbox_ob
);
$ftree = $injector->getInstance('IMP_Ftree');
$ftree->delete($ob);
$ftree->insert($ob);
break;
}
/* Reset the sort direction for system queries. */
if ($this->isSystemQuery($ob)) {
$ob->mbox_ob->setSort(null, null, true);
}
$this->changed = true;
return $ob;
}
/**
* Saves the list of filters for the current user.
*
* @param array $filters The filter list.
*/
public function setFilters($filters)
{
$GLOBALS['prefs']->setValue('filter', serialize(array_values($filters)));
$this->_getFilters();
}
/**
* Loads the list of filters for the current user.
*/
protected function _getFilters()
{
$filters = array();
/* Build list of default filters. */
$di = new DirectoryIterator(IMP_BASE . '/lib/Search/Filter');
foreach ($di as $val) {
if ($val->isFile()) {
$cname = 'IMP_Search_Filter_' . $val->getBasename('.php');
if (($cname != 'IMP_Search_Filter_Builtin') &&
class_exists($cname)) {
$filter = new $cname();
$filters[$filter->id] = $filter;
}
}
}
if ($f_list = $GLOBALS['prefs']->getValue('filter')) {
$f_list = @unserialize($f_list);
if (is_array($f_list)) {
foreach ($f_list as $val) {
if ($val instanceof IMP_Search_Filter) {
$filters[$val->id] = $val;
}
}
}
}
$this->_search['filters'] = $filters;
$this->changed = true;
}
/**
* Is a mailbox a filter query?
*
* @param string $id The mailbox ID.
* @param boolean $editable Is this an editable (i.e. not built-in)
* filter query?
*/
public function isFilter($id, $editable = false)
{
return (isset($this->_search['filters'][$this->_strip($id)]) &&
(!$editable || $this[$id]->canEdit));
}
/**
* Converts a filter to a search query and stores it in the local
* session.
*
* @param string $id The mailbox ID of the filter.
* @param array $mboxes The list of mailboxes to apply the filter on.
* @param string $mid Use as the mailbox ID.
*
* @return IMP_Search_Query The created query object.
* @throws InvalidArgumentException
*/
public function applyFilter($id, array $mboxes, $mid = null)
{
if (!$this->isFilter($id)) {
throw new InvalidArgumentException('Invalid filter ID given.');
}
if (!is_null($mid)) {
$mid = $this->_strip($mid);
}
$q_ob = $this[$id]->toQuery($mboxes, $mid);
$this->_search['query'][$q_ob->id] = $q_ob;
$this->changed = true;
return $q_ob;
}
/**
* Saves the list of virtual folders for the current user.
*
* @param array $vfolders The virtual folder list.
*/
public function setVFolders($vfolders)
{
$GLOBALS['prefs']->setValue('vfolder', serialize(array_values($vfolders)));
}
/**
* Loads the list of virtual folders for the current user.
*/
protected function _getVFolders()
{
$vf = array();
/* Build list of default virtual folders. */
$di = new DirectoryIterator(IMP_BASE . '/lib/Search/Vfolder');
$disable = array('IMP_Search_Vfolder_Vtrash');
foreach ($di as $val) {
if ($val->isFile()) {
$cname = 'IMP_Search_Vfolder_' . $val->getBasename('.php');
if (($cname != 'IMP_Search_Vfolder_Builtin') &&
class_exists($cname)) {
$vfolder = new $cname(array(
'disable' => in_array($cname, $disable)
));
$vf[$vfolder->id] = $vfolder;
}
}
}
if ($pref_vf = $GLOBALS['prefs']->getValue('vfolder')) {
$pref_vf = @unserialize($pref_vf);
if (is_array($pref_vf)) {
foreach ($pref_vf as $val) {
if ($val instanceof IMP_Search_Vfolder) {
$vf[$val->id] = $val;
}
}
}
}
$this->_search['vfolders'] = $vf;
$this->changed = true;
}
/**
* Is a mailbox a virtual folder?
*
* @param string $id The mailbox ID.
* @param boolean $editable Is this an editable (i.e. not built-in)
* virtual folder?
*
* @return boolean Whether the mailbox ID is a virtual folder.
*/
public function isVFolder($id, $editable = false)
{
return (isset($this->_search['vfolders'][$this->_strip($id)]) &&
(!$editable || $this[$id]->canEdit));
}
/**
* Determines whether a mailbox ID is the Virtual Trash Folder.
*
* @param string $id The mailbox id.
*
* @return boolean True if the ID is the Virtual Trash folder.
*/
public function isVTrash($id)
{
return (($this->isVFolder($id)) &&
($this[$id] instanceof IMP_Search_Vfolder_Vtrash));
}
/**
* Determines whether a mailbox ID is the Virtual INBOX Folder.
*
* @param string $id The mailbox id.
*
* @return boolean True if the ID is the Virtual INBOX folder.
*/
public function isVinbox($id)
{
return (($this->isVFolder($id)) &&
($this[$id] instanceof IMP_Search_Vfolder_Vinbox));
}
/**
* Is a mailbox a search query?
*
* @param string $id The mailbox ID.
* @param boolean $editable Is this an editable (i.e. not built-in)
* search query?
*
* @return boolean True if a search query.
*/
public function isQuery($id, $editable = false)
{
return (isset($this->_search['query'][$this->_strip($id)]) &&
(!$editable || !$this->isSystemQuery($id)));
}
/**
* Is a mailbox a system (built-in) search query?
*
* @param string $id The mailbox ID.
*
* @return boolean True if a system search query.
*/
public function isSystemQuery($id)
{
return (isset($this->_search['query'][$this->_strip($id)]) &&
in_array($this[$id]->id, array(self::BASIC_SEARCH, self::DIMP_FILTERSEARCH, self::DIMP_QUICKSEARCH)));
}
/**
* Returns a link to edit a given search query.
*
* @param string $id The search query id.
*
* @return Horde_Url The URL to the search page.
*/
public function editUrl($id)
{
global $registry;
$mbox = IMP_Mailbox::get($this->createSearchId($id));
switch ($registry->getView()) {
case $registry::VIEW_BASIC:
return $mbox->url(IMP_Basic_Search::url())->add(array(
'edit_query' => 1
));
case $registry::VIEW_DYNAMIC:
return IMP_Dynamic_Mailbox::url()->setAnchor(
'search:' . json_encode(array(
'edit_query' => 1,
'mailbox' => $mbox->form_to
))
);
}
}
/**
* Is the given mailbox a search mailbox?
*
* @param string $id The mailbox name.
*
* @return boolean Whether the given mailbox name is a search mailbox.
*/
public function isSearchMbox($id)
{
return (strpos($id, self::MBOX_PREFIX) === 0);
}
/**
* Strip the identifying label from a mailbox ID.
*
* @param string $id The mailbox query ID.
*
* @return string The virtual folder ID, with any IMP specific
* identifying information stripped off.
*/
protected function _strip($id)
{
return $this->isSearchMbox($id)
? substr($id, strlen(self::MBOX_PREFIX))
: strval($id);
}
/**
* Create the canonical search ID for a given search query.
*
* @param string $id The mailbox query ID.
*
* @return string The canonical search query ID.
*/
public function createSearchId($id)
{
return self::MBOX_PREFIX . $this->_strip($id);
}
/* ArrayAccess methods. */
public function offsetExists($offset)
{
$id = $this->_strip($offset);
foreach (array_keys($this->_search) as $key) {
if (isset($this->_search[$key][$id])) {
return true;
}
}
return false;
}
public function offsetGet($offset)
{
$id = $this->_strip($offset);
foreach (array_keys($this->_search) as $key) {
if (isset($this->_search[$key][$id])) {
return $this->_search[$key][$id];
}
}
return null;
}
/**
* Alter the current IMAP search query.
*
* @param string $offset The search query id.
* @param IMP_Search_Query $value The query object.
*
* @throws InvalidArgumentException
*/
public function offsetSet($offset, $value)
{
if (!($value instanceof IMP_Search_Query)) {
throw new InvalidArgumentException('$value must be a query object.');
}
$id = $this->_strip($offset);
foreach (array_keys($this->_search) as $key) {
if (isset($this->_search[$key][$id])) {
$this->_search[$key][$id] = $value;
$this->changed = true;
if ($key == 'vfolders') {
$this->setVFolders($this->_search['vfolders']);
$ftree = $GLOBALS['injector']->getInstance('IMP_Ftree');
$ftree->delete($value);
$ftree->insert($value);
}
return;
}
}
throw new InvalidArgumentException('Creating search queries by array index is not supported. Use createQuery() instead.');
}
/**
* Deletes an IMAP search query.
*
* @param string $offset The search query id.
*/
public function offsetUnset($offset)
{
$id = $this->_strip($offset);
foreach (array_keys($this->_search) as $val) {
if (isset($this->_search[$val][$id])) {
$value = $this->_search[$val][$id];
unset($this->_search[$val][$id]);
$this->changed = true;
if ($val == 'vfolders') {
$this->setVFolders($this->_search['vfolders']);
$GLOBALS['injector']->getInstance('IMP_Ftree')->delete($value);
}
break;
}
}
}
/* IteratorAggregate method. */
/**
*/
public function getIterator()
{
$iterator = new AppendIterator();
foreach ($this->_search as $val) {
$iterator->append(new ArrayIterator($val));
}
return $iterator;
}
/* Serializable methods. */
/**
* Serialize.
*
* @return string Serialized representation of this object.
*/
public function serialize()
{
return $GLOBALS['injector']->getInstance('Horde_Pack')->pack(
$this->_search,
array(
'compression' => false,
'phpob' => true
)
);
}
/**
* Unserialize.
*
* @param string $data Serialized data.
*
* @throws Exception
*/
public function unserialize($data)
{
$this->_search = $GLOBALS['injector']->getInstance('Horde_Pack')->unpack($data);
}
}