<?php
/**
 * TestLink Open Source Project - http://testlink.sourceforge.net/
 * This script is distributed under the GNU General Public License 2 or later.
 *
 * @filesource tlTestCaseFilterControl.class.php
 * @package    TestLink
 * @author     Andreas Simon
 * @copyright  2006-2013, TestLink community
 * @link       http://www.teamst.org/index.php
 * 
 *
 * This class extends tlFilterPanel for the specific use with test case tree.
 * It holds the logic to be used at GUI level to manage a common set of settings and filters for test cases.
 * 
 * This class is used from different navigator-frames (left frames with a test case tree in it)
 * with different modes for different features.
 * This is a little overview about its usage in TestLink:
 * 
 * - planTCNavigator.php/tpl use it in "plan_mode" for these features:
 *    --> assign test case execution
 *    --> update linked test case versions
 *    --> set urgent tests
 * 
 * - execNavigator.php/tpl in "execution_mode" 
 *    --> test execution
 * 
 * - planAddTCNavigator.php/tpl in "plan_add_mode"
 *    --> add/remove test cases
 * 
 * - listTestCases.php/tcTree.tpl in "edit_mode"
 *    --> edit test specification
 *    --> assign keywords
 *    --> assign requirements
 *
 * @internal revisions
 * @since 1.9.7
 *
 */

/*
 * --------------------------------------------------------
 * An important note on BUGID 3516 (request-URL too large):
 * --------------------------------------------------------
 * 
 * That problem has been solved by attaching some data (the set of active filters, settings and
 * testcase IDs to show if filtering has been done) to session.
 * 
 * Since a user can have the same feature open in multiple tabs, that alone is not enough to
 * solve this issue. When a user opens e.g. the test case execution page and sets filter options
 * there, then opens the same page in another tab, the data saved in session would also be
 * applied to this second tab although no filter options have been set there yet by the user.
 * 
 * This has now been solved by a so called form token. This token is, on first opening of a
 * navigator frame, generated by the method generate_form_token() and then stored in a member
 * variable with the name $form_token. This token will be stored in an identically named hidden 
 * input field within the HTML filter form, so it gets sent by POST to every called page.
 * It is also attached to the GET argument string returned by get_argument_string() that gets 
 * passed to multiple JavaScript functions, which are used to open nodes from the tree in the 
 * left frame in a new page in the right frame.
 * 
 * So the token is used to identify (from pages within the right frame) the data that got stored
 * for them in session by the navigator page in the left frame.
 * If the navigator page calls itself (when the user presses one of the submit buttons in the form),
 * it sends the stored token via POST to itself. So the same token can be used again to store data
 * in session, instead of generating a new token blindly on every page call no matter where the
 * call comes from. But if the user opens a new tab, the new navigator page knows this because
 * no token has been sent to it - so it generates a new one.
 * 
 * The data is saved in session in the form of an array like this example:
 * 
 * [execution_mode] => Array                              // "mode" used by navigator
 *   (
 *     [1986901204] => Array                              // form token to identify the correct tab
 *       (
 *         [filter_keywords_filter_type] => Or            // the active filters and settings,
 *         [filter_result_result] => f                    // prefixed with "filter_" and "setting_"
 *         [filter_result_method] => 3
 *         [filter_result_build] => 71
 *         [filter_assigned_user_include_unassigned] => 1
 *         [filter_testcase_name] => 
 *         [filter_toplevel_testsuite] => Array
 *           (
 *           )
 *
 *         [filter_keywords] => 
 *         [filter_priority] => 3
 *         [filter_execution_type] => 2
 *         [filter_assigned_user] => Array
 *           (
 *             [3] => 3
 *           )
 * 
 *         [filter_custom_fields] => 
 *         [setting_testplan] => 4990
 *         [setting_build] => 71
 *         [setting_platform] => 
 *         [setting_refresh_tree_on_action] => 1
 *         [testcases_to_show] => Array                   // The internal IDs of the test cases which
 *           (                                            // where not filtered out by user's choices.
 *             [0] => 1852                                // This was the part which earlier caused
 *             [1] => 60                                  // the error because of the too long URL.
 *             [2] => 2039
 *             [3] => 2033
 *             [4] => 2065
 *             [5] => 2159
 *             [6] => 3733
 *           )
 *
 *         [timestamp] => 1277727920                      // additional means to check age of session data
 *       )
 *   )
 * 
 * The access to this data can be done in the following way from the right frame page:
 * 
 * $form_token = isset($_REQUEST['form_token']) ? $_REQUEST['form_token'] : 0;
 * $mode = 'execution_mode';
 * $session_data = isset($_SESSION[$mode]) && isset($_SESSION[$mode][$form_token])
 *                 ? $_SESSION[$mode][$form_token] : null;
 * 
 * The variable $session_data then holds the array with all the active filters,
 * settings and filtered test case IDs in it, or is null if nothing has been stored yet
 * in the session.
 * 
 * But now we have another problem:
 * There can be one array for each mode in the session. In each of these arrays is a set of
 * further arrays with the form tokens as keys and the filter information in it.
 * If a user now opens the same page more than once in a row (by switching back and forth 
 * between features or by using the same feature in multiple tabs) there can be more and more
 * arrays with filter information in this set of arrays.
 * 
 * Because of this, an additional timestamp is written into each of these information arrays.
 * On each storage process that writes information into the session triggered by a call 
 * to a navigator page, the timestamp gets refreshed if an old token has been reused or
 * it gets created with the creation of a new data array.
 * 
 * This timestamp can be used to delete old arrays with information that is not needed anymore.
 * Since we have no means to otherwise detect the case that a user has closed the tab 
 * and doesn't need this information in the session anymore, we have to determine the age of 
 * those arrays with the timestamp and delete everything that is older than a certain given 
 * threshold. This is done by the method delete_old_session_data() which is automatically called
 * from the contstructor of this class. It checks the age of all the saved 
 * arrays inside the array for the active mode and then deletes everything that's older than
 * the given threshold. This threshold can be passed as a parameter to the method, otherwise a
 * default value of one hour is used.
 * 
 * If a user logs out of TestLink, of course all this data in the session is deleted,
 * no matter if the one hour threshold has passed or not.
 * ------------------------------------------------------------------------------------------------
 */

/**
 * This class extends tlFilterPanel for the specific use with the testcase tree.
 * It contains logic to be used at GUI level to manage
 * a common set of settings and filters for testcases.
 *
 * @author Andreas Simon
 * @package TestLink
 * @uses testplan
 * @uses exec_cf_mgr
 * @uses tlPlatform
 * @uses testcase
 */
class tlTestCaseFilterControl extends tlFilterControl {

  /**
   * Testcase manager object.
   * Initialized not in constructor, only on first use to save resources.
   * @var testcase
   */
  private $tc_mgr = null;
  
  /**
   * Platform manager object.
   * Initialized not in constructor, only on first use to save resources.
   * @var tlPlatform
   */
  private $platform_mgr = null;
  
  /**
   * Custom field manager object.
   * Initialized not in constructor, only on first use to save resources.
   * @var exec_cf_mgr
   */
  private $cfield_mgr = null;
  
  /**
   * Testplan manager object.
   * Initialized not in constructor, only on first use to save resources.
   * @var testplan
   */
  private $testplan_mgr = null;
  
  /**
   * This array contains all possible filters.
   * It is used as a helper to iterate over all the filters in some loops.
   * It also sets options how and from where to load the parameters with
   * input fetching functions in init_args()-method.
   * Its keys are the names of the settings (class constants are used),
   * its values are the arrays for the input parser.
   * @var array
   */
  private $all_filters = array('filter_tc_id' => array("POST", tlInputParameter::STRING_N),
                               'filter_testcase_name' => array("POST", tlInputParameter::STRING_N),
                               'filter_toplevel_testsuite' => array("POST", tlInputParameter::STRING_N),
                               'filter_keywords' => array("POST", tlInputParameter::ARRAY_INT),
                               // 'filter_active_inactive' => array("POST", tlInputParameter::INT_N),
                               'filter_importance' => array("POST", tlInputParameter::INT_N),
                               'filter_priority' => array("POST", tlInputParameter::INT_N),
                               'filter_execution_type' => array("POST", tlInputParameter::INT_N),
                               'filter_assigned_user' => array("POST", tlInputParameter::ARRAY_INT),
                               'filter_custom_fields' => array("POST", tlInputParameter::ARRAY_STRING_N),
                               'filter_result' => null); // result: no info here, divided into more parts

