PageRenderTime 64ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/functions/tlTestCaseFilterControl.class.php

https://bitbucket.org/pfernandez/testlink1.9.6
PHP | 1511 lines | 831 code | 226 blank | 454 comment | 166 complexity | b99f357b3f875cfc13c477c6ea6be25a MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1, GPL-3.0

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /**
  3. * TestLink Open Source Project - http://testlink.sourceforge.net/
  4. * This script is distributed under the GNU General Public License 2 or later.
  5. *
  6. * @package TestLink
  7. * @author Andreas Simon
  8. * @copyright 2006-2010, TestLink community
  9. * @version CVS: $Id: tlTestCaseFilterControl.class.php,v 1.18 2010/08/30 09:14:59 asimon83 Exp $
  10. * @link http://www.teamst.org/index.php
  11. * @filesource http://testlink.cvs.sourceforge.net/viewvc/testlink/testlink/lib/functions/tlTestCaseFilterControl.class.php?view=markup
  12. *
  13. * This class extends tlFilterPanel for the specific use with test case tree.
  14. * It holds the logic to be used at GUI level to manage a common set of settings and filters for test cases.
  15. *
  16. * This class is used from different navigator-frames (left frames with a test case tree in it)
  17. * with different modes for different features.
  18. * This is a little overview about its usage in TestLink:
  19. *
  20. * - planTCNavigator.php/tpl use it in "plan_mode" for these features:
  21. * --> assign test case execution
  22. * --> update linked test case versions
  23. * --> set urgent tests
  24. *
  25. * - execNavigator.php/tpl in "execution_mode"
  26. * --> test execution
  27. *
  28. * - planAddTCNavigator.php/tpl in "plan_add_mode"
  29. * --> add/remove test cases
  30. *
  31. * - listTestCases.php/tcTree.tpl in "edit_mode"
  32. * --> edit test specification
  33. * --> assign keywords
  34. * --> assign requirements
  35. *
  36. * @internal Revisions:
  37. *
  38. * 20100830 - asimon - BUGID 3726: store user's selection of build and platform
  39. * 20100811 - asimon - BUGID 3566: show/hide CF
  40. * 20100810 - asimon - added TC ID filter for Test Cases
  41. * 20100807 - franciscom - BUGID 3660
  42. * 20100727 - asimon - BUGID 3630 - syntax error in get_argument_string()
  43. * 20100716 - asimon - BUGID 3406 - changes on init_settings() and $mode_setting_mapping
  44. * 20100713 - asimon - fixed Drag&Drop error caused by init_filter_custom_fields()
  45. * 20100702 - asimon - fixed error in init_setting_testplan()
  46. * 20100701 - asimon - BUGID 3414 - additional work in init_filter_custom_fields()
  47. * 20100628 - asimon - removal of constants
  48. * 20100624 - asimon - CVS merge (experimental branch to HEAD)
  49. * 20100503 - asimon - start of implementation of filter panel class hierarchy
  50. * to simplify/generalize filter panel handling
  51. * for test cases and requirements
  52. */
  53. /*
  54. * --------------------------------------------------------
  55. * An important note on BUGID 3516 (request-URL too large):
  56. * --------------------------------------------------------
  57. *
  58. * That problem has been solved by attaching some data (the set of active filters, settings and
  59. * testcase IDs to show if filtering has been done) to session.
  60. *
  61. * Since a user can have the same feature open in multiple tabs, that alone is not enough to
  62. * solve this issue. When a user opens e.g. the test case execution page and sets filter options
  63. * there, then opens the same page in another tab, the data saved in session would also be
  64. * applied to this second tab although no filter options have been set there yet by the user.
  65. *
  66. * This has now been solved by a so called form token. This token is, on first opening of a
  67. * navigator frame, generated by the method generate_form_token() and then stored in a member
  68. * variable with the name $form_token. This token will be stored in an identically named hidden
  69. * input field within the HTML filter form, so it gets sent by POST to every called page.
  70. * It is also attached to the GET argument string returned by get_argument_string() that gets
  71. * passed to multiple JavaScript functions, which are used to open nodes from the tree in the
  72. * left frame in a new page in the right frame.
  73. *
  74. * So the token is used to identify (from pages within the right frame) the data that got stored
  75. * for them in session by the navigator page in the left frame.
  76. * If the navigator page calls itself (when the user presses one of the submit buttons in the form),
  77. * it sends the stored token via POST to itself. So the same token can be used again to store data
  78. * in session, instead of generating a new token blindly on every page call no matter where the
  79. * call comes from. But if the user opens a new tab, the new navigator page knows this because
  80. * no token has been sent to it - so it generates a new one.
  81. *
  82. * The data is saved in session in the form of an array like this example:
  83. *
  84. * [execution_mode] => Array // "mode" used by navigator
  85. * (
  86. * [1986901204] => Array // form token to identify the correct tab
  87. * (
  88. * [filter_keywords_filter_type] => Or // the active filters and settings,
  89. * [filter_result_result] => f // prefixed with "filter_" and "setting_"
  90. * [filter_result_method] => 3
  91. * [filter_result_build] => 71
  92. * [filter_assigned_user_include_unassigned] => 1
  93. * [filter_testcase_name] =>
  94. * [filter_toplevel_testsuite] => Array
  95. * (
  96. * )
  97. *
  98. * [filter_keywords] =>
  99. * [filter_priority] => 3
  100. * [filter_execution_type] => 2
  101. * [filter_assigned_user] => Array
  102. * (
  103. * [3] => 3
  104. * )
  105. *
  106. * [filter_custom_fields] =>
  107. * [setting_testplan] => 4990
  108. * [setting_build] => 71
  109. * [setting_platform] =>
  110. * [setting_refresh_tree_on_action] => 1
  111. * [testcases_to_show] => Array // The internal IDs of the test cases which
  112. * ( // where not filtered out by user's choices.
  113. * [0] => 1852 // This was the part which earlier caused
  114. * [1] => 60 // the error because of the too long URL.
  115. * [2] => 2039
  116. * [3] => 2033
  117. * [4] => 2065
  118. * [5] => 2159
  119. * [6] => 3733
  120. * )
  121. *
  122. * [timestamp] => 1277727920 // additional means to check age of session data
  123. * )
  124. * )
  125. *
  126. * The access to this data can be done in the following way from the right frame page:
  127. *
  128. * $form_token = isset($_REQUEST['form_token']) ? $_REQUEST['form_token'] : 0;
  129. * $mode = 'execution_mode';
  130. * $session_data = isset($_SESSION[$mode]) && isset($_SESSION[$mode][$form_token])
  131. * ? $_SESSION[$mode][$form_token] : null;
  132. *
  133. * The variable $session_data then holds the array with all the active filters,
  134. * settings and filtered test case IDs in it, or is null if nothing has been stored yet
  135. * in the session.
  136. *
  137. * But now we have another problem:
  138. * There can be one array for each mode in the session. In each of these arrays is a set of
  139. * further arrays with the form tokens as keys and the filter information in it.
  140. * If a user now opens the same page more than once in a row (by switching back and forth
  141. * between features or by using the same feature in multiple tabs) there can be more and more
  142. * arrays with filter information in this set of arrays.
  143. *
  144. * Because of this, an additional timestamp is written into each of these information arrays.
  145. * On each storage process that writes information into the session triggered by a call
  146. * to a navigator page, the timestamp gets refreshed if an old token has been reused or
  147. * it gets created with the creation of a new data array.
  148. *
  149. * This timestamp can be used to delete old arrays with information that is not needed anymore.
  150. * Since we have no means to otherwise detect the case that a user has closed the tab
  151. * and doesn't need this information in the session anymore, we have to determine the age of
  152. * those arrays with the timestamp and delete everything that is older than a certain given
  153. * threshold. This is done by the method delete_old_session_data() which is automatically called
  154. * from the contstructor of this class. It checks the age of all the saved
  155. * arrays inside the array for the active mode and then deletes everything that's older than
  156. * the given threshold. This threshold can be passed as a parameter to the method, otherwise a
  157. * default value of one hour is used.
  158. *
  159. * If a user logs out of TestLink, of course all this data in the session is deleted,
  160. * no matter if the one hour threshold has passed or not.
  161. * ------------------------------------------------------------------------------------------------
  162. */
  163. /**
  164. * This class extends tlFilterPanel for the specific use with the testcase tree.
  165. * It contains logic to be used at GUI level to manage
  166. * a common set of settings and filters for testcases.
  167. *
  168. * @author Andreas Simon
  169. * @package TestLink
  170. * @uses testplan
  171. * @uses exec_cf_mgr
  172. * @uses tlPlatform
  173. * @uses testcase
  174. */
  175. class tlTestCaseFilterControl extends tlFilterControl {
  176. /**
  177. * Testcase manager object.
  178. * Initialized not in constructor, only on first use to save resources.
  179. * @var testcase
  180. */
  181. private $tc_mgr = null;
  182. /**
  183. * Platform manager object.
  184. * Initialized not in constructor, only on first use to save resources.
  185. * @var tlPlatform
  186. */
  187. private $platform_mgr = null;
  188. /**
  189. * Custom field manager object.
  190. * Initialized not in constructor, only on first use to save resources.
  191. * @var exec_cf_mgr
  192. */
  193. private $exec_cf_mgr = null;
  194. /**
  195. * Testplan manager object.
  196. * Initialized not in constructor, only on first use to save resources.
  197. * @var testplan
  198. */
  199. private $testplan_mgr = null;
  200. /**
  201. * This array contains all possible filters.
  202. * It is used as a helper to iterate over all the filters in some loops.
  203. * It also sets options how and from where to load the parameters with
  204. * input fetching functions in init_args()-method.
  205. * Its keys are the names of the settings (class constants are used),
  206. * its values are the arrays for the input parser.
  207. * @var array
  208. */
  209. private $all_filters = array('filter_tc_id' => array("POST", tlInputParameter::STRING_N),
  210. 'filter_testcase_name' => array("POST", tlInputParameter::STRING_N),
  211. 'filter_toplevel_testsuite' => array("POST", tlInputParameter::STRING_N),
  212. 'filter_keywords' => array("POST", tlInputParameter::ARRAY_INT),
  213. 'filter_priority' => array("POST", tlInputParameter::INT_N),
  214. 'filter_execution_type' => array("POST", tlInputParameter::INT_N),
  215. 'filter_assigned_user' => array("POST", tlInputParameter::ARRAY_INT),
  216. 'filter_custom_fields' => array("POST", tlInputParameter::ARRAY_STRING_N),
  217. 'filter_result' => null); // result: no info here, divided into more parts
  218. /**
  219. * This array is used as an additional security measure. It maps all available
  220. * filters to the mode in which they can be used. If a user tries to
  221. * enable filters in config.inc.php which are not defined inside this array,
  222. * this will be simply ignored instead of trying to initialize the filter
  223. * no matter wether it has been implemented or not.
  224. * The keys inside this array are the modes defined above as class constants.
  225. * So it can be checked if a filter is available in a given mode without
  226. * relying only on the config parameter.
  227. * @var array
  228. */
  229. private $mode_filter_mapping = array('edit_mode' => array('filter_tc_id',
  230. 'filter_testcase_name',
  231. 'filter_toplevel_testsuite',
  232. 'filter_keywords',
  233. 'filter_execution_type',
  234. 'filter_custom_fields'),
  235. 'execution_mode' => array('filter_tc_id',
  236. 'filter_testcase_name',
  237. 'filter_toplevel_testsuite',
  238. 'filter_keywords',
  239. 'filter_priority',
  240. 'filter_execution_type',
  241. 'filter_assigned_user',
  242. 'filter_custom_fields',
  243. 'filter_result'),
  244. 'plan_mode' => array('filter_tc_id',
  245. 'filter_testcase_name',
  246. 'filter_toplevel_testsuite',
  247. 'filter_keywords',
  248. 'filter_priority',
  249. 'filter_execution_type',
  250. 'filter_custom_fields',
  251. 'filter_result'),
  252. 'plan_add_mode' => array('filter_tc_id',
  253. 'filter_testcase_name',
  254. 'filter_toplevel_testsuite',
  255. 'filter_keywords',
  256. 'filter_priority',
  257. 'filter_execution_type',
  258. 'filter_custom_fields'));
  259. /**
  260. * This array contains all possible settings. It is used as a helper
  261. * to later iterate over all possibilities in loops.
  262. * Its keys are the names of the settings, its values the arrays for the input parser.
  263. * @var array
  264. */
  265. private $all_settings = array('setting_testplan' => array("POST", tlInputParameter::INT_N),
  266. 'setting_build' => array("POST", tlInputParameter::INT_N),
  267. 'setting_platform' => array("POST", tlInputParameter::INT_N),
  268. 'setting_refresh_tree_on_action' => array("POST", tlInputParameter::CB_BOOL));
  269. /**
  270. * This array is used to map the modes to their available settings.
  271. * @var array
  272. */
  273. private $mode_setting_mapping = array('edit_mode' => array('setting_refresh_tree_on_action'),
  274. 'execution_mode' => array('setting_testplan',
  275. 'setting_build',
  276. 'setting_platform',
  277. 'setting_refresh_tree_on_action'),
  278. 'plan_mode' => array('setting_testplan',
  279. // BUGID 3406
  280. 'setting_build',
  281. 'setting_refresh_tree_on_action'),
  282. 'plan_add_mode' => array('setting_testplan',
  283. 'setting_refresh_tree_on_action'));
  284. /**
  285. * The mode used. Depending on the feature for which this class will be instantiated.
  286. * This mode defines which filter configuration will be loaded from config.inc.php
  287. * and therefore which filters will be loaded and used for the templates.
  288. * Value has to be one of the class constants for mode, default is edit mode.
  289. * @var string
  290. */
  291. private $mode = 'edit_mode';
  292. /**
  293. * The token that will be used to identify the relationship between left frame
  294. * (with navigator) and right frame (which displays execution of test case e.g.) in session.
  295. * @var string
  296. */
  297. public $form_token = null;
  298. /**
  299. *
  300. * @param database $dbHandler
  301. * @param string $mode can be edit_mode/execution_mode/plan_mode/plan_add_mode, depending on usage
  302. */
  303. public function __construct(&$dbHandler, $mode = 'edit_mode') {
  304. // set mode to define further actions before calling parent constructor
  305. $this->mode = array_key_exists($mode,$this->mode_filter_mapping) ? $mode : 'edit_mode';
  306. // Call to constructor of parent class tlFilterControl.
  307. // This already loads configuration and user input
  308. // and does all the remaining necessary method calls,
  309. // so no further method call is required here for initialization.
  310. parent::__construct($dbHandler);
  311. // delete any filter settings that may be left from previous calls in session
  312. $this->delete_own_session_data();
  313. $this->delete_old_session_data();
  314. $this->save_session_data();
  315. }
  316. public function __destruct() {
  317. parent::__destruct(); //destroys testproject manager
  318. // destroy member objects
  319. unset($this->tc_mgr);
  320. unset($this->testplan_mgr);
  321. unset($this->platform_mgr);
  322. unset($this->exec_cf_mgr);
  323. }
  324. /**
  325. * Reads the configuration from the configuration file specific for test cases,
  326. * additionally to those parts of the config which were already loaded by parent class.
  327. *
  328. */
  329. protected function read_config() {
  330. // some configuration reading already done in parent class
  331. parent::read_config();
  332. // load configuration for active mode only
  333. $this->configuration = config_get('tree_filter_cfg')->testcases->{$this->mode};
  334. // load also exec config - it is not only needed in exec mode
  335. $this->configuration->exec_cfg = config_get('exec_cfg');
  336. // some additional testcase configuration
  337. $this->configuration->tc_cfg = config_get('testcase_cfg');
  338. // is choice of advanced filter mode enabled?
  339. if (isset($this->configuration->advanced_filter_mode_choice)
  340. && $this->configuration->advanced_filter_mode_choice == ENABLED) {
  341. $this->filter_mode_choice_enabled = true;
  342. } else {
  343. $this->filter_mode_choice_enabled = false;
  344. }
  345. return tl::OK;
  346. } // end of method
  347. /**
  348. * Does what init_args() usually does in all scripts: Reads the user input
  349. * from request ($_GET and $_POST). Later configuration,
  350. * settings and filters get modified according to that user input.
  351. */
  352. protected function init_args() {
  353. // some common user input is already read in parent class
  354. parent::init_args();
  355. // add settings and filters to parameter info array for request parsers
  356. $params = array();
  357. foreach ($this->all_settings as $name => $info) {
  358. if (is_array($info)) {
  359. $params[$name] = $info;
  360. }
  361. }
  362. foreach ($this->all_filters as $name => $info) {
  363. if (is_array($info)) {
  364. $params[$name] = $info;
  365. }
  366. }
  367. I_PARAMS($params, $this->args);
  368. $type = 'filter_keywords_filter_type';
  369. $this->args->{$type} = (isset($_REQUEST[$type])) ? trim($_REQUEST[$type]) : 'Or';
  370. $extra_keys = array('filter_result_result',
  371. 'filter_result_method',
  372. 'filter_result_build');
  373. foreach ($extra_keys as $ek) {
  374. $this->args->{$ek} = (isset($_REQUEST[$ek])) ? $_REQUEST[$ek] : null;
  375. }
  376. $this->args->{'filter_assigned_user_include_unassigned'} =
  377. isset($_REQUEST['filter_assigned_user_include_unassigned']) ? 1 : 0;
  378. // got session token sent by form or do we have to generate a new one?
  379. $sent_token = null;
  380. $this->args->form_token = null;
  381. if (isset($_REQUEST['form_token'])) {
  382. // token got sent
  383. $sent_token = $_REQUEST['form_token'];
  384. }
  385. if (!is_null($sent_token) && isset($_SESSION[$this->mode][$sent_token])) {
  386. // sent token is valid
  387. $this->form_token = $sent_token;
  388. $this->args->form_token = $sent_token;
  389. } else {
  390. $this->generate_form_token();
  391. }
  392. // "feature" is needed for plan and edit modes
  393. $this->args->feature = isset($_REQUEST['feature']) ? trim($_REQUEST['feature']) : null;
  394. switch ($this->mode) {
  395. case 'plan_mode':
  396. switch($this->args->feature) {
  397. case 'planUpdateTC':
  398. case 'test_urgency':
  399. case 'tc_exec_assignment':
  400. // feature OK
  401. break;
  402. default:
  403. // feature not OK
  404. tLog("Wrong or missing GET argument 'feature'.", 'ERROR');
  405. exit();
  406. break;
  407. }
  408. break;
  409. case 'edit_mode':
  410. switch($this->args->feature) {
  411. case 'edit_tc':
  412. case 'keywordsAssign':
  413. case 'assignReqs':
  414. // feature OK
  415. break;
  416. default:
  417. // feature not OK
  418. tLog("Wrong or missing GET argument 'feature'.", 'ERROR');
  419. exit();
  420. break;
  421. }
  422. break;
  423. }
  424. } // end of method
  425. /**
  426. * Initializes all settings.
  427. * Iterates through all available settings and adds an array to $this->settings
  428. * for the active ones, sets the rest to false so this can be
  429. * checked from templates and elsewhere.
  430. * Then calls the initializing method for each still active setting.
  431. */
  432. protected function init_settings() {
  433. $at_least_one_active = false;
  434. foreach ($this->all_settings as $name => $info) {
  435. $init_method = "init_$name";
  436. if (in_array($name, $this->mode_setting_mapping[$this->mode])
  437. && method_exists($this, $init_method)) {
  438. // is valid, configured, exists and therefore can be used, so initialize this setting
  439. $this->$init_method();
  440. $at_least_one_active = true;
  441. } else {
  442. // is not needed, simply deactivate it by setting it to false in main array
  443. $this->settings[$name] = false;
  444. }
  445. }
  446. // special situation: the build setting is in plan mode only needed for one feature
  447. // BUGID 3406
  448. if ($this->mode == 'plan_mode' && $this->args->feature != 'tc_exec_assignment') {
  449. $this->settings['setting_build'] = false;
  450. }
  451. // if at least one active setting is left to display, switch settings panel on
  452. if ($at_least_one_active) {
  453. $this->display_settings = true;
  454. }
  455. }
  456. /**
  457. * Initialize all filters. (called by parent::__construct())
  458. * I'm double checking here with loaded configuration _and_ additional array
  459. * $mode_filter_mapping, set according to defined mode, because this can avoid errors in case
  460. * when users try to enable a filter in config that doesn't exist for a mode.
  461. * Effect: Only existing and implemented filters can be activated in config file.
  462. */
  463. protected function init_filters() {
  464. // In resulting data structure, all values have to be defined (at least initialized),
  465. // no matter wether they are wanted for filtering or not.
  466. $additional_filters_to_init = array('filter_keywords_filter_type',
  467. 'filter_result_result',
  468. 'filter_result_method',
  469. 'filter_result_build',
  470. 'filter_assigned_user_include_unassigned');
  471. // now nullify them
  472. foreach ($additional_filters_to_init as $filtername) {
  473. $this->active_filters[$filtername] = null;
  474. }
  475. // iterate through all filters and activate the needed ones
  476. $this->display_filters = false;
  477. foreach ($this->all_filters as $name => $info) {
  478. $init_method = "init_$name";
  479. if (in_array($name, $this->mode_filter_mapping[$this->mode]) &&
  480. method_exists($this, $init_method) && $this->configuration->{$name} == ENABLED) {
  481. $this->$init_method();
  482. // there is at least one filter item to display => switch panel on
  483. $this->display_filters = true;
  484. } else {
  485. // is not needed, deactivate filter by setting it to false in main array
  486. // and of course also in active filters array
  487. $this->filters[$name] = false;
  488. $this->active_filters[$name] = null;
  489. }
  490. }
  491. // add the important settings to active filter array
  492. foreach ($this->all_settings as $name => $info) {
  493. if ($this->settings[$name]) {
  494. $this->active_filters[$name] = $this->settings[$name]['selected'];
  495. } else {
  496. $this->active_filters[$name] = null;
  497. }
  498. }
  499. } // end of method
  500. /**
  501. * This method returns an object or array, containing all selections chosen
  502. * by the user for filtering.
  503. *
  504. * @return mixed $value Return value is either an array or stdClass object,
  505. * depending on active mode. It contains all filter values selected by the user.
  506. */
  507. protected function get_active_filters() {
  508. static $value = null; // serves as a kind of cache
  509. // if method is called more than once
  510. // convert array to stcClass if needed
  511. if (!$value) {
  512. switch ($this->mode) {
  513. case 'execution_mode':
  514. case 'plan_mode':
  515. // these features are generating an exec tree,
  516. // they need the filters as a stdClass object
  517. $value = (object) $this->active_filters;
  518. break;
  519. default:
  520. // otherwise simply return the array as-is
  521. $value = $this->active_filters;
  522. break;
  523. }
  524. }
  525. return $value;
  526. } // end of method
  527. public function set_testcases_to_show($testcases_to_show = null) {
  528. // update active_filters
  529. if (!is_null($testcases_to_show)) {
  530. $this->active_filters['testcases_to_show'] = $testcases_to_show;
  531. }
  532. // Since a new filter in active_filters has been set from outside class after
  533. // saving of session data has already happened in constructor,
  534. // we explicitly update data in session after this change here.
  535. $this->save_session_data();
  536. }
  537. /**
  538. * Active filters will be saved to $_SESSION.
  539. * If there already is data for the active mode and token, it will be overwritten.
  540. * This data will be read from pages in the right frame.
  541. * This solves the problems with too long URLs.
  542. * See issue 3516 in Mantis for a little bit more information/explanation.
  543. * The therefore caused new problem that would arise now if
  544. * a user uses the same feature simultaneously in multiple browser tabs
  545. * is solved be the additional measure of using a form token.
  546. *
  547. * @author Andreas Simon
  548. * @return $tl::OK
  549. */
  550. public function save_session_data() {
  551. if (!isset($_SESSION[$this->mode]) || is_null($_SESSION[$this->mode]) || !is_array($_SESSION[$this->mode])) {
  552. $_SESSION[$this->mode] = array();
  553. }
  554. $_SESSION[$this->mode][$this->form_token] = $this->active_filters;
  555. $_SESSION[$this->mode][$this->form_token]['timestamp'] = time();
  556. return tl::OK;
  557. }
  558. /**
  559. * Old filter data for active mode will be deleted from $_SESSION.
  560. * It happens automatically after a session has expired and a user therefore
  561. * has to log in again, but here we can configure an additional time limit
  562. * only for this special filter part in session data.
  563. *
  564. * @author Andreas Simon
  565. * @param int $token_validity_duration data older than given timespan will be deleted
  566. */
  567. public function delete_old_session_data($token_validity_duration = 0) {
  568. // TODO this duration could maybe also be configured in config/const.inc.php
  569. // how long shall the data remain in session before it will be deleted?
  570. if (!is_numeric($token_validity_duration) || $token_validity_duration <= 0) {
  571. $token_validity_duration = 60 * 60 * 1; // one hour as default
  572. }
  573. // delete all tokens from session that are older than given age
  574. if (isset($_SESSION[$this->mode]) && is_array($_SESSION[$this->mode])) {
  575. foreach ($_SESSION[$this->mode] as $token => $data) {
  576. if ($data['timestamp'] < (time() - $token_validity_duration)) {
  577. // too old, delete!
  578. unset($_SESSION[$this->mode][$token]);
  579. }
  580. }
  581. }
  582. }
  583. public function delete_own_session_data() {
  584. if (isset($_SESSION[$this->mode]) && isset($_SESSION[$this->mode][$this->form_token])) {
  585. unset($_SESSION[$this->mode][$this->form_token]);
  586. }
  587. }
  588. /**
  589. * Generates a form token, which will be used to identify the relationship
  590. * between left navigator-frame with its settings and right frame.
  591. */
  592. protected function generate_form_token() {
  593. // Notice: I am just generating an integer here for the token.
  594. // Since this is not any security relevant stuff like a password hash or similar,
  595. // but only a means to separate multiple tabs a single user opens, this should suffice.
  596. // If we should some day decide that an integer is not enough,
  597. // we just have to change this one method and everything will still work.
  598. $min = 1234567890; // not magic, just some large number so the tokens don't get too short
  599. $max = mt_getrandmax();
  600. $token = 0;
  601. // generate new tokens until we find one that doesn't exist yet
  602. do {
  603. $token = mt_rand($min, $max);
  604. } while (isset($_SESSION[$this->mode][$token]));
  605. $this->form_token = $token;
  606. }
  607. /**
  608. * Active filters will be formatted as a GET-argument string.
  609. *
  610. * @return string $string the formatted string with active filters
  611. */
  612. public function get_argument_string() {
  613. static $string = null; // cache for repeated calls of this method
  614. if (!$string) {
  615. $string = '';
  616. // important: the token with which the page in right frame can access data in session
  617. $string .= '&form_token=' . $this->form_token;
  618. if ($this->settings['setting_build']) {
  619. $string .= '&setting_build=' .
  620. $this->settings['setting_build']['selected'];
  621. }
  622. if ($this->settings['setting_platform']) {
  623. $string .= '&setting_platform=' .
  624. $this->settings['setting_platform']['selected'];
  625. }
  626. $keyword_list = null;
  627. if (is_array($this->active_filters['filter_keywords'])) {
  628. $keyword_list = implode(',', $this->active_filters['filter_keywords']);
  629. } else if ($this->active_filters['filter_keywords']) {
  630. $keyword_list = $this->active_filters['filter_keywords'];
  631. }
  632. if ($keyword_list) {
  633. $string .= '&filter_keywords=' . $keyword_list .
  634. '&filter_keywords_filter_type=' .
  635. $this->active_filters['filter_keywords_filter_type'];
  636. }
  637. if ($this->active_filters['filter_priority'] > 0) {
  638. $string .= '&filter_priority=' . $this->active_filters['filter_priority'];
  639. }
  640. if ($this->active_filters['filter_assigned_user']) {
  641. // 3630
  642. $unassigned = $this->active_filters['filter_assigned_user_include_unassigned'] ? '1' : '0';
  643. $string .= '&filter_assigned_user='.
  644. serialize($this->active_filters['filter_assigned_user']) .
  645. '&filter_assigned_user_include_unassigned=' . $unassigned;
  646. }
  647. if ($this->active_filters['filter_result_result']) {
  648. $string .= '&filter_result_result=' .
  649. serialize($this->active_filters['filter_result_result']) .
  650. '&filter_result_method=' .
  651. $this->active_filters['filter_result_method'] .
  652. '&filter_result_build=' .
  653. $this->active_filters['filter_result_build'];
  654. }
  655. }
  656. return $string;
  657. }
  658. /**
  659. * Build the tree menu for generation of JavaScript test case tree.
  660. * Depending on mode and user's selections in user interface,
  661. * either a completely filtered tree will be build and returned,
  662. * or only the minimal necessary data to "lazy load"
  663. * the objects in the tree by later Ajax calls.
  664. * No return value - all variables will be stored in gui object
  665. * which is passed by reference.
  666. *
  667. * @author Andreas Simon
  668. * @param object $gui Reference to GUI object (data will be written to it)
  669. */
  670. public function build_tree_menu(&$gui) {
  671. $tree_menu = null;
  672. $filters = $this->get_active_filters();
  673. $additional_info = null;
  674. $options = null;
  675. $loader = '';
  676. $children = "[]";
  677. $cookie_prefix = '';
  678. // by default, disable drag and drop, then later enable if needed
  679. $drag_and_drop = new stdClass();
  680. $drag_and_drop->enabled = false;
  681. $drag_and_drop->BackEndUrl = '';
  682. $drag_and_drop->useBeforeMoveNode = FALSE;
  683. if (!$this->testproject_mgr) {
  684. $this->testproject_mgr = new testproject($this->db);
  685. }
  686. $tc_prefix = $this->testproject_mgr->getTestCasePrefix($this->args->testproject_id);
  687. switch ($this->mode) {
  688. case 'plan_mode':
  689. // No lazy loading here.
  690. $additional_info = new stdClass();
  691. $additional_info->useCounters = CREATE_TC_STATUS_COUNTERS_OFF;
  692. $additional_info->useColours = COLOR_BY_TC_STATUS_OFF;
  693. $additional_info->testcases_colouring_by_selected_build = DISABLED;
  694. $filters->show_testsuite_contents = 1;
  695. $filters->hide_testcases = 0;
  696. if ($this->args->feature == 'test_urgency') {
  697. $filters->hide_testcases = 1;
  698. }
  699. list($tree_menu, $testcases_to_show) = generateExecTree($this->db,
  700. $gui->menuUrl,
  701. $this->args->testproject_id,
  702. $this->args->testproject_name,
  703. $this->args->testplan_id,
  704. $this->args->testplan_name,
  705. $filters,
  706. $additional_info);
  707. $this->set_testcases_to_show($testcases_to_show);
  708. $root_node = $tree_menu->rootnode;
  709. $children = $tree_menu->menustring ? $tree_menu->menustring : "[]";
  710. $cookie_prefix = $this->args->feature;
  711. break;
  712. case 'edit_mode':
  713. if ($gui->tree_drag_and_drop_enabled[$this->args->feature]) {
  714. $drag_and_drop->enabled = true;
  715. $drag_and_drop->BackEndUrl = $this->args->basehref .
  716. 'lib/ajax/dragdroptprojectnodes.php';
  717. $drag_and_drop->useBeforeMoveNode = false;
  718. }
  719. if ($this->do_filtering) {
  720. $options = array('forPrinting' => NOT_FOR_PRINTING,
  721. 'hideTestCases' => SHOW_TESTCASES,
  722. 'tc_action_enabled' => DO_ON_TESTCASE_CLICK,
  723. 'ignore_inactive_testcases' => DO_NOT_FILTER_INACTIVE_TESTCASES,
  724. 'exclude_branches' => null);
  725. $tree_menu = generateTestSpecTree($this->db, $this->args->testproject_id,
  726. $this->args->testproject_name,
  727. $gui->menuUrl, $filters, $options);
  728. $root_node = $tree_menu->rootnode;
  729. $children = $tree_menu->menustring ? $tree_menu->menustring : "[]";
  730. $cookie_prefix = $this->args->feature;
  731. } else {
  732. $loader = $this->args->basehref . 'lib/ajax/gettprojectnodes.php?' .
  733. "root_node={$this->args->testproject_id}&" .
  734. "tcprefix=" . urlencode($tc_prefix .
  735. $this->configuration->tc_cfg->glue_character);
  736. $tcase_qty = $this->testproject_mgr->count_testcases($this->args->testproject_id);
  737. $root_node = new stdClass();
  738. $root_node->href = "javascript:EP({$this->args->testproject_id})";
  739. $root_node->id = $this->args->testproject_id;
  740. $root_node->name = $this->args->testproject_name . " ($tcase_qty)";
  741. $root_node->testlink_node_type='testproject';
  742. $cookie_prefix = 'tproject_' . $root_node->id . "_";
  743. }
  744. break;
  745. case 'plan_add_mode':
  746. $cookie_prefix = "planaddtc_{$this->args->testproject_id}_{$this->args->user_id}_";
  747. if ($this->do_filtering) {
  748. $options = array('forPrinting' => NOT_FOR_PRINTING,
  749. 'hideTestCases' => HIDE_TESTCASES,
  750. 'tc_action_enabled' => ACTION_TESTCASE_DISABLE,
  751. 'ignore_inactive_testcases' => IGNORE_INACTIVE_TESTCASES,
  752. 'viewType' => 'testSpecTreeForTestPlan');
  753. $tree_menu = generateTestSpecTree($this->db,
  754. $this->args->testproject_id,
  755. $this->args->testproject_name,
  756. $gui->menuUrl,
  757. $filters,
  758. $options);
  759. $root_node = $tree_menu->rootnode;
  760. $children = $tree_menu->menustring ? $tree_menu->menustring : "[]";
  761. } else {
  762. $loader = $this->args->basehref . 'lib/ajax/gettprojectnodes.php?' .
  763. "root_node={$this->args->testproject_id}&show_tcases=0";
  764. $root_node = new stdClass();
  765. $root_node->href = "javascript:EP({$this->args->testproject_id})";
  766. $root_node->id = $this->args->testproject_id;
  767. $root_node->name = $this->args->testproject_name;
  768. $root_node->testlink_node_type = 'testproject';
  769. }
  770. break;
  771. case 'execution_mode':
  772. default:
  773. // No lazy loading here.
  774. // Filtering is always done in execution mode, no matter if user enters data or not,
  775. // since the user should usually never see the whole tree here.
  776. $additional_info = new stdClass();
  777. $filters->hide_testcases = false;
  778. $filters->show_testsuite_contents = $this->configuration->exec_cfg->show_testsuite_contents;
  779. $additional_info->useCounters = $this->configuration->exec_cfg->enable_tree_testcase_counters;
  780. $additional_info->useColours = new stdClass();
  781. $additional_info->useColours->testcases =
  782. $this->configuration->exec_cfg->enable_tree_testcases_colouring;
  783. $additional_info->useColours->counters =
  784. $this->configuration->exec_cfg->enable_tree_counters_colouring;
  785. $additional_info->testcases_colouring_by_selected_build =
  786. $this->configuration->exec_cfg->testcases_colouring_by_selected_build;
  787. list($tree_menu, $testcases_to_show) = generateExecTree($this->db,
  788. $gui->menuUrl,
  789. $this->args->testproject_id,
  790. $this->args->testproject_name,
  791. $this->args->testplan_id,
  792. $this->args->testplan_name,
  793. $filters,
  794. $additional_info);
  795. $this->set_testcases_to_show($testcases_to_show);
  796. $root_node = $tree_menu->rootnode;
  797. $children = $tree_menu->menustring ? $tree_menu->menustring : "[]";
  798. $cookie_prefix = 'exec_tplan_id_' . $this->args->testplan_id;
  799. break;
  800. }
  801. $gui->tree = $tree_menu;
  802. $gui->ajaxTree = new stdClass();
  803. $gui->ajaxTree->loader = $loader;
  804. $gui->ajaxTree->root_node = $root_node;
  805. $gui->ajaxTree->children = $children;
  806. $gui->ajaxTree->cookiePrefix = $cookie_prefix;
  807. $gui->ajaxTree->dragDrop = $drag_and_drop;
  808. } // end of method
  809. private function init_setting_refresh_tree_on_action() {
  810. $key = 'setting_refresh_tree_on_action';
  811. $hidden_key = 'hidden_setting_refresh_tree_on_action';
  812. $selection = 0;
  813. $this->settings[$key] = array();
  814. $this->settings[$key][$hidden_key] = false;
  815. // look where we can find the setting - POST, SESSION, config?
  816. if (isset($this->args->{$key})) {
  817. $selection = 1;
  818. } else if (isset($this->args->{$hidden_key})) {
  819. $selection = 0;
  820. } else if (isset($_SESSION[$key])) {
  821. $selection = $_SESSION[$key];
  822. } else {
  823. $spec_cfg = config_get('spec_cfg');
  824. $selection = ($spec_cfg->automatic_tree_refresh > 0) ? 1 : 0;
  825. }
  826. $this->settings[$key]['selected'] = $selection;
  827. $this->settings[$key][$hidden_key] = $selection;
  828. $_SESSION[$key] = $selection;
  829. } // end of method
  830. private function init_setting_build() {
  831. $key = 'setting_build';
  832. if (is_null($this->testplan_mgr)) {
  833. $this->testplan_mgr = new testplan($this->db);
  834. }
  835. $tplan_id = $this->settings['setting_testplan']['selected'];
  836. // when in plan mode (assigning execution), we want all builds,
  837. // otherwise only those which are active and open
  838. $active = ($this->mode == 'plan_mode') ? null : testplan::GET_ACTIVE_BUILD;
  839. $open = ($this->mode == 'plan_mode') ? null : testplan::GET_OPEN_BUILD;
  840. $this->settings[$key]['items'] = $this->testplan_mgr->get_builds_for_html_options($tplan_id, $active, $open);
  841. $tplan_builds = array_keys($this->settings[$key]['items']);
  842. // BUGID 3406 - depending on mode, we need different labels for this selector on GUI
  843. $label = ($this->mode == 'plan_mode') ? 'assign_build' : 'exec_build';
  844. $this->settings[$key]['label'] = lang_get($label);
  845. // if no build has been chosen by user, select newest build by default
  846. $newest_build_id = $this->testplan_mgr->get_max_build_id($tplan_id, $active, $open);
  847. // BUGID 3726
  848. $session_key = $tplan_id . '_stored_setting_build';
  849. $session_selection = isset($_SESSION[$session_key]) ? $_SESSION[$session_key] : null;
  850. $this->args->{$key} = $this->args->{$key} > 0 ? $this->args->{$key} : $session_selection;
  851. if (!$this->args->{$key}) {
  852. $this->args->{$key} = $newest_build_id;
  853. }
  854. // only take build ID into account if it really is a build from this testplan
  855. $this->settings[$key]['selected'] = (in_array($this->args->{$key}, $tplan_builds)) ?
  856. $this->args->{$key} : $newest_build_id;
  857. // still no build selected? take first one from selection.
  858. if (!$this->settings[$key]['selected'] && sizeof($this->settings[$key]['items'])) {
  859. $this->settings[$key]['selected'] = end($tplan_builds);
  860. }
  861. $_SESSION[$session_key] = $this->settings[$key]['selected'];
  862. } // end of method
  863. private function init_setting_testplan() {
  864. if (is_null($this->testplan_mgr)) {
  865. $this->testplan_mgr = new testplan($this->db);
  866. }
  867. $key = 'setting_testplan';
  868. $testplans = $this->user->getAccessibleTestPlans($this->db, $this->args->testproject_id);
  869. if (isset($_SESSION['testplanID']) && $_SESSION['testplanID'] != $this->args->{$key}) {
  870. // testplan was changed, we need to reset all filters
  871. // --> they were chosen for another testplan, not this one!
  872. $this->args->reset_filters = true;
  873. // check if user is allowed to set chosen testplan before changing
  874. foreach ($testplans as $plan) {
  875. if ($plan['id'] == $this->args->{$key}) {
  876. setSessionTestPlan($plan);
  877. }
  878. }
  879. }
  880. // now load info from session
  881. $info = $this->testplan_mgr->get_by_id($_SESSION['testplanID']);
  882. $this->args->testplan_name = $info['name'];
  883. $this->args->testplan_id = $info['id'];
  884. $this->args->{$key} = $info['id'];
  885. $this->settings[$key]['selected'] = $info['id'];
  886. // Now get all selectable testplans for the user to display.
  887. // For execution, don't take testplans into selection which have no (active/open) builds!
  888. // For plan add mode, add every plan no matter if he has builds or not.
  889. foreach ($testplans as $plan) {
  890. $add_plan = $this->mode == 'plan_add_mode';
  891. if (!$add_plan) {
  892. $builds = $this->testplan_mgr->get_builds($plan['id'],
  893. testplan::GET_ACTIVE_BUILD,
  894. testplan::GET_OPEN_BUILD);
  895. $add_plan = (is_array($builds) && count($builds));
  896. }
  897. if ($add_plan) {
  898. $this->settings[$key]['items'][$plan['id']] = $plan['name'];
  899. }
  900. }
  901. } // end of method
  902. private function init_setting_platform() {
  903. if (!$this->platform_mgr) {
  904. $this->platform_mgr = new tlPlatform($this->db);
  905. }
  906. $key = 'setting_platform';
  907. $this->settings[$key] = array('items' => null, 'selected' => $this->args->{$key});
  908. $testplan_id = $this->settings['setting_testplan']['selected'];
  909. $this->settings[$key]['items'] = $this->platform_mgr->getLinkedToTestplanAsMap($testplan_id);
  910. // BUGID 3726
  911. $session_key = $testplan_id . '_stored_setting_platform';
  912. $session_selection = isset($_SESSION[$session_key]) ? $_SESSION[$session_key] : null;
  913. if (!$this->settings[$key]['selected']) {
  914. $this->settings[$key]['selected'] = $session_selection;
  915. }
  916. if (!isset($this->settings[$key]['items']) || !is_array($this->settings[$key]['items'])) {
  917. $this->settings[$key] = false;
  918. } else if (isset($this->settings[$key]['items']) && is_array($this->settings[$key]['items']) &&
  919. is_null($this->settings[$key]['selected'])) {
  920. // platforms exist, but none has been selected yet, so select first one
  921. $this->settings[$key]['selected'] = key($this->settings[$key]['items']);
  922. }
  923. $_SESSION[$session_key] = $this->settings[$key]['selected'];
  924. } // end of method
  925. private function init_filter_tc_id() {
  926. $key = 'filter_tc_id';
  927. $selection = $this->args->{$key};
  928. $internal_id = null;
  929. if (!$this->testproject_mgr) {
  930. $this->testproject_mgr = new testproject($this->db);
  931. }
  932. if (!$this->tc_mgr) {
  933. $this->tc_mgr = new testcase($this->db);
  934. }
  935. $tc_prefix = $this->testproject_mgr->getTestCasePrefix($this->args->testproject_id);
  936. $tc_prefix .= $this->configuration->tc_cfg->glue_character;
  937. if (!$selection || $selection == $tc_prefix || $this->args->reset_filters) {
  938. $selection = null;
  939. } else {
  940. $this->do_filtering = true;
  941. // we got the external ID here when filtering, but need the internal one
  942. $internal_id = $this->tc_mgr->getInternalID($selection);
  943. }
  944. $this->filters[$key] = array('selected' => $selection ? $selection : $tc_prefix);
  945. $this->active_filters[$key] = $internal_id;
  946. } // end of method
  947. private function init_filter_testcase_name() {
  948. $key = 'filter_testcase_name';
  949. $selection = $this->args->{$key};
  950. if (!$selection || $this->args->reset_filters) {
  951. $selection = null;
  952. } else {
  953. $this->do_filtering = true;
  954. }
  955. $this->filters[$key] = array('selected' => $selection);
  956. $this->active_filters[$key] = $selection;
  957. } // end of method
  958. private function init_filter_toplevel_testsuite() {
  959. if (!$this->testproject_mgr) {
  960. $this->testproject_mgr = new testproject($this->db);
  961. }
  962. $key = 'filter_toplevel_testsuite';
  963. $first_level_suites = $this->testproject_mgr->get_first_level_test_suites($this->args->testproject_id,
  964. 'smarty_html_options');
  965. $selection = $this->args->{$key};
  966. if (!$selection || $this->args->reset_filters) {
  967. $selection = null;
  968. } else {
  969. $this->do_filtering = true;
  970. }
  971. // this filter should only be visible if there are any top level testsuites
  972. $this->filters[$key] = null; // BUGID 3660
  973. if ($first_level_suites) {
  974. $this->filters[$key] = array('items' => array(0 => ''),
  975. 'selected' => $selection,
  976. 'exclude_branches' => array());
  977. foreach ($first_level_suites as $suite_id => $suite_name) {
  978. $this->filters[$key]['items'][$suite_id] = $suite_name;
  979. if ($selection && $suite_id != $selection) {
  980. $this->filters[$key]['exclude_branches'][$suite_id] = 'exclude_me';
  981. }
  982. }
  983. // Important: This is the only case in which active_filters contains the items
  984. // which have to be deleted from tree, instead of the other way around.
  985. $this->active_filters[$key] = $this->filters[$key]['exclude_branches'];
  986. } else {
  987. $this->active_filters[$key] = null;
  988. }
  989. } // end of method
  990. private function init_filter_keywords() {
  991. $key = 'filter_keywords';
  992. $type = 'filter_keywords_filter_type';
  993. $this->filters[$key] = false;
  994. $keywords = null;
  995. switch ($this->mode) {
  996. case 'edit_mode':
  997. // we need the keywords for the whole testproject
  998. if (!$this->testproject_mgr) {
  999. $this->testproject_mgr = new testproject($this->db);
  1000. }
  1001. $keywords = $this->testproject_mgr->get_keywords_map($this->args->testproject_id);
  1002. break;
  1003. default:
  1004. // otherwise (not in edit mode), we want only keywords assigned to testplan
  1005. if (!$this->testplan_mgr) {
  1006. $this->testplan_mgr = new testplan($this->db);
  1007. }
  1008. $tplan_id = $this->settings['setting_testplan']['selected'];
  1009. $keywords = $this->testplan_mgr->get_keywords_map($tplan_id, ' ORDER BY keyword ');
  1010. break;
  1011. }
  1012. $selection = $this->args->{$key};
  1013. $type_selection = $this->args->{$type};
  1014. // are there any keywords?
  1015. if (!is_null($keywords) && count($keywords)) {
  1016. $this->filters[$key] = array();
  1017. if (!$selection || !$type_selection || $this->args->reset_filters) {
  1018. // default values for filter reset
  1019. $selection = null;
  1020. $type_selection = 'Or';
  1021. } else {
  1022. $this->do_filtering = true;
  1023. }
  1024. // data for the keywords themselves
  1025. $this->filters[$key]['items'] = array($this->option_strings['any']) + $keywords;
  1026. $this->filters[$key]['selected'] = $selection;
  1027. $this->filters[$key]['size'] = min(count($this->filters[$key]['items']),
  1028. self::ADVANCED_FILTER_ITEM_QUANTITY);
  1029. // additional data for the filter type (logical and/or)
  1030. $this->filters[$key][$type] = array();
  1031. $this->filters[$key][$type]['items'] = array('Or' => lang_get('logical_or'),
  1032. 'And…

Large files files are truncated, but you can click here to view the full file