view plugins/thunderbird_labels/thunderbird_labels.php @ 32:f8b3ac77e951

improved label deletion handling
author Charlie Root
date Tue, 13 Mar 2018 13:01:49 -0400
parents b2d0a9781472
children 05c4c32948af
line wrap: on
line source

<?php
/**
 * Thunderbird Labels Plugin for Roundcube Webmail
 *
 * Plugin to show the 5 Message Labels Thunderbird Email-Client provides for IMAP
 *
 * @version $Revision$
 * @author Michael Kefeder
 * @url http://code.google.com/p/rcmail-thunderbird-labels/
 */
class thunderbird_labels extends rcube_plugin
{
	public $task = 'mail|settings';
	private $rc;
	private $map;
	
	function init()
	{
		$this->rc = rcmail::get_instance();
		$this->load_config();
		$this->add_texts('localization/', false);
		
		$this->setCustomLabels();

		if ($this->rc->task == 'mail')
		{
			# -- disable plugin when printing message
			if ($this->rc->action == 'print')
				return;
			
			if (!$this->rc->config->get('tb_label_enable'))
			// disable plugin according to prefs
				return;
      
			// how many labels?
			$max_id = $this->rc->config->get('tb_max_label');
			// pass 'tb_max_label', 'tb_label_enable_shortcuts'
                        //   and 'tb_label_style' prefs to JS
			$this->rc->output->set_env('tb_max_label', $max_id);
			$this->rc->output->set_env('tb_label_enable_shortcuts', $this->rc->config->get('tb_label_enable_shortcuts'));
			$this->rc->output->set_env('tb_label_style', $this->rc->config->get('tb_label_style'));
			
			$this->include_script('tb_label.js');
			$this->add_hook('messages_list', array($this, 'read_flags'));
			$this->add_hook('message_load', array($this, 'read_single_flags'));
			$this->add_hook('template_object_messageheaders', array($this, 'color_headers'));
			$this->add_hook('render_page', array($this, 'tblabelpopup'));
			$this->add_hook('message_outgoing_headers', array($this, 'whiteMessage'));
			# The above hook is described as 'Depracated', but no alternative for
			#  non-intrusive access to the headers is available
			$this->include_stylesheet($this->local_skin_path() . '/tb_label.css');
			
			$this->name = get_class($this);
			# -- additional TB flags
			$this->add_tb_flags = array();
			for ($i = 1; $i <= $max_id; $i++) {
			  $this->add_tb_flags['LABEL'.$i]='$Label'.$i;
			}
			$this->message_tb_labels = array();
			
			# This builds the toolbar button, but
			#  it's also used by the ctxtmenu initialisation
			#  contextmenu.js:rcube_context_menu to build
			#  the context menu, and that somehow
			#  ends up with display: none???
			# Bother, missed it, it's before this.container.show
			# Afeter ul.appendnd -- inside menu.show on 307
			# 567? Yes -- that's calling 'hide', but overriding
			# doesn't fix :-(
			$this->add_button(
				array(
					#'command' => 'plugin.thunderbird_labels.rcm_tb_label_submenu',
				      'name' => 'tblabellink',
					'id' => 'tblabelpopuplink',
					'domain' => $this->ID,
					'type' => 'link',
					'class' => 'button tblabel',
					'label' => 'tb_label_button_label', 
				      'title' => 'tb_label_button_title',
					'onclick' => "UI.toggle_popup('tblabelpopup',event);return false",
					'aria-haspopup' => true,
					'aria-expanded' => false,
					'aria-owns' => 'plugin.thunderbird_labels.rcm_tb_label_submenu-menu'
					#'classact' => 'listbutton add',
					),
				'toolbar'
			);
			
			// JS function "set_flags" => PHP function "set_flags"
			$this->register_action('plugin.thunderbird_labels.set_flags', array($this, 'set_flags'));
    
			rcube::write_log('lab','set_flags registered');
    
			if (method_exists($this, 'require_plugin')
				&& in_array('contextmenu', $this->rc->config->get('plugins'))
				&& $this->require_plugin('contextmenu')
				&& $this->rc->config->get('tb_label_enable_contextmenu'))
			{
				if ($this->rc->action == 'list')
					$this->add_hook('render_mailboxlist', array($this, 'show_tb_label_contextmenu'));
			  #rcube::write_log('lab','ctxt: '.$this->rc->action);
			}
			else {
			  #rcube::write_log('lab','no ctxt');
			}
		}
		elseif ($this->rc->task == 'settings')
		{
			$this->include_stylesheet($this->local_skin_path() . '/tb_label.css');
			$this->add_hook('preferences_list', array($this, 'prefs_list'));
			$this->add_hook('preferences_sections_list', array($this, 'prefs_section'));
			$this->add_hook('preferences_save', array($this, 'prefs_save'));
		}
	}