  /**
   * This array is used as an additional security measure. It maps all available
   * filters to the mode in which they can be used. If a user tries to
   * enable filters in config.inc.php which are not defined inside this array,
   * this will be simply ignored instead of trying to initialize the filter
   * no matter wether it has been implemented or not.
   * The keys inside this array are the modes defined above as class constants.
   * So it can be checked if a filter is available in a given mode without
   * relying only on the config parameter.
   * @var array
   */
  private $mode_filter_mapping = array('edit_mode' => array('filter_tc_id',
                                                            'filter_testcase_name',
                                                            'filter_toplevel_testsuite',
                                                            'filter_keywords',
                                                            // 'filter_active_inactive',
                                                            'filter_importance',
                                                            'filter_execution_type',
                                                            'filter_custom_fields'),
                                       'execution_mode' => array('filter_tc_id',
                                                                 'filter_testcase_name',
                                                                 'filter_toplevel_testsuite',
                                                                 'filter_keywords',
                                                                 'filter_priority',
                                                                 'filter_execution_type',
                                                                 'filter_assigned_user',
                                                                 'filter_custom_fields',
                                                                 'filter_result'),
                                       'plan_mode' => array('filter_tc_id',
                                                            'filter_testcase_name',
                                                            'filter_toplevel_testsuite',
                                                            'filter_keywords',
                                                            'filter_priority',
                                                            'filter_execution_type',
                                                          // enabled user filter when assigning testcases
                                                          'filter_assigned_user',
                                                            'filter_custom_fields',
                                                            'filter_result'),
                                       'plan_add_mode' => array('filter_tc_id',
                                                                'filter_testcase_name',
                                                                'filter_toplevel_testsuite',
                                                                'filter_keywords',
                                                                // 'filter_active_inactive',
                                                                'filter_importance',
                                                                'filter_execution_type',
                                                                'filter_custom_fields'));

  /**
   * This array contains all possible settings. It is used as a helper
   * to later iterate over all possibilities in loops.
   * Its keys are the names of the settings, its values the arrays for the input parser.
   * @var array
   */
  private $all_settings = array('setting_testplan' => array("POST", tlInputParameter::INT_N),
                                'setting_build' => array("POST", tlInputParameter::INT_N),
                                'setting_platform' => array("POST", tlInputParameter::INT_N),
                                'setting_refresh_tree_on_action' => array("POST", tlInputParameter::CB_BOOL));

  /**
   * This array is used to map the modes to their available settings.
   * @var array
   */
   
  // TICKET 5176: Possibility to filter by Platform - changes in 'plan_mode'
  // 
  private $mode_setting_mapping = array('edit_mode' => array('setting_refresh_tree_on_action'),
                                        'execution_mode' => array('setting_testplan',
                                                                  'setting_build',
                                                                  'setting_platform',
                                                                  'setting_refresh_tree_on_action'),
                                        'plan_mode' => array('setting_testplan',
                                                             'setting_build',
                                                             'setting_platform',
                                                             'setting_refresh_tree_on_action'),
                                        'plan_add_mode' => array('setting_testplan',
                                                                 'setting_refresh_tree_on_action'));

  /**
   * The mode used. Depending on the feature for which this class will be instantiated.
   * This mode defines which filter configuration will be loaded from config.inc.php
   * and therefore which filters will be loaded and used for the templates.
   * Value has to be one of the class constants for mode, default is edit mode.
   * @var string
   */
  private $mode = 'edit_mode';


  /**
   * Options to be used accordin to $this->mode, to build tree
   * @var array
   */
  private $treeOpt = array();


  /**
   * The token that will be used to identify the relationship between left frame
   * (with navigator) and right frame (which displays execution of test case e.g.) in session.
   * @var string
   */
  public $form_token = null;
  
  
  
  
  /**
   *
   * @param database $dbHandler
   * @param string $mode can be edit_mode/execution_mode/plan_mode/plan_add_mode, depending on usage
   */
  public function __construct(&$dbHandler, $mode = 'edit_mode') {

    // set mode to define further actions before calling parent constructor
    $this->mode = array_key_exists($mode,$this->mode_filter_mapping) ? $mode : 'edit_mode';

    // Call to constructor of parent class tlFilterControl.
    // This already loads configuration and user input
    // and does all the remaining necessary method calls,
    // so no further method call is required here for initialization.
    parent::__construct($dbHandler);

    $this->initTreeOptions($this->mode);
    
    // delete any filter settings that may be left from previous calls in session
    $this->delete_own_session_data();
    $this->delete_old_session_data();
    
    $this->save_session_data();
  }

  /**
   * 
   * 
   */
  public function __destruct() 
  {
    parent::__destruct(); //destroys testproject manager
    
    unset($this->tc_mgr);
    unset($this->testplan_mgr);
    unset($this->platform_mgr);
    unset($this->cfield_mgr);
  }

  /**
   * Reads the configuration from the configuration file specific for test cases,
   * additionally to those parts of the config which were already loaded by parent class.
   * @return bool
   */
  protected function read_config() 
  {

    // some configuration reading already done in parent class
    parent::read_config();

    // load configuration for active mode only
    $this->configuration = config_get('tree_filter_cfg')->testcases->{$this->mode};

    // load also exec config - it is not only needed in exec mode
    $this->configuration->exec_cfg = config_get('exec_cfg');

    // some additional testcase configuration
    $this->configuration->tc_cfg = config_get('testcase_cfg');
    
    // is choice of advanced filter mode enabled?
      if (isset($this->configuration->advanced_filter_mode_choice) && 
        $this->configuration->advanced_filter_mode_choice == ENABLED) 
      {
        $this->filter_mode_choice_enabled = true;
      } 
      else 
      {
        $this->filter_mode_choice_enabled = false;
      }
    
    return tl::OK;
  } // end of method

  /**
   * Does what init_args() usually does in all scripts: Reads the user input
   * from request ($_GET and $_POST). Later configuration,
   * settings and filters get modified according to that user input.
   */
  protected function init_args() 
  {
    
    // some common user input is already read in parent class
    parent::init_args();

    // add settings and filters to parameter info array for request parsers
    $params = array();
    foreach ($this->all_settings as $name => $info) 
    {
      if (is_array($info)) 
      {
        $params[$name] = $info;
      }
    }
    foreach ($this->all_filters as $name => $info) 
    {
      if (is_array($info)) 
      {
        $params[$name] = $info;
      }
    }
    I_PARAMS($params, $this->args);

    $type = 'filter_keywords_filter_type';
    $this->args->{$type} = (isset($_REQUEST[$type])) ? trim($_REQUEST[$type]) : 'Or';

    $extra_keys = array('filter_result_result','filter_result_method','filter_result_build');

    foreach ($extra_keys as $ek) 
    {
      $this->args->{$ek} = (isset($_REQUEST[$ek])) ? $_REQUEST[$ek] : null;
    }

    $this->args->{'filter_assigned_user_include_unassigned'} = 
      isset($_REQUEST['filter_assigned_user_include_unassigned']) ? 1 : 0;

    // got session token sent by form or do we have to generate a new one?
    $sent_token = null;
    $this->args->form_token = null;
    if (isset($_REQUEST['form_token'])) 
    {
      $sent_token = $_REQUEST['form_token'];
    }
    if (!is_null($sent_token) && isset($_SESSION[$this->mode][$sent_token])) 
    {
      // sent token is valid
      $this->form_token = $sent_token;
      $this->args->form_token = $sent_token;
    } 
    else 
    {
      $this->generate_form_token();
    }
    
    // "feature" is needed for plan and edit modes
    $this->args->feature = isset($_REQUEST['feature']) ? trim($_REQUEST['feature']) : null;
    $doLog = false;
    switch ($this->mode) 
    {
      case 'plan_mode':
        switch($this->args->feature) 
        {
          case 'planUpdateTC':
          case 'test_urgency':
          case 'tc_exec_assignment':
          break;
        
          default:
            $doLog = true;
          break;
        }
      break;
      
      case 'edit_mode':
        switch($this->args->feature) 
        {
          case 'edit_tc':
          case 'keywordsAssign':
          case 'assignReqs':
          break;
        
          default:
            $doLog = true;
          break;
        }
      break;
    }
    if($doLog)
    {
      tLog('Mode:' . $this->mode . ' - Wrong or missing GET argument: feature', 'ERROR');
      exit();
    }
  } // end of method

