mirror of
https://github.com/EGroupware/egroupware.git
synced 2025-01-11 00:18:25 +01:00
* Mail/subscription: Performance improvement
-Enhancement in fast loading subscription dialog -Implement indicator for loading folder -Performance improvement by handling most of the operations in client-side
This commit is contained in:
commit
8788b444cf
@ -835,11 +835,10 @@ class emailadmin_imapbase
|
||||
* openConnection
|
||||
*
|
||||
* @param int $_icServerID = 0
|
||||
* @param boolean $_adminConnection = false
|
||||
* @throws Horde_Imap_Client_Exception on connection error or authentication failure
|
||||
* @throws InvalidArgumentException on missing credentials
|
||||
*/
|
||||
function openConnection($_icServerID=0, $_adminConnection=false)
|
||||
function openConnection($_icServerID=0)
|
||||
{
|
||||
//error_log( "-------------------------->open connection ".function_backtrace());
|
||||
//error_log(__METHOD__.' ('.__LINE__.') '.' ->'.array2string($this->icServer));
|
||||
@ -2622,7 +2621,69 @@ class emailadmin_imapbase
|
||||
if (self::$debugTimes) self::logRunTimes($starttime,null,function_backtrace(),__METHOD__.' ('.__LINE__.') ');
|
||||
return $folders2return[$this->icServer->ImapServerId];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get IMAP folder for a mailbox
|
||||
*
|
||||
* @param $_nodePath = null folder name to fetch from IMAP,
|
||||
* null means all folders
|
||||
* @param $_onlyTopLevel if set to true only top level objects
|
||||
* will be return and nodePath would be ignored
|
||||
* @param int $_search = 2 search restriction in given mailbox
|
||||
* 0:All folders recursively from the $_nodePath
|
||||
* 1:Only folder of specified $_nodePath
|
||||
* 2:All folders of $_nodePath in the same heirachy level
|
||||
* @return array an array of folder
|
||||
*/
|
||||
function getFolderArray ($_nodePath = null, $_onlyTopLevel = false, $_search= 2)
|
||||
{
|
||||
// delimiter
|
||||
$delimiter = $this->getHierarchyDelimiter();
|
||||
|
||||
$folders = array();
|
||||
|
||||
if ($_onlyTopLevel) // top level leaves
|
||||
{
|
||||
// Get top mailboxes of icServer
|
||||
$topFolders = $this->icServer->getMailboxes("", 2, true);
|
||||
|
||||
foreach ($topFolders as &$node)
|
||||
{
|
||||
$pattern = "/\\".$delimiter."/";
|
||||
$reference = preg_replace($pattern, '', $node['MAILBOX']);
|
||||
$mainFolder = $this->icServer->getMailboxes($reference, 1, true);
|
||||
$subFolders = $this->icServer->getMailboxes($node['MAILBOX'].$node['delimiter'], 2, true);
|
||||
$folders[$node['MAILBOX']] = array_merge((array)$mainFolder, (array)$subFolders);
|
||||
ksort($folders[$node['MAILBOX']]);
|
||||
}
|
||||
}
|
||||
elseif ($_nodePath) // single node
|
||||
{
|
||||
switch ($_search)
|
||||
{
|
||||
// Including children
|
||||
case 0:
|
||||
case 2:
|
||||
$path = $_nodePath.''.$delimiter;
|
||||
break;
|
||||
// Node itself
|
||||
// shouldn't contain next level delimiter
|
||||
case 1:
|
||||
$path = $_nodePath;
|
||||
break;
|
||||
}
|
||||
$folders = $this->icServer->getMailboxes($path, $_search, true);
|
||||
ksort($folders);
|
||||
return $folders;
|
||||
}
|
||||
elseif(!$_nodePath)
|
||||
{
|
||||
$folders = $this->icServer->getMailboxes('', 0, true);
|
||||
}
|
||||
return $folders;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if all automatic folders exist and create them if not
|
||||
*
|
||||
|
408
mail/inc/class.mail_tree.inc.php
Normal file
408
mail/inc/class.mail_tree.inc.php
Normal file
@ -0,0 +1,408 @@
|
||||
<?php
|
||||
/**
|
||||
* EGroupware - Mail - tree worker class
|
||||
*
|
||||
* @link http://www.egroupware.org
|
||||
* @package mail
|
||||
* @author Hadi Nategh [hn@stylite.de]
|
||||
* @copyright (c) 2015 by Stylite AG <info-AT-stylite.de>
|
||||
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
||||
* @version $Id:$
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Define tree as class tree widget
|
||||
*/
|
||||
use \etemplate_widget_tree as tree;
|
||||
|
||||
|
||||
/**
|
||||
* Mail tree worker class
|
||||
* -provides backend functionality for folder tree
|
||||
* -provides classes that may be used by other apps too
|
||||
*
|
||||
*/
|
||||
class mail_tree
|
||||
{
|
||||
/**
|
||||
* delimiter - used to separate profileID from folder-tree-structure
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
static $delimiter = '::';
|
||||
|
||||
/**
|
||||
* Icons used for nodes different states
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
static $leafImages = array(
|
||||
'folderNoSelectClosed' => "folderNoSelectClosed.gif",
|
||||
'folderNoSelectOpen' => "folderNoSelectOpen.gif",
|
||||
'folderOpen' => "folderOpen.gif",
|
||||
'folderClosed' => "MailFolderClosed.png",
|
||||
'folderLeaf' => "MailFolderPlain.png",
|
||||
'folderHome' => "kfm_home.png",
|
||||
'folderAccount' => "thunderbird.png",
|
||||
);
|
||||
|
||||
/**
|
||||
* Mail tree constructor
|
||||
*
|
||||
* @param object $mail_ui
|
||||
*/
|
||||
function __construct($mail_ui) {
|
||||
$this->ui = $mail_ui;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Structs an array of fake INBOX to show as an error node
|
||||
* @param string $_profileID icServer profile id
|
||||
* @param string $_err error message to be shown on tree node
|
||||
* @param mixed $_path
|
||||
* @param mixed $_parent
|
||||
* @return array returns an array of tree node
|
||||
*/
|
||||
static function treeLeafNoConnectionArray($_profileID, $_err, $_path, $_parent)
|
||||
{
|
||||
$baseNode = array('id' => $_profileID);
|
||||
$leaf = array(
|
||||
'id' => $_profileID.self::$delimiter.'INBOX',
|
||||
'text' => $_err,
|
||||
'tooltip' => $_err,
|
||||
'im0' => self::$leafImages["folderNoSelectClosed"],
|
||||
'im1' => self::$leafImages["folderNoSelectOpen.gif"],
|
||||
'im2' => self::$leafImages["folderNoSelectClosed"],
|
||||
'path'=> $_path,
|
||||
'parent' => $_parent
|
||||
);
|
||||
self::setOutStructure($leaf, $baseNode, self::$delimiter);
|
||||
|
||||
return ($baseNode?$baseNode:array( // fallback not connected array
|
||||
'id'=>0,
|
||||
'item'=> array(
|
||||
'text'=>'INBOX',
|
||||
'tooltip'=>'INBOX'.' '.lang('(not connected)'),
|
||||
'im0'=> self::$leafImages['folderHome']
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get folder data from path
|
||||
*
|
||||
* @param string $path a node path
|
||||
* @return array returns an array of data extracted from given node path
|
||||
*/
|
||||
static function pathToFolderData ($_path, $_hDelimiter)
|
||||
{
|
||||
list(,$path) = explode(self::$delimiter, $_path);
|
||||
$path_chain = $parts = explode($_hDelimiter, $path);
|
||||
$name = array_pop($parts);
|
||||
return array (
|
||||
'name' => $name,
|
||||
'mailbox' => $path,
|
||||
'parent' => implode($_hDelimiter, $parts),
|
||||
'text' => $name,
|
||||
'tooltip' => $name,
|
||||
'path' => $path_chain
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given node has children attribute set
|
||||
*
|
||||
* @param array $_node array of a node
|
||||
* @return int returns 1 if it has children flag set otherwise 0
|
||||
*/
|
||||
private static function nodeHasChildren ($_node)
|
||||
{
|
||||
$hasChildren = 0;
|
||||
if (in_array('\haschildren', $_node['ATTRIBUTES']) ||
|
||||
in_array('\Haschildren', $_node['ATTRIBUTES']) ||
|
||||
in_array('\HasChildren', $_node['ATTRIBUTES'])) $hasChildren = 1;
|
||||
return $hasChildren;
|
||||
}
|
||||
|
||||
/**
|
||||
* getTree provides tree structure regarding to selected node
|
||||
*
|
||||
* @param string $_parent = null no parent node means root with the first level of folders
|
||||
* @param string $_profileID = '' icServer id
|
||||
* @param int|boolean $_openTopLevel = 1 Open top level folders on load if it's set to 1|true,
|
||||
* false|0 leaves them in closed state
|
||||
* @param $_noCheckboxNS = false no checkbox for namesapaces makes sure to not put checkbox for namespaces node
|
||||
*
|
||||
* @return array returns an array of mail tree structure according to provided node
|
||||
*/
|
||||
function getTree ($_parent = null, $_profileID = '', $_openTopLevel = 1, $_noCheckboxNS = false)
|
||||
{
|
||||
//Init mail folders
|
||||
$tree = array(tree::ID=> $_parent?$_parent:0,tree::CHILDREN => array());
|
||||
$hDelimiter = $this->ui->mail_bo->getHierarchyDelimiter();
|
||||
|
||||
if ($_parent) list($_profileID) = explode(self::$delimiter, $_parent);
|
||||
|
||||
if (is_numeric($_profileID) && $_profileID != $this->ui->mail_bo->profileID)
|
||||
{
|
||||
try
|
||||
{
|
||||
$this->ui->changeProfile($_profileID);
|
||||
} catch (Exception $ex) {
|
||||
return self::treeLeafNoConnectionArray($_profileID, $ex->getMessage(),array($_profileID), '');
|
||||
}
|
||||
}
|
||||
|
||||
if ($_parent) // Single node loader
|
||||
{
|
||||
try
|
||||
{
|
||||
$nodeInfo = self::pathToFolderData($_parent, $hDelimiter);
|
||||
$folders = $this->ui->mail_bo->getFolderArray($nodeInfo['mailbox'],false,2);
|
||||
} catch (Exception $ex) {
|
||||
return self::treeLeafNoConnectionArray($_profileID, $ex->getMessage(),array($_profileID), '');
|
||||
}
|
||||
|
||||
$childrenNode = array();
|
||||
foreach ($folders as &$node)
|
||||
{
|
||||
$nodeId = $_profileID.self::$delimiter.$node['MAILBOX'];
|
||||
$nodeData = self::pathToFolderData($nodeId, $node['delimiter']);
|
||||
$childrenNode[] = array(
|
||||
tree::ID=> $nodeId,
|
||||
tree::AUTOLOAD_CHILDREN => self::nodeHasChildren($node),
|
||||
tree::CHILDREN =>array(),
|
||||
tree::LABEL => $nodeData['text'],
|
||||
tree::TOOLTIP => $nodeData['tooltip'],
|
||||
tree::IMAGE_LEAF => self::$leafImages['folderLeaf'],
|
||||
tree::IMAGE_FOLDER_OPEN => self::$leafImages['folderOpen'],
|
||||
tree::IMAGE_FOLDER_CLOSED => self::$leafImages['folderClose'],
|
||||
tree::CHECKED => $node['SUBSCRIBED'],
|
||||
'parent' => $_parent
|
||||
);
|
||||
}
|
||||
$tree[tree::CHILDREN] = $childrenNode;
|
||||
}
|
||||
else //Top Level Nodes loader
|
||||
{
|
||||
$baseNode = array('id' => 0);
|
||||
foreach(emailadmin_account::search(true, false) as $acc_id => $accObj)
|
||||
{
|
||||
if (!$accObj->is_imap()|| $acc_id != $_profileID) continue;
|
||||
$identity = emailadmin_account::identity_name($accObj,true,$GLOBALS['egw_info']['user']['acount_id']);
|
||||
$baseNode = array(
|
||||
tree::ID=> $acc_id,
|
||||
tree::LABEL => str_replace(array('<','>'),array('[',']'),$identity),
|
||||
tree::TOOLTIP => '('.$acc_id.') '.htmlspecialchars_decode($identity),
|
||||
tree::IMAGE_LEAF => self::$leafImages['folderAccount'],
|
||||
tree::IMAGE_FOLDER_OPEN => self::$leafImages['folderAccount'],
|
||||
tree::IMAGE_FOLDER_CLOSED => self::$leafImages['folderAccount'],
|
||||
'path'=> array($acc_id),
|
||||
tree::CHILDREN => array(), // dynamic loading on unfold
|
||||
tree::AUTOLOAD_CHILDREN => true,
|
||||
'parent' => '',
|
||||
tree::OPEN => $_openTopLevel,
|
||||
// mark on account if Sieve is enabled
|
||||
'data' => array(
|
||||
'sieve' => $accObj->imapServer()->acc_sieve_enabled,
|
||||
'spamfolder'=> $accObj->imapServer()->acc_folder_junk?true:false
|
||||
),
|
||||
tree::NOCHECKBOX => $_noCheckboxNS
|
||||
);
|
||||
self::setOutStructure($baseNode, $tree,self::$delimiter);
|
||||
}
|
||||
//List of folders
|
||||
$foldersList = $this->ui->mail_bo->getFolderArray(null, true);
|
||||
|
||||
// Parent node arrays
|
||||
$parentNode = $parentNodes = array();
|
||||
|
||||
foreach ($foldersList as $index => $topFolder)
|
||||
{
|
||||
$nameSpaces = $this->ui->mail_bo->_getNameSpaces();
|
||||
$noCheckbox = false;
|
||||
foreach ($nameSpaces as &$ns)
|
||||
{
|
||||
if($_noCheckboxNS && $ns['prefix'] === $index.$hDelimiter) $noCheckbox = true;
|
||||
}
|
||||
$parentNode = array(
|
||||
tree::ID=>$_profileID.self::$delimiter.$topFolder[$index]['MAILBOX'],
|
||||
tree::AUTOLOAD_CHILDREN => self::nodeHasChildren($topFolder[$index]),
|
||||
tree::CHILDREN =>array(),
|
||||
tree::LABEL =>lang($topFolder[$index]['MAILBOX']),
|
||||
tree::OPEN => $_openTopLevel,
|
||||
tree::TOOLTIP => lang($topFolder[$index]['MAILBOX']),
|
||||
tree::CHECKED => $topFolder[$index]['SUBSCRIBED'],
|
||||
tree::NOCHECKBOX => $noCheckbox
|
||||
);
|
||||
if ($index === "INBOX")
|
||||
{
|
||||
$parentNode[tree::IMAGE_LEAF] = self::$leafImages['folderHome'];
|
||||
$parentNode[tree::IMAGE_FOLDER_OPEN] = self::$leafImages['folderHome'];
|
||||
$parentNode[tree::IMAGE_FOLDER_CLOSED] = self::$leafImages['folderHome'];
|
||||
}
|
||||
if(stripos(array2string($topFolder[$index]['ATTRIBUTES']),'\noselect')!== false)
|
||||
{
|
||||
$parentNode[tree::IMAGE_LEAF] = self::$leafImages['folderNoSelectClosed'];
|
||||
$parentNode[tree::IMAGE_FOLDER_OPEN] = self::$leafImages['folderNoSelectOpen'];
|
||||
$parentNode[tree::IMAGE_FOLDER_CLOSED] = self::$leafImages['folderNoSelectClosed'];
|
||||
}
|
||||
// Save parentNodes
|
||||
$parentNodes []= $index;
|
||||
// Remove the parent nodes from the list
|
||||
unset ($topFolder[$index]);
|
||||
|
||||
//$parentNode[tree::CHILDREN][] =$childrenNode;
|
||||
$baseNode[tree::CHILDREN][] = $parentNode;
|
||||
}
|
||||
foreach ($parentNodes as $pIndex => $parent)
|
||||
{
|
||||
$childrenNodes = $childNode = array();
|
||||
$definedFolders = array(
|
||||
'Trash' => $this->ui->mail_bo->getTrashFolder(false),
|
||||
'Templates' => $this->ui->mail_bo->getTemplateFolder(false),
|
||||
'Drafts' => $this->ui->mail_bo->getDraftFolder(false),
|
||||
'Sent' => $this->ui->mail_bo->getSentFolder(false),
|
||||
'Junk' => $this->ui->mail_bo->getJunkFolder(false),
|
||||
'Outbox' => $this->ui->mail_bo->getOutboxFolder(false),
|
||||
);
|
||||
// Iterate over childern of each top folder(namespaces)
|
||||
foreach ($foldersList[$parent] as &$node)
|
||||
{
|
||||
// Skipe the parent node itself
|
||||
if (is_array($foldersList[$parent][$parent]) &&
|
||||
$foldersList[$parent][$parent]['MAILBOX'] === $node['MAILBOX']) continue;
|
||||
|
||||
$pathArr = explode($node['delimiter'], $node['MAILBOX']);
|
||||
$folderName = array_pop($pathArr);
|
||||
$parentPath = $_profileID.self::$delimiter.implode($pathArr,$node['delimiter']);
|
||||
|
||||
$nodeId = $_profileID.self::$delimiter.$node['MAILBOX'];
|
||||
|
||||
$childNode = array(
|
||||
tree::ID => $nodeId,
|
||||
tree::AUTOLOAD_CHILDREN => self::nodeHasChildren($node),
|
||||
tree::CHILDREN => array(),
|
||||
tree::LABEL => lang($folderName),
|
||||
'parent' => $parentPath,
|
||||
tree::CHECKED => $node['SUBSCRIBED']
|
||||
);
|
||||
|
||||
if (array_search($node['MAILBOX'], $definedFolders) !== false)
|
||||
{
|
||||
//User defined folders icons
|
||||
$childNode[tree::IMAGE_LEAF] =
|
||||
$childNode[tree::IMAGE_FOLDER_OPEN] =
|
||||
$childNode [tree::IMAGE_FOLDER_CLOSED] = "MailFolder".$folderName.".png";
|
||||
}
|
||||
elseif(stripos(array2string($node['ATTRIBUTES']),'\noselect')!== false)
|
||||
{
|
||||
$childNode[tree::IMAGE_LEAF] = self::$leafImages['folderNoSelectClosed'];
|
||||
$childNode[tree::IMAGE_FOLDER_OPEN] = self::$leafImages['folderNoSelectOpen'];
|
||||
$childNode[tree::IMAGE_FOLDER_CLOSED] = self::$leafImages['folderNoSelectClosed'];
|
||||
}
|
||||
else
|
||||
{
|
||||
$childNode[tree::IMAGE_LEAF] = self::$leafImages['folderLeaf'];
|
||||
$childNode[tree::IMAGE_FOLDER_OPEN] = self::$leafImages['folderOpen'];
|
||||
$childNode[tree::IMAGE_FOLDER_CLOSED] = self::$leafImages['folderClose'];
|
||||
}
|
||||
$childrenNodes[] = $childNode;
|
||||
}
|
||||
$baseNode[tree::CHILDREN][$pIndex][tree::CHILDREN] = $childrenNodes;
|
||||
}
|
||||
$tree[tree::CHILDREN][0] = $baseNode;
|
||||
}
|
||||
return $tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* setOutStructure - helper function to transform the folderObjectList to dhtmlXTreeObject requirements
|
||||
*
|
||||
* @param array $data data to be processed
|
||||
* @param array &$out, out array
|
||||
* @param string $del needed as glue for parent/child operation / comparsion
|
||||
* @param boolean $createMissingParents a missing parent, instead of throwing an exception
|
||||
* @param array $nameSpace used to check on creation of nodes in namespaces other than personal
|
||||
* as clearance for access may be limited to a single branch-node of a tree
|
||||
* @return void
|
||||
*/
|
||||
static function setOutStructure($data, &$out, $del='.', $createMissingParents=true, $nameSpace=array())
|
||||
{
|
||||
//error_log(__METHOD__."(".array2string($data).', '.array2string($out).", '$del')");
|
||||
$components = $data['path'];
|
||||
array_pop($components); // remove own name
|
||||
|
||||
$insert = &$out;
|
||||
$parents = array();
|
||||
foreach($components as $component)
|
||||
{
|
||||
if (count($parents)>1)
|
||||
{
|
||||
$helper = array_slice($parents,1,null,true);
|
||||
$parent = $parents[0].self::$delimiter.implode($del, $helper);
|
||||
if ($parent) $parent .= $del;
|
||||
}
|
||||
else
|
||||
{
|
||||
$parent = implode(self::$delimiter, $parents);
|
||||
if ($parent) $parent .= self::$delimiter;
|
||||
}
|
||||
|
||||
if (!is_array($insert) || !isset($insert['item']))
|
||||
{
|
||||
// throwing an exeption here seems to be unrecoverable,
|
||||
// even if the cause is a something that can be handeled by the mailserver
|
||||
if (mail_bo::$debug) error_log(__METHOD__.':'.__LINE__." id=$data[id]: Parent '$parent' of '$component' not found!");
|
||||
// should we hit the break? if in personal: sure, something is wrong with the folderstructure
|
||||
// if in shared or others we may proceed as access to folders may very well be limited to
|
||||
// a single folder within the tree
|
||||
$break = true;
|
||||
foreach ($nameSpace as $nsp)
|
||||
{
|
||||
// if (appropriately padded) namespace prefix of (others or shared) is the leading part of parent
|
||||
// we want to create the node in question as we meet the above considerations
|
||||
if ($nsp['type']!='personal' && $nsp['prefix_present'] && stripos($parent,$data['path'][0].self::$delimiter.$nsp['prefix'])===0)
|
||||
{
|
||||
if (mail_bo::$debug) error_log(__METHOD__.__LINE__.' about to create:'.$parent.' in '.$data['path'][0].self::$delimiter.$nsp['prefix']);
|
||||
$break=false;
|
||||
}
|
||||
}
|
||||
if ($break) break;
|
||||
}
|
||||
if ($insert['item'])
|
||||
{
|
||||
foreach($insert['item'] as &$item)
|
||||
{
|
||||
if ($item['id'] == $parent.$component)
|
||||
{
|
||||
$insert =& $item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($item['id'] != $parent.$component)
|
||||
{
|
||||
if ($createMissingParents)
|
||||
{
|
||||
unset($item);
|
||||
$item = array('id' => $parent.$component, 'text' => $component, 'im0' => "folderNoSelectClosed.gif",'im1' => "folderNoSelectOpen.gif",'im2' => "folderNoSelectClosed.gif",'tooltip' => lang('no access'));
|
||||
$insert['item'][] =& $item;
|
||||
$insert =& $item;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new egw_exception_assertion_failed(__METHOD__.':'.__LINE__.": id=$data[id]: Parent '$parent' '$component' not found!");
|
||||
}
|
||||
}
|
||||
$parents[] = $component;
|
||||
}
|
||||
unset($data['path']);
|
||||
$insert['item'][] = $data;
|
||||
//error_log(__METHOD__."() leaving with out=".array2string($out));
|
||||
}
|
||||
|
||||
}
|
@ -23,7 +23,7 @@
|
||||
* If no profile change is needed they just call:
|
||||
* $mail_ui = new mail_ui();
|
||||
* Afterwards they use $mail_ui instead of $this.
|
||||
*/
|
||||
*/
|
||||
class mail_ui
|
||||
{
|
||||
/**
|
||||
@ -142,6 +142,7 @@ class mail_ui
|
||||
//openConnection gathers SpecialUseFolderInformation and Delimiter Info
|
||||
$this->mail_bo->openConnection(self::$icServerID);
|
||||
}
|
||||
$this->mail_tree = new mail_tree($this);
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
@ -233,7 +234,17 @@ class mail_ui
|
||||
|
||||
if (mail_bo::$debugTimes) mail_bo::logRunTimes($starttime,null,'',__METHOD__.__LINE__);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Ajax function to request next branch of a tree branch
|
||||
*/
|
||||
static function ajax_tree_autoloading ($_id = null)
|
||||
{
|
||||
$mail_ui = new mail_ui();
|
||||
$_id = $_id? $_id:$_GET['id'];
|
||||
etemplate_widget_tree::send_quote_json($mail_ui->mail_tree->getTree($_id,'',1,false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscription popup window
|
||||
*
|
||||
@ -252,19 +263,28 @@ class mail_ui
|
||||
{
|
||||
egw_framework::window_close('Missing acc_id!');
|
||||
}
|
||||
$sel_options['foldertree'] = $this->getFolderTree(false, $profileId, false, false);
|
||||
|
||||
// Initial tree's options, the rest would be loaded dynamicaly by autoloading,
|
||||
// triggered from client-side. Also, we keep this here as
|
||||
$sel_options['foldertree'] = $this->mail_tree->getTree(null,$profileId,1,true);
|
||||
|
||||
//Get all subscribed folders
|
||||
// as getting all subscribed folders is very fast operation
|
||||
// we can use it to get a comparison base for folders which
|
||||
// got subscribed or unsubscribed by the user
|
||||
try {
|
||||
$subscribed = $this->mail_bo->icServer->listSubscribedMailboxes('',0,true);
|
||||
} catch (Exception $ex) {
|
||||
egw_framework::message($ex->getMessage());
|
||||
}
|
||||
|
||||
if (!is_array($content))
|
||||
{
|
||||
$content['foldertree'] = array();
|
||||
$allFolders = $this->mail_bo->getFolderObjects(false,false,false,false);
|
||||
foreach ($allFolders as $folder)
|
||||
|
||||
foreach ($subscribed as $folder)
|
||||
{
|
||||
$folderName = $profileId . self::$delimiter . $folder->folderName;
|
||||
if ($folder->subscribed)
|
||||
{
|
||||
array_push($content['foldertree'], $folderName);
|
||||
}
|
||||
$folderName = $profileId . self::$delimiter . $folder['MAILBOX'];
|
||||
array_push($content['foldertree'], $folderName);
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -281,13 +301,29 @@ class mail_ui
|
||||
{
|
||||
$namespace_roots[] = $profileId . self::$delimiter . str_replace($namespace['delimiter'], '', $namespace['prefix']);
|
||||
}
|
||||
error_log(__METHOD__."() namespace_roots=".array2string($namespace_roots));
|
||||
$to_subscribe = array_diff($content['foldertree'], $content['current_subscribed'], $namespace_roots);
|
||||
$to_unsubscribe = array_diff($content['current_subscribed'], $content['foldertree'], $namespace_roots);
|
||||
$to_unsubscribe = $to_subscribe = array();
|
||||
foreach ($content['foldertree'] as $path => $value)
|
||||
{
|
||||
list(,$node) = explode($profileId.self::$delimiter, $path);
|
||||
if ($node)
|
||||
{
|
||||
if (is_array($subscribed) && $subscribed[$node] && !$value['value']) $to_unsubscribe []= $node;
|
||||
if (is_array($subscribed) && !$subscribed[$node] && $value['value']) $to_subscribe [] = $node;
|
||||
if ($value['value']) $cont[] = $path;
|
||||
}
|
||||
|
||||
}
|
||||
$content['foldertree'] = $cont;
|
||||
// set foldertree options to basic node in order to avoid initial autoloading
|
||||
// from client side, as no options would trigger that.
|
||||
$sel_options['foldertree'] = array('id' => '0', 'item'=> array());
|
||||
foreach(array_merge($to_subscribe, $to_unsubscribe) as $mailbox)
|
||||
{
|
||||
if (in_array($profileId.self::$delimiter.$mailbox, $namespace_roots, true))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
$subscribe = in_array($mailbox, $to_subscribe);
|
||||
list(,$mailbox) = explode(self::$delimiter, $mailbox); // remove profileId and delimiter
|
||||
try {
|
||||
$this->mail_bo->icServer->subscribeMailbox($mailbox, $subscribe);
|
||||
}
|
||||
@ -319,7 +355,7 @@ class mail_ui
|
||||
// update foldertree in main window
|
||||
$parentFolder='INBOX';
|
||||
$refreshData = array(
|
||||
$profileId => lang($parentFolder)
|
||||
$profileId => lang($parentFolder),
|
||||
);
|
||||
$response = egw_json_response::get();
|
||||
foreach($refreshData as $folder => &$name)
|
||||
@ -327,7 +363,9 @@ class mail_ui
|
||||
$name = $this->getFolderTree(true, $folder, true, true, false);
|
||||
}
|
||||
// give success/error message to opener and popup itself
|
||||
//$response->call('opener.app.mail.subscription_refresh',$refreshData);
|
||||
$response->call('opener.app.mail.mail_reloadNode',$refreshData);
|
||||
|
||||
egw_framework::refresh_opener($msg, 'mail', null, null, null, null, null, $msg_type);
|
||||
if ($button == 'apply')
|
||||
{
|
||||
@ -343,7 +381,7 @@ class mail_ui
|
||||
}
|
||||
|
||||
$preserv['profileId'] = $profileId;
|
||||
$preserv['current_subscribed'] = $content['foldertree'];
|
||||
|
||||
$readonlys = array();
|
||||
|
||||
$stmpl->exec('mail.mail_ui.subscription', $content,$sel_options,$readonlys,$preserv,2);
|
||||
@ -733,7 +771,7 @@ class mail_ui
|
||||
function getFolderTree($_fetchCounters=false, $_nodeID=null, $_subscribedOnly=true, $_returnNodeOnly=true, $_useCacheIfPossible=true, $_popWizard=true)
|
||||
{
|
||||
if (mail_bo::$debugTimes) $starttime = microtime (true);
|
||||
if (!is_null($_nodeID) && $_nodeID !=0)
|
||||
if (!is_null($_nodeID) && $_nodeID != 0)
|
||||
{
|
||||
list($_profileID,$_folderName) = explode(self::$delimiter,$_nodeID,2);
|
||||
unset($_folderName);
|
||||
@ -748,18 +786,7 @@ class mail_ui
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
$out = array('id' => $_profileID);
|
||||
$oA = array('id' => $_profileID.self::$delimiter.'INBOX',
|
||||
'text' => $e->getMessage(),
|
||||
'tooltip' => $e->getMessage(),
|
||||
'im0' => "folderNoSelectClosed.gif",
|
||||
'im1' => "folderNoSelectOpen.gif",
|
||||
'im2' => "folderNoSelectClosed.gif",
|
||||
'path'=> array($_profileID),
|
||||
'parent' => ''
|
||||
);
|
||||
$this->setOutStructure($oA, $out, self::$delimiter);
|
||||
return ($out?$out:array('id'=>0, 'item'=>array('text'=>'INBOX','tooltip'=>'INBOX'.' '.lang('(not connected)'),'im0'=>'kfm_home.png')));
|
||||
return self::treeLeafNoConnectionArray($_profileID, $e->getMessage(), array($_profileID), '');
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -820,22 +847,12 @@ class mail_ui
|
||||
// mark on account if Sieve is enabled
|
||||
'data' => array('sieve' => $accountObj->imapServer()->acc_sieve_enabled),
|
||||
);
|
||||
$this->setOutStructure($oA, $out, self::$delimiter);
|
||||
mail_tree::setOutStructure($oA, $out, self::$delimiter);
|
||||
|
||||
// create a fake INBOX folder showing connection error (necessary that client UI unlocks tree!)
|
||||
if ($e && $acc_id == $_profileID && !$folderObjects)
|
||||
{
|
||||
$oA = array(
|
||||
'id' => $acc_id.self::$delimiter.'INBOX',
|
||||
'text' => lang($e->getMessage()),
|
||||
'tooltip' => '('.$acc_id.') '.$e->getMessage(),
|
||||
'im0' => "folderNoSelectClosed.gif",
|
||||
'im1' => "folderNoSelectOpen.gif",
|
||||
'im2' => "folderNoSelectClosed.gif",
|
||||
'path'=> array($acc_id, 'INBOX'),
|
||||
'parent' => $acc_id,
|
||||
);
|
||||
$this->setOutStructure($oA, $out, self::$delimiter);
|
||||
$out = self::treeLeafNoConnectionArray($acc_id, lang($e->getMessage()), array($acc_id, 'INBOX'), $acc_id);
|
||||
}
|
||||
}
|
||||
//$endtime = microtime(true) - $starttime;
|
||||
@ -852,6 +869,7 @@ class mail_ui
|
||||
$nameSpace = $this->mail_bo->_getNameSpaces();
|
||||
foreach($folderObjects as $key => $obj)
|
||||
{
|
||||
$fS = $this->mail_bo->getFolderStatus($key,false,($_fetchCounters?false:true));
|
||||
//error_log(__METHOD__.__LINE__.array2string($key));
|
||||
$levels = explode($delimiter,$key);
|
||||
$levelCt = count($levels);
|
||||
@ -913,7 +931,7 @@ class mail_ui
|
||||
}
|
||||
$oA['parent'] = $parentName;
|
||||
|
||||
$this->setOutStructure($oA,$out,$obj->delimiter,true,$nameSpace);
|
||||
mail_tree::setOutStructure($oA,$out,$obj->delimiter,true,$nameSpace);
|
||||
$c++;
|
||||
}
|
||||
if (!is_null($_nodeID) && $_nodeID !=0 && $_returnNodeOnly==true)
|
||||
@ -952,92 +970,7 @@ class mail_ui
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* setOutStructure - helper function to transform the folderObjectList to dhtmlXTreeObject requirements
|
||||
*
|
||||
* @param array $data data to be processed
|
||||
* @param array &$out, out array
|
||||
* @param string $del needed as glue for parent/child operation / comparsion
|
||||
* @param boolean $createMissingParents a missing parent, instead of throwing an exception
|
||||
* @param array $nameSpace used to check on creation of nodes in namespaces other than personal
|
||||
* as clearance for access may be limited to a single branch-node of a tree
|
||||
* @return void
|
||||
*/
|
||||
function setOutStructure($data, &$out, $del='.', $createMissingParents=true, $nameSpace=array())
|
||||
{
|
||||
//error_log(__METHOD__."(".array2string($data).', '.array2string($out).", '$del')");
|
||||
$components = $data['path'];
|
||||
array_pop($components); // remove own name
|
||||
|
||||
$insert = &$out;
|
||||
$parents = array();
|
||||
foreach($components as $component)
|
||||
{
|
||||
if (count($parents)>1)
|
||||
{
|
||||
$helper = array_slice($parents,1,null,true);
|
||||
$parent = $parents[0].self::$delimiter.implode($del, $helper);
|
||||
if ($parent) $parent .= $del;
|
||||
}
|
||||
else
|
||||
{
|
||||
$parent = implode(self::$delimiter, $parents);
|
||||
if ($parent) $parent .= self::$delimiter;
|
||||
}
|
||||
|
||||
if (!is_array($insert) || !isset($insert['item']))
|
||||
{
|
||||
// throwing an exeption here seems to be unrecoverable,
|
||||
// even if the cause is a something that can be handeled by the mailserver
|
||||
if (mail_bo::$debug) error_log(__METHOD__.':'.__LINE__." id=$data[id]: Parent '$parent' of '$component' not found!");
|
||||
// should we hit the break? if in personal: sure, something is wrong with the folderstructure
|
||||
// if in shared or others we may proceed as access to folders may very well be limited to
|
||||
// a single folder within the tree
|
||||
$break = true;
|
||||
foreach ($nameSpace as $nsp)
|
||||
{
|
||||
// if (appropriately padded) namespace prefix of (others or shared) is the leading part of parent
|
||||
// we want to create the node in question as we meet the above considerations
|
||||
if ($nsp['type']!='personal' && $nsp['prefix_present'] && stripos($parent,$data['path'][0].self::$delimiter.$nsp['prefix'])===0)
|
||||
{
|
||||
if (mail_bo::$debug) error_log(__METHOD__.__LINE__.' about to create:'.$parent.' in '.$data['path'][0].self::$delimiter.$nsp['prefix']);
|
||||
$break=false;
|
||||
}
|
||||
}
|
||||
if ($break) break;
|
||||
}
|
||||
if ($insert['item'])
|
||||
{
|
||||
foreach($insert['item'] as &$item)
|
||||
{
|
||||
if ($item['id'] == $parent.$component)
|
||||
{
|
||||
$insert =& $item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($item['id'] != $parent.$component)
|
||||
{
|
||||
if ($createMissingParents)
|
||||
{
|
||||
unset($item);
|
||||
$item = array('id' => $parent.$component, 'text' => $component, 'im0' => "folderNoSelectClosed.gif",'im1' => "folderNoSelectOpen.gif",'im2' => "folderNoSelectClosed.gif",'tooltip' => lang('no access'));
|
||||
$insert['item'][] =& $item;
|
||||
$insert =& $item;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new egw_exception_assertion_failed(__METHOD__.':'.__LINE__.": id=$data[id]: Parent '$parent' '$component' not found!");
|
||||
}
|
||||
}
|
||||
$parents[] = $component;
|
||||
}
|
||||
unset($data['path']);
|
||||
$insert['item'][] = $data;
|
||||
//error_log(__METHOD__."() leaving with out=".array2string($out));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get actions / context menu for index
|
||||
*
|
||||
@ -2283,7 +2216,7 @@ class mail_ui
|
||||
|
||||
//error_log(__METHOD__.__LINE__.$linkView);
|
||||
$attachmentHTML[$key]['link_view'] = '<a href="#" ." title="'.$attachmentHTML[$key]['filename'].'" onclick="'.$linkView.' return false;"><b>'.
|
||||
($value['name'] ? ( $filename ? $filename : $value['name'] ) : lang('(no subject)')).
|
||||
($value['name'] ? $value['name'] : lang('(no subject)')).
|
||||
'</b></a>';
|
||||
|
||||
$linkData = array
|
||||
|
@ -44,7 +44,11 @@ app.classes.mail = AppJS.extend(
|
||||
timeout: null,
|
||||
request: null
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
subscription_treeLastState : "",
|
||||
|
||||
/**
|
||||
* abbrevations for common access rights
|
||||
* @array
|
||||
@ -259,6 +263,15 @@ app.classes.mail = AppJS.extend(
|
||||
jQuery('input',to.node).focus();
|
||||
}
|
||||
break;
|
||||
case 'mail.subscribe':
|
||||
if (this.subscription_treeLastState != "")
|
||||
{
|
||||
var tree = this.et2.getWidgetById('foldertree');
|
||||
//Saved state of tree
|
||||
var state = jQuery.parseJSON(this.subscription_treeLastState);
|
||||
|
||||
tree.input.loadJSONObject(tree._htmlencode_node(state));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -3565,7 +3578,57 @@ app.classes.mail = AppJS.extend(
|
||||
var acc_id = parseInt(_senders[0].id);
|
||||
this.egw.open_link('mail.mail_sieve.editVacation&acc_id='+acc_id,'_blank','700x480');
|
||||
},
|
||||
|
||||
|
||||
subscription_refresh: function(_data)
|
||||
{
|
||||
console.log(_data);
|
||||
},
|
||||
|
||||
/**
|
||||
* Submit on apply button and save current tree state
|
||||
*
|
||||
* @param {type} _egw
|
||||
* @param {type} _widget
|
||||
* @returns {undefined}
|
||||
*/
|
||||
subscription_apply: function (_egw, _widget)
|
||||
{
|
||||
var tree = etemplate2.getByApplication('mail')[0].widgetContainer.getWidgetById('foldertree');
|
||||
if (tree)
|
||||
{
|
||||
tree.input._xfullXML = true;
|
||||
this.subscription_treeLastState = tree.input.serializeTreeToJSON();
|
||||
}
|
||||
this.et2._inst.submit(_widget);
|
||||
},
|
||||
|
||||
/**
|
||||
* Show ajax-loader when the autoloading get started
|
||||
*
|
||||
* @param {type} _id item id
|
||||
* @param {type} _widget tree widget
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
subscription_autoloadingStart: function (_id, _widget)
|
||||
{
|
||||
var node = _widget.input._globalIdStorageFind(_id);
|
||||
if (node && typeof node.htmlNode != 'undefined')
|
||||
{
|
||||
var img = jQuery('img',node.htmlNode)[0];
|
||||
img.src = egw.image('ajax-loader', 'admin');
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Revert back the icon after autoloading is finished
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
subscription_autoloadingEnd: function ()
|
||||
{
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Popup the subscription dialog
|
||||
*
|
||||
|
@ -885,3 +885,7 @@ div.mailComposeHeaderSection>table {
|
||||
width:100%;
|
||||
height:100%;
|
||||
}
|
||||
/*Avoid getting scrollbar on form area, let scrolling be handled by tree*/
|
||||
#mail-subscribe {
|
||||
overflow: hidden;
|
||||
}
|
@ -13,14 +13,14 @@
|
||||
</row>
|
||||
<row>
|
||||
<box scrolling="auto">
|
||||
<tree id="foldertree" onclick="app.mail.subscribe_onclick" multiple="true"/>
|
||||
<tree id="foldertree" multiple="true" autoloading="mail_ui::ajax_tree_autoloading" onopenstart="app.mail.subscription_autoloadingStart" onopenend="app.mail.subscription_autoloadingEnd"/>
|
||||
</box>
|
||||
</row>
|
||||
</rows>
|
||||
</grid>
|
||||
<hbox class="dialogFooterToolbar">
|
||||
<button statustext="Saves subscription changes" label="Save" id="button[save]"/>
|
||||
<button statustext="Applies the changes made" label="Apply" id="button[apply]"/>
|
||||
<button statustext="Applies the changes made" label="Apply" id="button[apply]" onclick="app.mail.subscription_apply"/>
|
||||
<button label="Cancel" id="button[cancel]" onclick="window.close()"/>
|
||||
</hbox>
|
||||
</template>
|
||||
|
@ -873,6 +873,10 @@ div.mailComposeHeaderSection > table {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
/*Avoid getting scrollbar on form area, let scrolling be handled by tree*/
|
||||
#mail-subscribe {
|
||||
overflow: hidden;
|
||||
}
|
||||
#popupMainDiv {
|
||||
padding: 5px;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user