	private function setCustomLabels()
	{
		$c = $this->rc->config->get('tb_label_custom_labels');
		if (empty($c))
		{
			// if no user specific labels, use localized strings by default
			$max_id = $this->rc->config->get('tb_max_label');
			$parmLabs = array();
			$tb_flag_text = array();
			for ($i = 0; $i <= $max_id; $i++) {
			  $lab=$this->getText('label'.$i);
			  $isParm=($lab[-1]=='1');
			  $lab=substr($lab,0,strlen($lab)-1);
			  $tb_flag_text[$i] = $lab;
			  if ($isParm) {
			    $parmLabs[$i]=strtolower($lab);
			  }
			}
			$this->rc->config->set('tb_label_custom_labels',
					       $tb_flag_text);
			$this->rc->config->set('tb_label_parm_labels',
					       $parmLabs);
		}
		// pass label strings to JS
		$this->rc->output->set_env('tb_label_custom_labels', $this->rc->config->get('tb_label_custom_labels'));
		$this->rc->output->set_env('tb_label_parm_labels', $this->rc->config->get('tb_label_parm_labels'));
	}

	// create a section for the tb-labels Settings
	public function prefs_section($args)
    {
        $args['list']['thunderbird_labels'] = array(
    	        'id' => 'thunderbird_labels',
	            'section' => rcube::Q($this->gettext('tb_label_options'))
		);

        return $args;
    }

	// display thunderbird-labels prefs in Roundcube Settings
	public function prefs_list($args)
	{
		if ($args['section'] != 'thunderbird_labels')
			return $args;

		$this->load_config();
		$dont_override = (array) $this->rc->config->get('dont_override', array());
		
		$args['blocks']['tb_label'] = array();
		$args['blocks']['tb_label']['name'] = $this->gettext('tb_label_options');
		
		$key = 'tb_label_enable';
		if (!in_array($key, $dont_override))
		{
			$input = new html_checkbox(array(
				'name' => $key,
				'id' => $key,
				'value' => 1
			));
			$content = $input->show($this->rc->config->get($key));
			$args['blocks']['tb_label']['options'][$key] = array(
				'title' => $this->gettext('tb_label_enable_option'),
				'content' => $content
			);
		}
		
		$key = 'tb_label_enable_shortcuts';
		if (!in_array($key, $dont_override))
		{
			$input = new html_checkbox(array(
				'name' => $key,
				'id' => $key,
				'value' => 1
			));
			$content = $input->show($this->rc->config->get($key));
			$args['blocks']['tb_label']['options'][$key] = array(
				'title' => $this->gettext('tb_label_enable_shortcuts_option'),
				'content' => $content
			);
		}
		
		$key = 'tb_label_style';
		if (!in_array($key, $dont_override))
		{
			$select = new html_select(array(
				'name' => $key,
				'id' => $key
			));
			$select->add(array($this->gettext('thunderbird'), $this->gettext('bullets')), array('thunderbird', 'bullets'));
			$content = $select->show($this->rc->config->get($key));
			
			$args['blocks']['tb_label']['options'][$key] = array(
				'title' => $this->gettext('tb_label_style_option'),
				'content' => $content
			);    
		}
		
		$key = 'tb_label_custom_labels';
		if (!in_array($key, $dont_override)
			&& $this->rc->config->get('tb_label_modify_labels'))
		{
			$old = $this->rc->config->get($key);
			$oldParms = $this->rc->config->get('tb_label_parm_labels');
			$max_id = $this->rc->config->get('tb_max_label');
			for($i=1; $i<=$max_id; $i++)
			{
			  $oldVal = $old[$i];
			  $oparm=$oldParms[$i]?1:0;
				$input = new html_inputfield(array(
					'name' => $key.$i,
					'id' => $key.$i,
					'type' => 'text',
					'autocomplete' => 'off',
					'value' => $oldVal));
				$parmBox = new html_checkbox(array(
					     'name' => $key.$i.'parm',
					     'id' => $key.$i.'parm',
					     'value' => 1));
				$args['blocks']['tb_label']['options'][$key.$i] = array(
					'title' => $this->gettext('tb_label_label')." ".$i,
					'content' => $input->show()."&nbsp;".$parmBox->show($oparm)
					);
			}
		}
		
		return $args;
	}