  /**
   * Initializes all settings.
   * Iterates through all available settings and adds an array to $this->settings
   * for the active ones, sets the rest to false so this can be
   * checked from templates and elsewhere.
   * Then calls the initializing method for each still active setting.
   */
  protected function init_settings() 
  {
    $at_least_one_active = false;

    foreach ($this->all_settings as $name => $info) 
    {
      $init_method = "init_$name";
      if (in_array($name, $this->mode_setting_mapping[$this->mode]) && 
        method_exists($this, $init_method)) 
      {
        // is valid, configured, exists and therefore can be used, so initialize this setting
        $this->$init_method();
        $at_least_one_active = true;
      } 
      else 
      {
        // is not needed, simply deactivate it by setting it to false in main array
        $this->settings[$name] = false;
      }
    }
    
    // special situations 
    // the build setting is in plan mode only needed for one feature
    if ($this->mode == 'plan_mode' && $this->args->feature != 'tc_exec_assignment') 
    {
      $this->settings['setting_build'] = false;
      $this->settings['setting_platform'] = false;
    }
  
    // TICKET 5176: Possibility to filter by Platform - changes in 'plan_mode'  
    //if ($this->mode == 'plan_mode' && $this->args->feature != 'tc_exec_assignment') 
    //{
    //  $this->settings['setting_platform'] = false;
    //}
    
    
    // if at least one active setting is left to display, switch settings panel on
    if ($at_least_one_active) 
    {
      $this->display_settings = true;
    }
  }

  /**
   * Initialize all filters. (called by parent::__construct())
   * I'm double checking here with loaded configuration _and_ additional array
   * $mode_filter_mapping, set according to defined mode, because this can avoid errors in case
   * when users try to enable a filter in config that doesn't exist for a mode.
   * Effect: Only existing and implemented filters can be activated in config file.
   */
  protected function init_filters() 
  {
    // In resulting data structure, all values have to be defined (at least initialized),
    // no matter wether they are wanted for filtering or not.
    $dummy = array('filter_keywords_filter_type','filter_result_result',
                   'filter_result_method','filter_result_build',
                   'filter_assigned_user_include_unassigned');
    
    foreach ($dummy as $filtername) 
    {
      $this->active_filters[$filtername] = null;
    }
    

    // iterate through all filters and activate the needed ones
    $this->display_filters = false;

    foreach ($this->all_filters as $name => $info) 
    {
      $init_method = "init_$name";
      if (in_array($name, $this->mode_filter_mapping[$this->mode]) &&
        method_exists($this, $init_method) && $this->configuration->{$name} == ENABLED &&
        $this->configuration->show_filters == ENABLED) 
      {
        $this->$init_method();

        // there is at least one filter item to display => switch panel on
        $this->display_filters = true;
      } 
      else 
      {
        // is not needed, deactivate filter by setting it to false in main array
        // and of course also in active filters array
        $this->filters[$name] = false;
        $this->active_filters[$name] = null;
      }
    }

    // special situation: the assigned user filter is in plan mode only needed for one feature
    if ($this->mode == 'plan_mode' && $this->args->feature != 'tc_exec_assignment') 
    {
      $this->settings['filter_assigned_user'] = false;
    }

    // add the important settings to active filter array
    foreach ($this->all_settings as $name => $info) 
    {
      if ($this->settings[$name]) 
      {
        $this->active_filters[$name] = $this->settings[$name]['selected'];
      } 
      else 
      {
        $this->active_filters[$name] = null;
      }
    }
  } // end of method

  /**
   * This method returns an object or array, containing all selections chosen
   * by the user for filtering.
   * 
   * @return mixed $value Return value is either an array or stdClass object,
   * depending on active mode. It contains all filter values selected by the user.
   */
  protected function get_active_filters() 
  {
    static $value = null; // serves as a kind of cache if method is called more than once
        
    // convert array to stcClass if needed
    if (!$value) 
    {
      switch ($this->mode) 
      {
        case 'execution_mode':
        case 'plan_mode':
          // these features are generating an exec tree,
          // they need the filters as a stdClass object
          $value = (object)$this->active_filters;
        break;
        
        default:
          // otherwise simply return the array as-is
          $value = $this->active_filters;
        break;
      }
    }
    
    return $value;
  } // end of method

  /**
   * 
   * 
   */
  public function set_testcases_to_show($value = null) 
  {
    // update active_filters
    if (!is_null($value)) {
      $this->active_filters['testcases_to_show'] = $value;
    }
    
    // Since a new filter in active_filters has been set from outside class after
    // saving of session data has already happened in constructor, 
    // we explicitly update data in session after this change here.
    $this->save_session_data();
  }
  
  /**
   * Active filters will be saved to $_SESSION. 
   * If there already is data for the active mode and token, it will be overwritten.
   * This data will be read from pages in the right frame.
   * This solves the problems with too long URLs.
   * See issue 3516 in Mantis for a little bit more information/explanation.
   * The therefore caused new problem that would arise now if
   * a user uses the same feature simultaneously in multiple browser tabs
   * is solved be the additional measure of using a form token.
   * 
   * @author Andreas Simon
   * @return $tl::OK
   */
  public function save_session_data() {   
    if (!isset($_SESSION[$this->mode]) || is_null($_SESSION[$this->mode]) || !is_array($_SESSION[$this->mode])) {
      $_SESSION[$this->mode] = array();
    }
    
    $_SESSION[$this->mode][$this->form_token] = $this->active_filters;
    $_SESSION[$this->mode][$this->form_token]['timestamp'] = time();
    
    return tl::OK;
  }
  
  /**
   * Old filter data for active mode will be deleted from $_SESSION.
   * It happens automatically after a session has expired and a user therefore
   * has to log in again, but here we can configure an additional time limit
   * only for this special filter part in session data.
   * 
   * @author Andreas Simon
   * @param int $token_validity_duration data older than given timespan will be deleted
   */
  public function delete_old_session_data($token_validity_duration = 0) {
    // TODO this duration could maybe also be configured in config/const.inc.php
    
    // how long shall the data remain in session before it will be deleted?
    if (!is_numeric($token_validity_duration) || $token_validity_duration <= 0) {
      $token_validity_duration = 60 * 60 * 1; // one hour as default
    }
    
    // delete all tokens from session that are older than given age
    if (isset($_SESSION[$this->mode]) && is_array($_SESSION[$this->mode])) {
      foreach ($_SESSION[$this->mode] as $token => $data) {
        if ($data['timestamp'] < (time() - $token_validity_duration)) {
          // too old, delete!
          unset($_SESSION[$this->mode][$token]);
        }
      }
    }
  }
  
  /**
   * 
   * 
   */
  public function delete_own_session_data() 
  {
    if (isset($_SESSION[$this->mode]) && isset($_SESSION[$this->mode][$this->form_token])) 
    {
      unset($_SESSION[$this->mode][$this->form_token]);
    }
  }
  
  /**
   * Generates a form token, which will be used to identify the relationship
   * between left navigator-frame with its settings and right frame.
   */
  protected function generate_form_token() 
  {
    // Notice: I am just generating an integer here for the token.
    // Since this is not any security relevant stuff like a password hash or similar,
    // but only a means to separate multiple tabs a single user opens, this should suffice.
    // If we should some day decide that an integer is not enough,
    // we just have to change this one method and everything will still work.
    
    $min = 1234567890; // not magic, just some large number so the tokens don't get too short 
    $max = mt_getrandmax();
    $token = 0;
    
    // generate new tokens until we find one that doesn't exist yet
    do {
      $token = mt_rand($min, $max);
    } while (isset($_SESSION[$this->mode][$token]));
    
    $this->form_token = $token;
  }
  
