diff --git a/emailadmin/inc/class.emailadmin_imapbase.inc.php b/emailadmin/inc/class.emailadmin_imapbase.inc.php index 6eb44ee074..1674d43a1d 100644 --- a/emailadmin/inc/class.emailadmin_imapbase.inc.php +++ b/emailadmin/inc/class.emailadmin_imapbase.inc.php @@ -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 * diff --git a/mail/inc/class.mail_tree.inc.php b/mail/inc/class.mail_tree.inc.php new file mode 100644 index 0000000000..b7d43cfdd4 --- /dev/null +++ b/mail/inc/class.mail_tree.inc.php @@ -0,0 +1,408 @@ + + * @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)); + } + +} diff --git a/mail/inc/class.mail_ui.inc.php b/mail/inc/class.mail_ui.inc.php index 374eba7ed3..8850f047e4 100644 --- a/mail/inc/class.mail_ui.inc.php +++ b/mail/inc/class.mail_ui.inc.php @@ -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'] = ''. - ($value['name'] ? ( $filename ? $filename : $value['name'] ) : lang('(no subject)')). + ($value['name'] ? $value['name'] : lang('(no subject)')). ''; $linkData = array diff --git a/mail/js/app.js b/mail/js/app.js index 79b7f16129..b9651920b1 100644 --- a/mail/js/app.js +++ b/mail/js/app.js @@ -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 * diff --git a/mail/templates/default/app.css b/mail/templates/default/app.css index 927a2106f4..0c6fe166a9 100644 --- a/mail/templates/default/app.css +++ b/mail/templates/default/app.css @@ -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; +} \ No newline at end of file diff --git a/mail/templates/default/subscribe.xet b/mail/templates/default/subscribe.xet index 2b0107e6cc..5fc5fedca3 100755 --- a/mail/templates/default/subscribe.xet +++ b/mail/templates/default/subscribe.xet @@ -13,14 +13,14 @@ - +