	// save prefs after modified in UI  
	public function prefs_save($args)
	{
	  #rcube::write_log('lab','saving: '.print_r($args,true));
	if ($args['section'] != 'thunderbird_labels')
		  return $args;

		
		$this->load_config();
		$dont_override = (array) $this->rc->config->get('dont_override', array());
		
		if (!in_array('tb_label_enable', $dont_override))
			$args['prefs']['tb_label_enable'] = rcube_utils::get_input_value('tb_label_enable', rcube_utils::INPUT_POST) ? true : false;
		
		if (!in_array('tb_label_enable_shortcuts', $dont_override))
		  $args['prefs']['tb_label_enable_shortcuts'] = rcube_utils::get_input_value('tb_label_enable_shortcuts', rcube_utils::INPUT_POST) ? true : false;

		if (!in_array('tb_label_style', $dont_override))  
			$args['prefs']['tb_label_style'] = rcube_utils::get_input_value('tb_label_style', rcube_utils::INPUT_POST);
	
		if (!in_array('tb_label_custom_labels', $dont_override)
			&& $this->rc->config->get('tb_label_modify_labels'))
		{
		  $max_id = $this->rc->config->get('tb_max_label');
		  $tb_lab_prefs = array();
		  $tb_parm_prefs = array();
		  $tb_lab_prefs[0] = $this->gettext('label0');
		  for ($i = 1; $i <= $max_id; $i++) {
		    $tb_lab_prefs[$i] = rcube_utils::get_input_value('tb_label_custom_labels'.$i, rcube_utils::INPUT_POST);
		    if (rcube_utils::get_input_value('tb_label_custom_labels'.$i.'parm', rcube_utils::INPUT_POST)) {
		      $tb_parm_prefs[$i] = strtolower($tb_lab_prefs[$i]);
		    }
		  }
		  $args['prefs']['tb_label_custom_labels'] = $tb_lab_prefs;
		  $args['prefs']['tb_label_parm_labels'] = $tb_parm_prefs;
		}
	
		return $args;
	}
	
	public function show_tb_label_contextmenu($args)
	  # never called?
	{
		#$this->api->output->add_label('copymessage.copyingmessage');
		$li = html::tag('li',
		  array('class' => 'submenu'),
		  '<span>'.rcube::Q($this->gettext('tb_label_contextmenu_title')).'</span>' . $this->_gen_label_submenu($args, 'tb_label_ctxm_submenu'));
		$out .= html::tag('ul', array('id' => 'tb_label_ctxm_mainmenu'), $li);
		#rcube::write_log('lab',"stblc $out");
		$this->api->output->add_footer(html::div(array('style' => 'display: none;'), $out));
	}
	
	private function _gen_label_submenu($args, $id)
	  # never called?
	{
		$out = '';
		$custom_labels = $this->rc->config->get('tb_label_custom_labels');
		$max_id = $this->rc->config->get('tb_max_label');
		for ($i = 0; $i <= $max_id; $i++)
		{
			$separator = ($i == 0)? ' separator_below' :'';
			$out .= '<li class="label'.$i.$separator.
			  ' ctxm_tb_label"><a href="#ctxm_tb_label" class="active" onclick="rcmail_ctxm_label_set('.$i.')"><span>'.
			  $i.' '.$custom_labels[$i].
			  '</span></a></li>';
		}
		$out = html::tag('ul', array('class' => 'popupmenu toolbarmenu folders', 'id' => $id), $out);
		return $out;
	}
	
	public function read_single_flags($args)
	{
		#rcube::write_log($this->name, print_r(($args['object']), true));
		if (!isset($args['object'])) {
				return;
		}
		
		if (is_array($args['object']->headers->flags))
		{
			$this->message_tb_labels = array();
			foreach ($args['object']->headers->flags as $flagname => $flagvalue)
			{
				$flag = is_numeric("$flagvalue")? $flagname:$flagvalue;// for compatibility with < 0.5.4
				$flag = strtolower($flag);
				if (preg_match('/^\$?label/', $flag))
				{
					$flag_no = preg_replace('/^\$?label/', '', $flag);
					rcube::write_log($this->name, "Single message Flag: ".$flag." Flag_no:".$flag_no);
					$this->message_tb_labels[] = (int)$flag_no;
				}
			}
		}
		# -- no return value for this hook
	}
	
	/**
	*	Writes labelnumbers for single message display
	*	Coloring of Message header table happens via Javascript
	*/
	public function color_headers($p)
	{
		#rcube::write_log($this->name, print_r($p, true));
		# -- always write array, even when empty
		$p['content'] .= '<script type="text/javascript">
		var tb_labels_for_message = ['.join(',', $this->message_tb_labels).'];
		</script>';
		return $p;
	}
	