  /**
   * Active filters will be formatted as a GET-argument string.
   * 
   * @return string $string the formatted string with active filters
   */
  public function get_argument_string() 
  {
    static $string = null; // cache for repeated calls of this method
    
    if (!$string) 
    {
      $string = '';

      // important: the token with which the page in right frame can access data in session
      $string .= '&form_token=' . $this->form_token;

      $key2loop = array('setting_build','setting_platform');
      foreach($key2loop as $kiwi)
      {
        if($this->settings[$kiwi]) 
        {
          $string .= "&{$kiwi}={$this->settings[$kiwi]['selected']}";
        }
        
      }     
      if ($this->active_filters['filter_priority'] > 0) 
      {
        $string .= '&filter_priority=' . $this->active_filters['filter_priority'];
      }
    
      
      $keyword_list = null;
      if (is_array($this->active_filters['filter_keywords'])) 
      {
        $keyword_list = implode(',', $this->active_filters['filter_keywords']);
      } 
      else if ($this->active_filters['filter_keywords']) 
      {
        $keyword_list = $this->active_filters['filter_keywords'];
      }     
      
      
      // Need to undertand why for other filters that also are array
      // we have choosen to serialize, and here not.
      // may be to avoid more refactoring
      if ($keyword_list) 
      {
        $string .= '&filter_keywords=' . $keyword_list . 
                   '&filter_keywords_filter_type=' . 
                   $this->active_filters['filter_keywords_filter_type'];
      }
      
      // Using serialization      
      if ($this->active_filters['filter_assigned_user']) 
      {
        $string .= '&filter_assigned_user='. serialize($this->active_filters['filter_assigned_user']) .
                   '&filter_assigned_user_include_unassigned=' . 
                   ($this->active_filters['filter_assigned_user_include_unassigned'] ? '1' : '0');
      }
      
      if ($this->active_filters['filter_result_result']) 
      {
        $string .= '&filter_result_result=' . serialize($this->active_filters['filter_result_result']) .
                   '&filter_result_method=' . $this->active_filters['filter_result_method'] .
                   '&filter_result_build=' .  $this->active_filters['filter_result_build'];
      }
    }
    
    return $string;
  }
  
  /**
   * Build the tree menu for generation of JavaScript test case tree.
   * Depending on mode and user's selections in user interface, 
   * either a completely filtered tree will be build and returned,
   * or only the minimal necessary data to "lazy load" 
   * the objects in the tree by later Ajax calls.
   * No return value - all variables will be stored in gui object
   * which is passed by reference.
   * 
   * @author Andreas Simon
   * @param object $gui Reference to GUI object (data will be written to it)
   */
  public function build_tree_menu(&$gui) 
  {
    $tree_menu = null;
    $filters = $this->get_active_filters();
    $loader = '';
    $children = "[]";
    $cookie_prefix = '';

    // by default, disable drag and drop, then later enable if needed
    $drag_and_drop = new stdClass();
    $drag_and_drop->enabled = false;
    $drag_and_drop->BackEndUrl = '';
    $drag_and_drop->useBeforeMoveNode = FALSE;
        
    if (!$this->testproject_mgr) 
    {
      $this->testproject_mgr = new testproject($this->db);
    }
    
    $tc_prefix = $this->testproject_mgr->getTestCasePrefix($this->args->testproject_id);

    switch ($this->mode) 
    {
      
      case 'plan_mode':
        // No lazy loading here.
        $opt_etree = $this->treeOpt[$this->mode];
        $filters->show_testsuite_contents = 1;
        switch($this->args->feature)
        {
          case 'test_urgency':
            $filters->hide_testcases = 1; // ??
            $opt_etree->allow_empty_build = 1;
            $opt_etree->hideTestCases = 1;
            $opt_etree->getTreeMethod = 'getLinkedForTesterAssignmentTree';
          break;
          
          case 'tc_exec_assignment':
            $filters->hide_testcases = 0;
            $opt_etree->hideTestCases = 0;
            $opt_etree->allow_empty_build = 0;
            $opt_etree->getTreeMethod = 'getLinkedForTesterAssignmentTree';
            
            // TICKET 4905: Test Case Tester Assignment - filters dont work properly 
            //        for 'Assigned to' Field
            // This way we are GOING TO IGNORE SETTING BUILD
            $opt_etree->buildIDKeyMap = 'filter_result_build';
            
          break;
          
          case 'planUpdateTC':
            $filters->hide_testcases = 0;
            $opt_etree->hideTestCases = 0;
            $opt_etree->allow_empty_build = 1;
            $opt_etree->getTreeMethod = 'getLinkedForTesterAssignmentTree';
          break;
          
        }
        
        list($tree_menu, $testcases_to_show) = testPlanTree($this->db,$gui->menuUrl,
                                                            $this->args->testproject_id,
                                                            $this->args->testproject_name,
                                                            $this->args->testplan_id,
                                                            $this->args->testplan_name,
                                                            $filters,$opt_etree);
        
        $this->set_testcases_to_show($testcases_to_show);
        
        $root_node = $tree_menu->rootnode;
        $children = $tree_menu->menustring ? $tree_menu->menustring : "[]";
        
        // improved cookiePrefix - all trees in plan mode show test cases
        // assigned to a specified test plan -> store state for each feature and each project
        //
        // usage of wrong values in $this->args->xyz for cookiePrefix
        // instead of correct values in $filters->setting_xyz
        $cookie_prefix = $this->args->feature . "_tplan_id_" . $filters->setting_testplan ."_";
      break;
      
      case 'edit_mode':
        if ($gui->tree_drag_and_drop_enabled[$this->args->feature]) 
        {
          $drag_and_drop->enabled = true;
          $drag_and_drop->BackEndUrl = $this->args->basehref . 
                                       'lib/ajax/dragdroptprojectnodes.php';
          $drag_and_drop->useBeforeMoveNode = true;
        }
        // improved cookiePrefix - 
        // all trees in edit mode show test cases of whole test project 
        // -> store state for each feature and each project
        $cookie_prefix = $this->args->feature . "_tproject_id_" . $this->args->testproject_id ."_";
        
        if ($this->do_filtering) 
        {
          
          // TICKET 4353: added active/inactive filter
          $ignore_inactive_testcases = DO_NOT_FILTER_INACTIVE_TESTCASES;
          $ignore_active_testcases = DO_NOT_FILTER_INACTIVE_TESTCASES;
          if ($filters['filter_active_inactive'] == IGNORE_INACTIVE_TESTCASES)
          {
              $ignore_inactive_testcases = IGNORE_INACTIVE_TESTCASES;
          }
          if ($filters['filter_active_inactive'] == IGNORE_ACTIVE_TESTCASES)
          {
              $ignore_active_testcases = IGNORE_ACTIVE_TESTCASES;
          }
                    
          $options = array('forPrinting' => NOT_FOR_PRINTING,
                           'hideTestCases' => SHOW_TESTCASES,
                           'tc_action_enabled' => DO_ON_TESTCASE_CLICK,
                           'exclude_branches' => null,
                           'ignore_inactive_testcases' => $ignore_inactive_testcases,
                           'ignore_active_testcases' => $ignore_active_testcases);
            
          $tree_menu = generateTestSpecTree($this->db, $this->args->testproject_id,
                                            $this->args->testproject_name,
                                            $gui->menuUrl, $filters, $options);
          
          $root_node = $tree_menu->rootnode;
          $children = $tree_menu->menustring ? $tree_menu->menustring : "[]";
        } 
        else 
        {
          $loader = $this->args->basehref . 'lib/ajax/gettprojectnodes.php?' .
                    "root_node={$this->args->testproject_id}&" .
                    "tcprefix=" . urlencode($tc_prefix .
                    $this->configuration->tc_cfg->glue_character);
          
          $tcase_qty = $this->testproject_mgr->count_testcases($this->args->testproject_id);
          
          $root_node = new stdClass();
          $root_node->href = "javascript:EP({$this->args->testproject_id})";
          $root_node->id = $this->args->testproject_id;
          $root_node->name = $this->args->testproject_name . " ($tcase_qty)";
          $root_node->testlink_node_type='testproject';
        }
      break;
      
      case 'plan_add_mode':
        // improved cookiePrefix - 
        // tree in plan_add_mode is only used for add/removed test cases features 
        // and shows all test cases defined within test project, 
        // but as test cases are added to a specified test plan -> store state for each test plan
        // 
        // usage of wrong values in $this->args->xyz for cookiePrefix instead of correct 
        // values in $filters->setting_xyz
        $cookie_prefix = "add_remove_tc_tplan_id_{$filters['setting_testplan']}_";
        
        if ($this->do_filtering)
        {
          // TICKET 4496: added active/inactive filter
          // Will be refactored in future versions
          // $ignore_inactive_testcases = DO_NOT_FILTER_INACTIVE_TESTCASES;
          // $ignore_active_testcases = DO_NOT_FILTER_INACTIVE_TESTCASES;
          // if ($filters['filter_active_inactive'] == IGNORE_INACTIVE_TESTCASES)
          // {
          //   $ignore_inactive_testcases = IGNORE_INACTIVE_TESTCASES;
          // }
          // if ($filters['filter_active_inactive'] == IGNORE_ACTIVE_TESTCASES)
          // {
          //   $ignore_active_testcases = IGNORE_ACTIVE_TESTCASES;
          // }
                    
          $options = array('forPrinting' => NOT_FOR_PRINTING,
                           'hideTestCases' => HIDE_TESTCASES,
                           'tc_action_enabled' => ACTION_TESTCASE_DISABLE,
                           'viewType' => 'testSpecTreeForTestPlan',
                           'ignore_inactive_testcases' => $ignore_inactive_testcases,
                           'ignore_active_testcases' => $ignore_active_testcases);
      
          $tree_menu = generateTestSpecTree($this->db,
                                            $this->args->testproject_id,
                                            $this->args->testproject_name,
                                            $gui->menuUrl,$filters,$options);
          
          $root_node = $tree_menu->rootnode;
            $children = $tree_menu->menustring ? $tree_menu->menustring : "[]";
        } 
        else 
        {
          $loader = $this->args->basehref . 'lib/ajax/gettprojectnodes.php?' .
                              "root_node={$this->args->testproject_id}&show_tcases=0";
        
          $root_node = new stdClass();
          $root_node->href = "javascript:EP({$this->args->testproject_id})";
          $root_node->id = $this->args->testproject_id;
          $root_node->name = $this->args->testproject_name;
          $root_node->testlink_node_type = 'testproject';
        }
      break;
      
      case 'execution_mode':
      default:
        // No lazy loading here.
        // Filtering is always done in execution mode, no matter if user enters data or not,
        // since the user should usually never see the whole tree here.
        $filters->hide_testcases = false;
        $filters->show_testsuite_contents = $this->configuration->exec_cfg->show_testsuite_contents;
    
    
        $exec_cfg = &$this->configuration->exec_cfg;
        $opt_etree = new stdClass();
        $opt_etree->useCounters = $exec_cfg->enable_tree_testcase_counters;
        
        $opt_etree->useColours = new stdClass();
        $opt_etree->useColours->testcases = $exec_cfg->enable_tree_testcases_colouring;
        $opt_etree->useColours->counters =  $exec_cfg->enable_tree_counters_colouring;
        $opt_etree->testcases_colouring_by_selected_build = $exec_cfg->testcases_colouring_by_selected_build; 
        
      
        //$chronos[] = $tstart = microtime(true);
        //echo '<br>' . basename(__FILE__) . '::' . __LINE__ . '::Start!!!' . current($chronos);
        //reset($chronos);  
        list($tree_menu, $testcases_to_show) = execTree($this->db,$gui->menuUrl,
                                                        $this->args->testproject_id,
                                                        $this->args->testproject_name,
                                                        $this->args->testplan_id,
                                                        $this->args->testplan_name,
                                                        $filters,$opt_etree);
        
        //$chronos[] = microtime(true); $tnow = end($chronos); $tprev = prev($chronos);
        //$t_elapsed = number_format( $tnow - $tprev, 4);
        //echo '<br> ' . basename(__FILE__) . ' Elapsed (sec):' . $t_elapsed;

        $this->set_testcases_to_show($testcases_to_show);
        
        $root_node = $tree_menu->rootnode;
        $children = $tree_menu->menustring ? $tree_menu->menustring : "[]";
        
        //
        // improved cookiePrefix - 
        // tree on test execution shows test cases depending on test plan, platform and build. 
        // Because test plan is implicitily given with build -> store state for each (platform-build)
        // combination
        //
        // Usage of wrong values in $this->args->xyz for cookiePrefix
        // instead of correct values in $filters->setting_xyz
        //
        $cookie_prefix = 'test_exec_build_id_' . $filters->setting_build . '_';
        if (isset($filters->setting_platform)) 
        {
          $cookie_prefix .= 'platform_id_' . $filters->setting_platform . '_';
        }
      break;
    }
    
    $gui->tree = $tree_menu;
    
    $gui->ajaxTree = new stdClass();
    $gui->ajaxTree->loader = $loader;
    $gui->ajaxTree->root_node = $root_node;
    $gui->ajaxTree->children = $children;
    $gui->ajaxTree->cookiePrefix = $cookie_prefix;
    $gui->ajaxTree->dragDrop = $drag_and_drop;
  } // end of method
  
  /**
   * 
   * 
   */
  private function init_setting_refresh_tree_on_action() 
  {

    $key = 'setting_refresh_tree_on_action';
    $hidden_key = 'hidden_setting_refresh_tree_on_action';
    $selection = 0;

    $this->settings[$key] = array();
    $this->settings[$key][$hidden_key] = false;

    // look where we can find the setting - POST, SESSION, config?
    if (isset($this->args->{$key})) {
      $selection = 1;
    } else if (isset($this->args->{$hidden_key})) {
      $selection = 0;
    } else if (isset($_SESSION[$key])) {
      $selection = $_SESSION[$key];
    } else {
      $spec_cfg = config_get('spec_cfg');
      $selection = ($spec_cfg->automatic_tree_refresh > 0) ? 1 : 0;
    }
    
    $this->settings[$key]['selected'] = $selection;
    $this->settings[$key][$hidden_key] = $selection;
    $_SESSION[$key] = $selection;   
  } // end of method



  /**
   * 
   * 
   */
  private function init_setting_build() 
  {

    $key = 'setting_build';
    if (is_null($this->testplan_mgr)) 
    {
      $this->testplan_mgr = new testplan($this->db);
    }

    $tplan_id = $this->settings['setting_testplan']['selected'];

    // when in plan mode (assigning execution), we want all builds,
    // otherwise only those which are active and open
    $active = ($this->mode == 'plan_mode') ? null : testplan::GET_ACTIVE_BUILD;
    $open = ($this->mode == 'plan_mode') ? null : testplan::GET_OPEN_BUILD;
    
    $this->settings[$key]['items'] = $this->testplan_mgr->get_builds_for_html_options($tplan_id, $active, $open);
    $tplan_builds = array_keys((array)$this->settings[$key]['items']);

    // According to mode, we need different labels for this selector on GUI
    $label = ($this->mode == 'plan_mode') ? 'assign_build' : 'exec_build';
    $this->settings[$key]['label'] = lang_get($label);
    
    // if no build has been chosen by user, select newest build by default
    $newest_build_id = $this->testplan_mgr->get_max_build_id($tplan_id, $active, $open);

    $session_key = $tplan_id . '_stored_setting_build';
    $session_selection = isset($_SESSION[$session_key]) ? $_SESSION[$session_key] : null;

    $this->args->{$key} = $this->args->{$key} > 0 ? $this->args->{$key} : $session_selection;

    if (!$this->args->$key) 
    {
      $this->args->$key = $newest_build_id;
    }
    
    // only take build ID into account if it really is a build from this testplan
    $this->settings[$key]['selected'] = (in_array($this->args->$key, (array)$tplan_builds)) ? 
                                        $this->args->$key : $newest_build_id;

    // still no build selected? take first one from selection.
    if (!$this->settings[$key]['selected'] && sizeof($this->settings[$key]['items'])) 
    {
      $this->settings[$key]['selected'] = end($tplan_builds);
    }

    $_SESSION[$session_key] = $this->settings[$key]['selected'];
  } // end of method