	public function read_flags($args)
	{
		// add color information for all messages
		// dont loop over all messages if we dont have any highlights or no msgs
		if (!isset($args['messages']) or !is_array($args['messages'])) {
				return $args;
		}
		// loop over all messages and add $LabelX info to the extra_flags
		$mbox = $this->rc->output->get_env('mailbox') ?: $this->rc->storage->get_folder();
		#rcube::write_log($this->name, "mbox: $mbox");
		foreach($args['messages'] as $message)
		{
			$message->list_flags['extra_flags']['tb_labels'] = array(); # always set extra_flags, needed for javascript later!
			$message->list_flags['extra_flags']['tb_lparms'] = array();
			$keyPos = array_search($_SESSION['sort_col'],$this->rc->config->get('tb_label_parm_labels'));
			#rcube::write_log($this->name,"sort? $keyPos ".$_SESSION['sort_order']." ".print_r($this->rc->config->get('tb_label_parm_labels'),true));
			if (is_array($message->flags))
			foreach ($message->flags as $flagname => $flagvalue)
			{
				$flag = is_numeric("$flagvalue")? $flagname:$flagvalue;// for compatibility with < 0.5.4
				$flag = strtolower($flag);
				if (preg_match('/^\$?label/', $flag))
				{
					$flag_no = preg_replace('/^\$?label/', '', $flag);
					#rcube::write_log($this->name, "Flag:".$flag." Flag_no:".$flag_no);
					if ($ppos = strpos($flag_no,'_') > -1) {
					  // We have a flag parameter value...
					  $pn=(int)substr($flag_no,0,$ppos);
					  $pv=substr($flag_no,$ppos+1);
					  $message->list_flags['extra_flags']['tb_lparms'][]=array(
				         'number' => $pn,
					 'parm' => $pv);
					  if ($keyPos && $message->depth==0) {
					    $message->sortKey=array_map('intval',preg_split('#[/-]#',$pv));
					  }
					}
					else {
					  $message->list_flags['extra_flags']['tb_labels'][] = (int)$flag_no;
					}
				}
			}
		}
		if ($keyPos) {
		  $rev = $_SESSION['sort_order']=='DESC';
		  $datesSort = 
			  function($a, $b) use ($rev) {
			    if ($ak=$a->sortKey) {
			      if ($bk=$b->sortKey) {
				// each key is sd sm ed em
				if ($ak[1]<$bk[1]) return $rev?1:-1;
				if ($ak[1]>$bk[1]) return $rev?-1:1;
				// start month is the same
				if ($ak[0]<$bk[0]) return $rev?1:-1;
				if ($ak[0]>$bk[0]) return $rev?-1:1;
				// start date is the same
				if ($ak[3]<$bk[3]) return $rev?1:-1;
				if ($ak[3]>$bk[3]) return $rev?-1:1;
				// end month is the same
				if ($ak[2]<$bk[2]) return $rev?1:-1;
				if ($ak[2]>$bk[2]) return $rev?-1:1;
				// both dates are the same
				return 0;
			      }
			      else {
				return $rev?1:-1;
			      }
			    }
			    else if ($b->sortKey) {
			      return $rev?-1:1;
			    }
			    else {
			      return 0;
			    }
		  };
		  if ($this->rc->config->get('message_threading')[$mbox]) {
		    // The threading information is contained in three fields:
		    //  depth, parent_uid and uid
		    // I _think_ if you gave every depth0 message its dates as key
		    //                       every depth>0        [already sorted by date, see list.inc:71]
		    // Then first sort all the depth0 messages into a new array
		    //  then iterate over them, pull all depth1 with shared parent
		    //                          insert after parent
		    //  repeat at depth 1, etc.
		    $sm = array(); // sorted result accumulates here
		    $rm = array(); // unprocessed remnant
		    foreach ($args['messages'] as $m) {
		      if ($m->depth===0) {
			$sm[]=$m;
			  }
		      else {
			$rm[]=$m;
		      }
		    }
		    #rcube::write_log($this->name,"ts0: |".$rm[0]->depth."| |".($rm[0]->depth===0)."|");
		    usort($sm,$datesSort);
		    $depth=0;
		    $foundSome=true;
		    while ($foundSome) {
		      #rcube::write_log($this->name,"ts: $depth ".count($sm).' '.count($rm));
		      $foundSome=false;
		      $tsm = array();
		      $trm = array();
		      foreach ($sm as $m) {
			$tsm[]=$m;
			if ($m->depth===$depth) {
			  $puid = $m->uid;
			  foreach ($rm as $c) {
			    if ($c->parent_uid===$puid) {
			      $tsm[]=$c;
			      $foundSome=true;
			    }
			    else {
			      $trm[]=$c;
			    }
			  }
			  $rm = $trm;
			  $trm = array();
			}
		      }
		      $sm = $tsm;
		      $depth+=1;
		    }
		    $args['messages']=$sm;
		  }
		  else {
		    usort($args['messages'],$datesSort);
		  }
		}
		return($args);
	}
	