  /**
   * 
   * 
   */
  private function init_setting_testplan() 
  {

    if (is_null($this->testplan_mgr)) {
      $this->testplan_mgr = new testplan($this->db);
    }
    
    $key = 'setting_testplan';
    $testplans = $this->user->getAccessibleTestPlans($this->db, $this->args->testproject_id);

    if (isset($_SESSION['testplanID']) && $_SESSION['testplanID'] != $this->args->{$key}) {
      // testplan was changed, we need to reset all filters
      // --> they were chosen for another testplan, not this one!
      $this->args->reset_filters = true;

      // check if user is allowed to set chosen testplan before changing
      foreach ($testplans as $plan) {
        if ($plan['id'] == $this->args->{$key}) {
          setSessionTestPlan($plan);
        }
      }
    }

    // now load info from session
    $info = $this->testplan_mgr->get_by_id($_SESSION['testplanID']);
    $this->args->testplan_name = $info['name'];
    $this->args->testplan_id = $info['id'];
    $this->args->{$key} = $info['id'];
    $this->settings[$key]['selected'] = $info['id'];

    // Now get all selectable testplans for the user to display.
    // For execution, don't take testplans into selection which have no (active/open) builds!
    // For plan add mode, add every plan no matter if he has builds or not.
    foreach ($testplans as $plan) 
    {
      // List also test plans without builds for "plan_mode"
      $add_plan = $this->mode == 'plan_add_mode' || $this->mode == 'plan_mode';
      if (!$add_plan) 
      {
        $builds = $this->testplan_mgr->get_builds($plan['id'],
                                                  testplan::GET_ACTIVE_BUILD,
                                                  testplan::GET_OPEN_BUILD);
        $add_plan =  (is_array($builds) && count($builds));
      }
      
      if ($add_plan) 
      {
        $this->settings[$key]['items'][$plan['id']] = $plan['name'];
      }
    }
  } // end of method

  /**
   * 
   * 
   * 20120825 - franciscom - TICKET 5176: Possibility to filter by Platform
   *               according mode we need to add [Any] option
   *
   */
  private function init_setting_platform() 
  {
    if (!$this->platform_mgr) 
    {
      $this->platform_mgr = new tlPlatform($this->db);
    }
    $key = 'setting_platform';

    $this->settings[$key] = array('items' => null, 'selected' => $this->args->{$key});
    $testplan_id = $this->settings['setting_testplan']['selected'];

    $checkSelected = true;
    $platformSet = $this->platform_mgr->getLinkedToTestplanAsMap($testplan_id);
    $this->settings[$key]['items'] = $platformSet;
    switch($this->mode)
    {
      case 'plan_mode':
        if( !is_null($platformSet) )
        {
          $this->settings[$key]['items'] = array(0 => $this->option_strings['any']);
          $this->settings[$key]['items'] += $platformSet;
          $checkSelected = false;
        }
      break;
    }
    
    // BUGID 3726
    $session_key = $testplan_id . '_stored_setting_platform';
    $session_selection = isset($_SESSION[$session_key]) ? $_SESSION[$session_key] : null;
    
    // if no platform has been selected when entering the feature the first time set it
    // according to preselected value on gui
    // TICKET 
    $this->args->{$key} = !is_null($this->args->{$key}) ? $this->args->{$key} : $session_selection;
    if( $checkSelected && !$this->settings[$key]['selected']) 
    {
      $this->settings[$key]['selected'] = $session_selection;
    }

    if (!isset($this->settings[$key]['items']) || !is_array($this->settings[$key]['items'])) {
      $this->settings[$key] = false;
    } 
    else if (isset($this->settings[$key]['items']) && is_array($this->settings[$key]['items']) && 
           is_null($this->settings[$key]['selected'])) 
      {
      // platforms exist, but none has been selected yet, so select first one
      $this->settings[$key]['selected'] = key($this->settings[$key]['items']);
    }
    $_SESSION[$session_key] = $this->settings[$key]['selected'];
  } // end of method

  /**
   * 
   * 
   */
  private function init_filter_tc_id() {
    $key = 'filter_tc_id';
    $selection = $this->args->{$key};
    $internal_id = null;
    
    if (!$this->testproject_mgr) {
      $this->testproject_mgr = new testproject($this->db);
    }
    if (!$this->tc_mgr) {
      $this->tc_mgr = new testcase($this->db);
    }
    
    $tc_prefix = $this->testproject_mgr->getTestCasePrefix($this->args->testproject_id);
    $tc_prefix .= $this->configuration->tc_cfg->glue_character;
    
    if (!$selection || $selection == $tc_prefix || $this->args->reset_filters) {
      $selection = null;
    } else {
      $this->do_filtering = true;
      // we got the external ID here when filtering, but need the internal one
      $internal_id = $this->tc_mgr->getInternalID($selection);
    }
    
    $this->filters[$key] = array('selected' => $selection ? $selection : $tc_prefix);
    $this->active_filters[$key] = $internal_id;
  } // end of method
  
  /**
   * 
   * 
   */
  private function init_filter_testcase_name() {
    $key = 'filter_testcase_name';
    $selection = $this->args->{$key};
    
    if (!$selection || $this->args->reset_filters) {
      $selection = null;
    } else {
      $this->do_filtering = true;
    }
    
    $this->filters[$key] = array('selected' => $selection);
    $this->active_filters[$key] = $selection;
  } // end of method


  /**
   * 
   * 
   */
  private function init_filter_toplevel_testsuite() 
  {
    if (!$this->testproject_mgr) 
    {
      $this->testproject_mgr = new testproject($this->db);
    }
    $key = 'filter_toplevel_testsuite';
    $first_level_suites = $this->testproject_mgr->get_first_level_test_suites($this->args->testproject_id,
                                                                              'smarty_html_options');
    
    $selection = $this->args->{$key};
    if (!$selection || $this->args->reset_filters) 
    {
      $selection = null;
    } 
    else 
    {
      $this->do_filtering = true;
    }
    
    // this filter should only be visible if there are any top level testsuites
    $this->filters[$key] = null;
    if ($first_level_suites) 
    {     
      $this->filters[$key] = array('items' => array(0 => ''),
                                   'selected' => $selection,
                                   'exclude_branches' => array());
    
      foreach ($first_level_suites as $suite_id => $suite_name) 
      {
        $this->filters[$key]['items'][$suite_id] = $suite_name;
        if ($selection && $suite_id != $selection) 
        {
          $this->filters[$key]['exclude_branches'][$suite_id] = 'exclude_me';
        }
      }
      
      // Important: This is the only case in which active_filters contains the items
      // which have to be deleted from tree, instead of the other way around.
      $this->active_filters[$key] = $this->filters[$key]['exclude_branches'];
    } 
    else 
    {
      $this->active_filters[$key] = null;
    }   
  } // end of method

  /**
   * 
   * 
   */
  private function init_filter_keywords() {
    $key = 'filter_keywords';
    $type = 'filter_keywords_filter_type';
    $this->filters[$key] = false;
    $keywords = null;

    switch ($this->mode) 
    {
      case 'edit_mode':
      case 'plan_add_mode':   // BUGID 3822
        // we need the keywords for the whole testproject
        if (!$this->testproject_mgr) {
          $this->testproject_mgr = new testproject($this->db);
        }
        $keywords = $this->testproject_mgr->get_keywords_map($this->args->testproject_id);
        break;

      default:
        // otherwise (not in edit mode), we want only keywords assigned to testplan
        if (!$this->testplan_mgr) {
          $this->testplan_mgr = new testplan($this->db);
        }
        $tplan_id = $this->settings['setting_testplan']['selected'];
        $keywords = $this->testplan_mgr->get_keywords_map($tplan_id, ' ORDER BY keyword ');
        break;
    }

    $selection = $this->args->{$key};
    $type_selection = $this->args->{$type};
    
    // are there any keywords?
    if (!is_null($keywords) && count($keywords)) {
      $this->filters[$key] = array();

      if (!$selection || !$type_selection || $this->args->reset_filters) {
        // default values for filter reset
        $selection = null;
        $type_selection = 'Or';
      } else {
        $this->do_filtering = true;
      }
      
      // data for the keywords themselves
      $this->filters[$key]['items'] = array($this->option_strings['any']) + $keywords;
      $this->filters[$key]['selected'] = $selection;
      $this->filters[$key]['size'] = min(count($this->filters[$key]['items']),
                                         self::ADVANCED_FILTER_ITEM_QUANTITY);

      // additional data for the filter type (logical and/or)
      $this->filters[$key][$type] = array();
      $this->filters[$key][$type]['items'] = array('Or' => lang_get('logical_or'),
                                                           'And' => lang_get('logical_and'));
      $this->filters[$key][$type]['selected'] = $type_selection;
    }
    
    // set the active value to filter
    // delete keyword filter if "any" (0) is part of the selection - regardless of filter mode
    if (is_array($this->filters[$key]['selected'])
    && in_array(0, $this->filters[$key]['selected'])) {
      $this->active_filters[$key] = null;
    } else {
      $this->active_filters[$key] = $this->filters[$key]['selected'];
    }
    $this->active_filters[$type] = $selection ? $type_selection : null;
  } // end of method

    // TICKET 4353: added active/inactive filter
    private function init_filter_active_inactive() {
        $key = 'filter_active_inactive';
        
        $items = array(DO_NOT_FILTER_INACTIVE_TESTCASES => $this->option_strings['any'],
                       IGNORE_INACTIVE_TESTCASES => lang_get('show_only_active_testcases'),
                       IGNORE_ACTIVE_TESTCASES => lang_get('show_only_inactive_testcases'));
        
        $selection = $this->args->{$key};
        
        if (!$selection || $this->args->reset_filters) {
            $selection = null;
        } else {
            $this->do_filtering = true;
        }

        $this->filters[$key] = array('items' => $items, 'selected' => $selection);
        $this->active_filters[$key] = $selection;
    }
    
    // TICKET 4217: added importance filter
    private function init_filter_importance() {
        // This is a special case of filter: the menu items don't get initialized here,
        // they are available as a global smarty variable. So the only thing to be managed
        // here is the selection by user.

        $key = 'filter_importance';
        $this->active_filters[$key] = null;
        $this->filters[$key] = false;

        // show this filter only if test priority management is enabled
        if (!$this->testproject_mgr) {
            $this->testproject_mgr = new testproject($this->db);
        }
        $tp_info = $this->testproject_mgr->get_by_id($this->args->testproject_id);
        $enabled = $tp_info['opt']->testPriorityEnabled;

        if ($enabled) {
            // default value and filter reset
            $selection = $this->args->{$key};
            if (!$selection || $this->args->reset_filters) {
                $selection = null;
            } else {
                $this->do_filtering = true;
            }

            $this->filters[$key] = array('selected' => $selection);
            $this->active_filters[$key] = $selection;
        }
    }
    
  private function init_filter_priority() {
    // This is a special case of filter: the menu items don't get initialized here,
    // they are available as a global smarty variable. So the only thing to be managed
    // here is the selection by user.
    $key = 'filter_priority';
    
    if (!$this->testproject_mgr) {
      $this->testproject_mgr = new testproject($this->db);
    }
    
    $tp_info = $this->testproject_mgr->get_by_id($this->args->testproject_id);
    $enabled = $tp_info['opt']->testPriorityEnabled;
        
    $this->active_filters[$key] = null;
    $this->filters[$key] = false;
    
    // BUGID 3910: show filter only if test priority management is enabled
    if ($enabled) {
      // default value and filter reset
      $selection = $this->args->{$key};
      if (!$selection || $this->args->reset_filters) {
        $selection = null;
      } else {
        $this->do_filtering = true;
      }
  
      $this->filters[$key] = array('selected' => $selection);
      $this->active_filters[$key] = $selection;
    }   
  } // end of method

  private function init_filter_execution_type() {
    if (!$this->tc_mgr) {
      $this->tc_mgr = new testcase($this->db);
    }
    $key = 'filter_execution_type';

    $selection = $this->args->{$key};
    // handle filter reset
    if (!$selection || $this->args->reset_filters) {
      $selection = null;
    } else {
      $this->do_filtering = true;
    }
    
    $this->filters[$key] = array('items' => array(), 'selected' => $selection);

    // load available execution types
    $this->filters[$key]['items'] = $this->tc_mgr->get_execution_types();
    // add "any" string to these types at index 0 as default selection
    $this->filters[$key]['items'] = array(0 => $this->option_strings['any'])
                                          + $this->filters[$key]['items'];
    
    $this->active_filters[$key] = $selection;
  } // end of method

  private function init_filter_assigned_user() {
    if (!$this->testproject_mgr) {
      $this->testproject_mgr = new testproject($this->db);
    }

    $key = 'filter_assigned_user';
    $unassigned_key = 'filter_assigned_user_include_unassigned';
    $tplan_id = $this->settings['setting_testplan']['selected'];

    // set selection to default (any), only change if value is sent by user and reset is not requested
    $selection = $this->args->{$key};
    if (!$selection || $this->args->reset_filters) {
      $selection = null;
    } else {
      $this->do_filtering = true;
    }

    $tproject_info = $this->testproject_mgr->get_by_id($this->args->testproject_id);

    $all_testers = getTestersForHtmlOptions($this->db, $tplan_id, $tproject_info, null,
                                          array(TL_USER_ANYBODY => $this->option_strings['any'],
                                                TL_USER_NOBODY => $this->option_strings['none'],
                                                TL_USER_SOMEBODY => $this->option_strings['somebody']),
                                          'any');
    $visible_testers = $all_testers;
    
    // in execution mode the rights of the user have to be regarded
    if ($this->mode == 'execution_mode') 
    {
      $role = $this->user->getEffectiveRole($this->db, $this->args->testproject_id, $tplan_id);
      
      $simple_tester_roles = array_flip($this->configuration->exec_cfg->simple_tester_roles);
      
      // check the user's rights to see what he may do
      $right_to_execute = $role->hasRight('testplan_execute');
      $right_to_manage = $role->hasRight('testplan_planning');
      
      $simple = false;
      if (isset($simple_tester_roles[$role->dbID]) || ($right_to_execute && !$right_to_manage)) {
        // user is only simple tester and may not see/execute everything
        $simple = true;
      }

      $view_mode = $simple ? $this->configuration->exec_cfg->view_mode->tester : 'all';
      
      if ($view_mode != 'all') {
        $visible_testers = (array)$this->user->getDisplayName();
        $selection = (array)$this->user->dbID;
      }

      // re-enable option "user_filter_default"
      if (!$selection && $this->configuration->exec_cfg->user_filter_default == 'logged_user') {
        $selection = (array)$this->user->dbID;
      }
    }
    
    $this->filters[$key] = array('items' => $visible_testers,
                                 'selected' => $selection,
                                 $unassigned_key => $this->args->{$unassigned_key});
    
    // which value shall be passed to tree generation class?
    
    if ((is_array($selection) && in_array(TL_USER_ANYBODY, $selection))
    || ($selection == TL_USER_ANYBODY)) {
      // delete user assignment filter if "any user" is part of the selection
      $this->active_filters[$key] = null;
      $this->active_filters[$unassigned_key] = 0;
    }
    
    if (is_array($selection)) {
      // get keys of the array as values
      $this->active_filters[$key] = array_flip($selection);
      foreach ($this->active_filters[$key] as $user_key => $user_value) {
        $this->active_filters[$key][$user_key] = $user_key;
      }
      $this->active_filters[$unassigned_key] = $this->filters[$key][$unassigned_key];
    }
  } // end of method