	// set flags in IMAP server
	function set_flags()
	{
		#rcube::write_log($this->name, "set: ".print_r($_GET, true));

		$imap = $this->rc->imap;
		$cbox = rcube_utils::get_input_value('_cur', rcube_utils::INPUT_GET);
		$mbox = rcube_utils::get_input_value('_mbox', rcube_utils::INPUT_GET);
		$toggle_label = rcube_utils::get_input_value('_toggle_label', rcube_utils::INPUT_GET);
		$flag_uids = rcube_utils::get_input_value('_flag_uids', rcube_utils::INPUT_GET);
		$flag_uids = explode(',', $flag_uids);
		$unflag_uids = rcube_utils::get_input_value('_unflag_uids', rcube_utils::INPUT_GET);
		$unflag_uids = explode(',', $unflag_uids);
		
		$imap->conn->flags = array_merge($imap->conn->flags, $this->add_tb_flags);
		
		#rcube::write_log($this->name, print_r($flag_uids, true));
		#rcube::write_log($this->name, print_r($unflag_uids, true));

		if (!is_array($unflag_uids)
			|| !is_array($flag_uids))
			return false;

		$imap->set_flag($flag_uids, $toggle_label, $mbox);
		// belt and braces
		if (count($unflag_uids)>0) {
		  $flaggedMsgs = $imap->list_flags($mbox,$unflag_uids);
		  #rcube::write_log($this->name,'oldFlags: '.print_r($flaggedMsgs,true));
		  $fullFlags=array();
		  foreach ($flaggedMsgs as $uid => $flags) {
		    foreach ($flags as $flag => $x) {
		      $pos=strpos(strtolower($flag),$toggle_label);
		      #rcube::write_log($this->name,"flag?: $flag $toggle_label $pos");
		      if ($pos>-1 && $pos<2 && !in_array($flag,$fullFlags)) {
			$fullFlags[]=$flag;
		      }
		    }
		  }
		  if (count($fullFlags)==0) {
		    #rcube::write_log('errors',"no full flags for $toggle_label in ".print_r($flaggedMsgs,true));
		    $imap->set_flag($unflag_uids, "UN$toggle_label", $mbox);
		  }
		  else {
		    foreach ($fullFlags as $flag) {
		      #rcube::write_log($this->name,"UNsetting $flag in $mbox for ".join(',',$unflag_uids));
		      $imap->set_flag($unflag_uids, "UN$flag", $mbox);
		    }
		  }
		}
		$this->api->output->send();
	}
	
	function tblabelpopup()
	{
	  $custom_labels = $this->rc->config->get('tb_label_custom_labels');
	  $max_id = $this->rc->config->get('tb_max_label');
		$out = '<div id="tblabelpopup" class="popupmenu">
			<ul class="toolbarmenu">';
		for ($i = 0; $i <= $max_id; $i++)
		{
			$separator = ($i == 0)? ' separator_below' :'';
			$out .= '<li class="label'.$i.$separator.'"><a href="#" id="tblabel'.$i.'" class="active">'.$i.' '.$custom_labels[$i].'</a></li>';
		}
		$out .= '</ul>
		</div>';
		$this->rc->output->add_gui_object('tblabelpopup_obj', 'tblabelpopup');
		#rcube::write_log('lab',"tblp $out");
		$this->rc->output->add_footer($out);
	}

	public static function whiteMessage($data) {
	  require_once 'Mail/RFC822.php';
	  $hdrs = $data['headers'];
	  $to = $hdrs['To'];
	  $cc = $hdrs['Cc'];
	  $addrs = Mail_RFC822::parseAddressList($to);
	  if ($cc) {
	    foreach (Mail_RFC822::parseAddressList($cc) as $addr) {
	      $addrs[]=$addr;
	    }
	  }
	  #rcube::write_log('lab',"cc: ".print_r($cca,true));
	  $wp = popen("/usr/local/bin/makeWhite.sh >>/var/log/white.log 2>&1",'w');
	  foreach ($addrs as $addr) {
	    fwrite($wp,$addr->mailbox.'@'.$addr->host."\n");
	  }
	  fclose($wp);
	  return $data;
	}

}