  private function init_filter_custom_fields() 
  {
    $key = 'filter_custom_fields';
    $no_warning = true;

    $locale = (isset($_SESSION['locale'])) ? $_SESSION['locale'] : 'en_GB';
    $localesDateFormat = config_get('locales_date_format');
    $date_format = str_replace('%', '', $localesDateFormat[$locale]);
    
    $collapsed = isset($_SESSION['cf_filter_collapsed']) ? $_SESSION['cf_filter_collapsed'] : 0;
    $collapsed = isset($_REQUEST['btn_toggle_cf']) ? !$collapsed : $collapsed;
    $_SESSION['cf_filter_collapsed'] = $collapsed;  
    $btn_label = $collapsed ? lang_get('btn_show_cf') : lang_get('btn_hide_cf');
    
    if (!$this->cfield_mgr) {
      $this->cfield_mgr = new cfield_mgr($this->db, $this->args->testproject_id);
    }
    
    $cfields = $this->cfield_mgr->get_linked_cfields_at_design($this->args->testproject_id, 1, null, 'testcase');
    $cf_prefix = $this->cfield_mgr->name_prefix;
    $cf_html_code = "";
    $selection = array();
    
    $this->filters[$key] = false;
    $this->active_filters[$key] = null;

    if (!is_null($cfields)) 
    {
      // display and compute only when custom fields are in use
      foreach ($cfields as $cf_id => $cf) 
      {
        // has a value been selected?
        $id = $cf['id'];
        $type = $cf['type'];
        $verbose_type = trim($this->cfield_mgr->custom_field_types[$type]);
        $cf_input_name = "{$cf_prefix}{$type}_{$id}";
        
        
        // BUGID 3716
        // custom fields on test spec did not retain value after apply
        // IMPORTANT/CRITIC issue:  trim() on array makes array = null !!!
        //
        $value = isset($_REQUEST[$cf_input_name]) ? $_REQUEST[$cf_input_name] : null;

        if ($this->args->reset_filters) 
        {
          $value = null;
        }
        else
        {
          // BUGID 3884: added filtering for datetime custom fields
          if ($verbose_type == 'datetime') {
            // if cf is a date field, convert the three given values to unixtime format
            if (isset($_REQUEST[$cf_input_name . '_input']) && $_REQUEST[$cf_input_name . '_input'] != ''
            && isset($_REQUEST[$cf_input_name . '_hour']) && $_REQUEST[$cf_input_name . '_hour'] != ''
            && isset($_REQUEST[$cf_input_name . '_minute']) && $_REQUEST[$cf_input_name . '_minute'] != ''
            && isset($_REQUEST[$cf_input_name . '_second']) && $_REQUEST[$cf_input_name . '_second'] != '') {
              $date = $_REQUEST[$cf_input_name . '_input'];
              $hour = $_REQUEST[$cf_input_name . '_hour'];
              $minute = $_REQUEST[$cf_input_name . '_minute'];
              $second = $_REQUEST[$cf_input_name . '_second'];
                  
              $date_array = split_localized_date($date, $date_format);
              $value = mktime($hour, $minute, $second, $date_array['month'], $date_array['day'], $date_array['year']);
            }
          }
                  
          if ($verbose_type == 'date') {
            // if cf is a date field, convert the three given values to unixtime format
            // BUGID 3883: only set values if different from 0
            if (isset($_REQUEST[$cf_input_name . '_input']) && $_REQUEST[$cf_input_name . '_input'] != '') {
              $date = $_REQUEST[$cf_input_name . '_input'];           
              $date_array = split_localized_date($date, $date_format);
              $value = mktime(0, 0, 0, $date_array['month'], $date_array['day'], $date_array['year']);
            }
          }
        }


        
        $value2display = $value;
        if (!is_null($value2display) && is_array($value2display))
        {
          $value2display = implode("|", $value2display);
        }
        else 
        {
          $value = trim($value);
          $value2display = $value;
        }
        $cf['value'] = $value2display;

        if (!is_null($value) && $value !='') 
        {
          $this->do_filtering = true;
          $selection[$id] = $value;
        }

        $label = str_replace(TL_LOCALIZE_TAG, '', lang_get($cf['label'], null, $no_warning));

        $cf_size = self::CF_INPUT_SIZE;
        // set special size for list inputs
        if ($verbose_type == 'list' || $verbose_type == 'multiselection list') {
          $cf_size = 3;
        }
        
        // don't show textarea inputs here, they are too large for filterpanel
        if ($verbose_type != 'text area') {
          $cf_html_code .= '<tr class="cfRow"><td>' . htmlspecialchars($label) . '</td><td>' .
                           $this->cfield_mgr->string_custom_field_input($cf, '', $cf_size, true) .
                           '</td></tr>';
        }
      }

      // BUGID 3566: show/hide CF
      $this->filters[$key] = array('items' => $cf_html_code, 
                                   'btn_label' => $btn_label, 
                                   'collapsed' => $collapsed);
      $this->active_filters[$key] = count($selection) ? $selection : null;
    }
  } // end of method


  private function init_filter_result() 
  {
    $result_key = 'filter_result_result';
    $method_key = 'filter_result_method';
    $build_key = 'filter_result_build';
    
    if (is_null($this->testplan_mgr)) 
    {
      $this->testplan_mgr = new testplan($this->db);
    }
    $tplan_id = $this->settings['setting_testplan']['selected'];

    $this->configuration->results = config_get('results');

    // determine, which config to load and use for filter methods - depends on mode!
    $cfg = ($this->mode == 'execution_mode') ? 
           'execution_filter_methods' : 'execution_assignment_filter_methods';
    $this->configuration->filter_methods = config_get($cfg);

    //
    // CRITIC - Differences bewteen this configuration and
    // (file const.inc.php)
    // $tlCfg->execution_filter_methods['default_type'] 
    // $tlCfg->execution_assignment_filter_methods['default_type']
    // 
    // Will create issues: you will see an string on HTML SELECT, but code
    // returned on submit will not code for string you are seeing.!!!! 
    //
    // determine which filter method shall be selected by the JS function in template,
    // when only one build is selectable by the user
    $js_key_to_select = 0;
    if ($this->mode == 'execution_mode') 
    {
      $js_key_to_select = $this->configuration->filter_methods['status_code']['current_build'];
    } 
    else if ($this->mode == 'plan_mode') 
    {
      $js_key_to_select = $this->configuration->filter_methods['status_code']['specific_build'];
    }
    
    // values selected by user
    $result_selection = $this->args->$result_key;
    $method_selection = $this->args->$method_key;
    $build_selection = $this->args->$build_key;

    // default values
    $default_filter_method = $this->configuration->filter_methods['default_type'];
    $any_result_key = $this->configuration->results['status_code']['all'];
    $newest_build_id = $this->testplan_mgr->get_max_build_id($tplan_id, testplan::GET_ACTIVE_BUILD);

    if (is_null($method_selection)) 
    {
      $method_selection = $default_filter_method;
    }

    if (is_null($result_selection) || $this->args->reset_filters) 
    // if ($this->args->reset_filters)
    {
      // no selection yet or filter reset requested
      $result_selection = $any_result_key;
      $method_selection = $default_filter_method;
      $build_selection = $newest_build_id;
    } 
    else 
    {
      $this->do_filtering = true;
    }
    
    // init array structure
    $key = 'filter_result';
    $this->filters[$key] = array($result_key => array('items' => null,
                                                      'selected' => $result_selection),
                                 $method_key => array('items' => array(),
                                                      'selected' => $method_selection,
                                                      'js_selection' => $js_key_to_select),
                                 $build_key => array('items' => null,
                                                     'selected' => $build_selection));

    // init menu for result selection by function from exec.inc.php
    $this->filters[$key][$result_key]['items'] = createResultsMenu();
    $this->filters[$key][$result_key]['items'][$any_result_key] = $this->option_strings['any'];

    // init menu for filter method selection
    foreach ($this->configuration->filter_methods['status_code'] as $statusname => $statusshortcut) 
    {
      $code = $this->configuration->filter_methods['status_code'][$statusname];
      $this->filters[$key][$method_key]['items'][$code] =
        lang_get($this->configuration->filter_methods['status_label'][$statusname]);
    }
    
    // init menu for build selection
    $this->filters[$key][$build_key]['items'] =
      $this->testplan_mgr->get_builds_for_html_options($tplan_id, testplan::GET_ACTIVE_BUILD);
    
    // if "any" is selected, nullify the active filters
    if ((is_array($result_selection) && in_array($any_result_key, $result_selection)) || 
        $result_selection == $any_result_key) {
      $this->active_filters[$result_key] = null;
      $this->active_filters[$method_key] = null;
      $this->active_filters[$build_key] = null;
      $this->filters[$key][$result_key]['selected'] = $any_result_key;
    
      // TICKET 0005627
      // $this->filters[$key][$method_key]['selected'] = $default_filter_method;
      // $this->filters[$key][$build_key]['selected'] = $newest_build_id;
    } 
    else {
      $this->active_filters[$result_key] = $result_selection;
      $this->active_filters[$method_key] = $method_selection;
      $this->active_filters[$build_key] = $build_selection;
    }
  } // end of method
  
  
  
  /**
   *
   * @used-by __construct
   */
  private function initTreeOptions()
  {
    $this->treeOpt['plan_mode'] = new stdClass();
    $this->treeOpt['plan_mode']->useCounters = CREATE_TC_STATUS_COUNTERS_OFF;
    $this->treeOpt['plan_mode']->useColours = COLOR_BY_TC_STATUS_OFF;
    $this->treeOpt['plan_mode']->testcases_colouring_by_selected_build = DISABLED;
    $this->treeOpt['plan_mode']->absolute_last_execution = true;  // hmm  probably useless
    
  }
  
} // end of class tlTestCaseFilterControl
?>