PageRenderTime 61ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 1ms

/core/types/ContainerBlock.php

https://bitbucket.org/audax/testmaker-mod
PHP | 1045 lines | 707 code | 104 blank | 234 comment | 143 complexity | 80d09fa819190e1451c45638f4d828c3 MD5 | raw file
Possible License(s): BSD-2-Clause, AGPL-1.0, BSD-3-Clause, LGPL-2.1
  1. <?php
  2. /* This file is part of testMaker.
  3. testMaker is free software; you can redistribute it and/or modify
  4. it under the terms of version 2 of the GNU General Public License as
  5. published by the Free Software Foundation.
  6. testMaker is distributed in the hope that it will be useful,
  7. but WITHOUT ANY WARRANTY; without even the implied warranty of
  8. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  9. GNU General Public License for more details.
  10. You should have received a copy of the GNU General Public License
  11. along with this program. If not, see <http://www.gnu.org/licenses/>. */
  12. /**
  13. * @package Core
  14. */
  15. /**
  16. * Include the Block class
  17. */
  18. require_once(dirname(__FILE__).'/Block.php');
  19. /**
  20. * Include the UserList class
  21. */
  22. require_once(dirname(__FILE__).'/UserList.php');
  23. /**
  24. * Include FileHandling class
  25. */
  26. require_once(dirname(__FILE__).'/FileHandling.php');
  27. /**
  28. * Modifier for simulating adding a block
  29. */
  30. define('MODIFICATION_ADD', 1);
  31. /**
  32. * Modifier for simulating removing a block
  33. */
  34. define('MODIFICATION_REMOVE', 2);
  35. /**
  36. * Problem flag for a missing source for a feedback block
  37. */
  38. define('PROBLEM_MISSING', 2);
  39. /**
  40. * Conceptually, container blocks are blocks that may contain other blocks.
  41. * They are used for describing tests and sub-tests.
  42. *
  43. * @package Core
  44. */
  45. class ContainerBlock extends Block
  46. {
  47. /**
  48. * Creates a new ContainerBlock.
  49. * @param DB db object
  50. * @param integer id of block
  51. */
  52. function ContainerBlock($id) {
  53. $this->db = &$GLOBALS['dao']->getConnection();
  54. $this->table = DB_PREFIX.'container_blocks';
  55. $this->sequence = DB_PREFIX.'blocks';
  56. $this->Block($id);
  57. }
  58. /**
  59. * return correct table name for block determinated by blocktype
  60. * @access private
  61. * @param integer Block type
  62. * @return string Table name
  63. */
  64. function _getTableByType($type)
  65. {
  66. switch($type) {
  67. case BLOCK_TYPE_CONTAINER:
  68. return DB_PREFIX."container_blocks";
  69. break;
  70. case BLOCK_TYPE_INFO:
  71. return DB_PREFIX."info_blocks";
  72. break;
  73. case BLOCK_TYPE_FEEDBACK:
  74. return DB_PREFIX."feedback_blocks";
  75. break;
  76. case BLOCK_TYPE_ITEM:
  77. return DB_PREFIX."item_blocks";
  78. break;
  79. }
  80. }
  81. /**
  82. * Returns whether to show the progress bar or not
  83. * @return boolean
  84. */
  85. function getShowProgressBar()
  86. {
  87. return $this->_getField('progress_bar') ? TRUE : FALSE;
  88. }
  89. /**
  90. * Returns whether to show the pause button or not
  91. * @return integer
  92. */
  93. function getShowPauseButton()
  94. {
  95. return $this->_getField('pause_button');
  96. }
  97. /**
  98. * Return whether to use the parent-test's style
  99. * @return boolean
  100. */
  101. function getUseParentStyle()
  102. {
  103. if ($this->isRootBlock()) return FALSE;
  104. $query = "SELECT use_parent_style FROM ".DB_PREFIX."item_style WHERE test_id=? LIMIT 1";
  105. $res = $this->db->getOne($query, array($this->getId()));
  106. return $res == 1 ? TRUE : FALSE;
  107. }
  108. /**
  109. * Returns the password
  110. * @return string
  111. */
  112. function getPassword()
  113. {
  114. return $this->_getField('password');
  115. }
  116. /**
  117. * Returns whether users should be asked whether they want to skip certain items
  118. * @return boolean
  119. */
  120. function getEnableSkip()
  121. {
  122. return $this->_getField('enable_skip') ? TRUE : FALSE;
  123. }
  124. /**
  125. * Returns whether subtests should be displayed for this test in portal
  126. * @return boolean
  127. */
  128. function getShowSubtests()
  129. {
  130. return $this->_getField('show_subtests') ? TRUE : FALSE;
  131. }
  132. /**
  133. * Returns whether the user's email address should be requested in conjunction with a TAN test
  134. */
  135. function getTanAskEmail()
  136. {
  137. return $this->_getField('tan_ask_email') ? TRUE : FALSE;
  138. }
  139. /**
  140. * Returns whether this block is inactive or not
  141. */
  142. function isInactive()
  143. {
  144. return $this->_getField('subtest_inactive') ? TRUE : FALSE;
  145. }
  146. /**
  147. * Returns whether this block is inactive or not
  148. */
  149. /*nction isInactive($parentBlock)
  150. {
  151. $parentId = $parentBlock->getId();
  152. $res = $this->db->getAll("SELECT * FROM ".DB_PREFIX." WHERE id=? AND parent_id=? AND disabled=1", array($pathIds[$maxPos - 2], $pathIds[$maxPos - 3]), array($this->id));
  153. return $res == 1 ? TRUE : FALSE;
  154. }*/
  155. /**
  156. * Returns if this block has been published (made runnable).
  157. * @param string access type (portal, run)
  158. * @param boolean Whether to check for the current user (true) or for
  159. * regular users, i.e. those who don't have any view permissions
  160. * (false). Note that access is allowed for everyone if it's allowed for
  161. * guests.
  162. * @return boolean
  163. */
  164. function isAccessAllowed($type, $specific = true, $parent = null)
  165. {
  166. switch ($type) {
  167. case 'portal': case 'run': case 'tanrun': case 'review': case 'preview':
  168. break;
  169. case 'tan':
  170. $type = 'tanrun';
  171. break;
  172. case 'direct':
  173. $type = 'run';
  174. break;
  175. case NULL: case false:
  176. return ($this->isAccessAllowed('portal', $specific) || $this->isAccessAllowed('run', $specific)
  177. || $this->isAccessAllowed('tanrun', $specific) || $this->isAccessAllowed('review', $specific)
  178. || $this->isAccessAllowed('password', $specific));
  179. default:
  180. return false;
  181. }
  182. // TANs are handled courtesy of the virtual TAN group
  183. if ($type == 'tanrun') {
  184. $grp = DataObject::getById('Group', GROUP_VIRTUAL_TAN);
  185. if($parent == null) return $grp->checkPermission('run', $this);
  186. else return $grp->checkPermission('run', $parent);
  187. }
  188. // We'll check the guest user first; after all, if the guest can do
  189. // it, why not let everyone else do it too?
  190. $user = DataObject::getById('User', 0);
  191. if ($user->checkPermission($type, $this)) return true;
  192. if (is_string($specific) && $specific == 'password') {
  193. $grp = DataObject::getById('Group', GROUP_VIRTUAL_PASSWORD);
  194. return $grp->checkPermission($type, $this);
  195. } elseif ($specific) {
  196. $user = $GLOBALS['PORTAL']->getUser();
  197. return ($user->checkPermission($type, $this));
  198. } else {
  199. $userList = new UserList();
  200. //isSpecial needs to much time for Access allowed check, rework needed
  201. foreach ($userList->getGroupList(false, true) as $group) {
  202. if (!$group->isSpecial() && $group->checkPermission($type, $this)) return true;
  203. }
  204. return false;
  205. }
  206. }
  207. /**
  208. * Returns if this block will be valid after adding/removing/linking blocks
  209. * The modifications array has to be organized as an 2 dimensional array,
  210. * in the first dimension all modifications are listed. In the second dimension the fields
  211. * 'id', 'parent', 'position' and 'type' have to be given. In 'id', 'parent' (and 'position') the new/old position of the added/removed block has to be given.
  212. * The field 'type' is the kind of modification (MODIFICATION_ADD = add, MODIFICATION_REMOVE = remove)
  213. * Example: $modifications = array(0 => array('id' => 5, 'parent' => 1, 'type' => MODIFICATION_REMOVE, 'original' => true), 1 => array('id' => 5, 'parent' => 1, 'position' => 2, 'type' = MODIFICATION_ADD, 'original' => false));
  214. * The array problems get filled within 2 dimesion. In the first dimension all problems are listed,
  215. * In the second dimension the fields 'public', 'id', 'type' (and 'source') are given.
  216. * 'public' is the id of the public block, 'checkIntegrity' is called from
  217. * 'id' is the block id where the problem appears
  218. * 'type' is representing the kind of problem
  219. * There is currently one kind problems: PROBLEM_MISSING = feedback block is missing corresponding item block before.
  220. * @param $modifications array of modifications to be checked
  221. * @param array 2-dimensional array where to store changed ids from item blocks and item answers for feedback blocks and dimensions format: array('blocks' => array(id1, id2, ...), 'item_answers' => array(id1, id2, ...))
  222. * @param &$problems array filled with appearing problems
  223. * @param string name of action requiring this check ('move_target', 'move_source', 'delete', 'copy', 'link')
  224. * @return bool
  225. */
  226. function checkIntegrity($modifications, $changedIds, &$problems, $action = 'unknown')
  227. {
  228. $result = true;
  229. if(!is_array($modifications)) {
  230. trigger_error('<b>ContainerBlock::checkIntegrity()</b>: $modifications is no valid array');
  231. return false;
  232. }
  233. if(!is_array($problems)) {
  234. trigger_error('<b>ContainerBlock::checkIntegrity()</b>: $problems is no valid array');
  235. return false;
  236. }
  237. //get new list of children
  238. $list = $this->listModifiedNode($modifications);
  239. //check for problems
  240. $analyse = array();
  241. for($i = 0; $i < count($list); $i++)
  242. {
  243. $analyse[$list[$i]['id']] = true;
  244. if($list[$i]['type'] == BLOCK_TYPE_FEEDBACK)
  245. {
  246. $currentBlock = $GLOBALS["BLOCK_LIST"]->getBlockById($list[$i]['id'], BLOCK_TYPE_FEEDBACK);
  247. foreach ($currentBlock->getSourceIds() as $id) {
  248. if(isset($changedIds['blocks'][$id])) {
  249. $id = $changedIds['blocks'][$id];
  250. }
  251. if(!isset($analyse[$id]) || $analyse[$id] != true) {
  252. $problem['public'] = $this->id;
  253. $problem['id'] = $list[$i]['id'];
  254. $problem['type'] = PROBLEM_MISSING;
  255. $problem['source'] = $id;
  256. $problem['action'] = $action;
  257. $problems[] = $problem;
  258. $result = false;
  259. }
  260. }
  261. }
  262. }
  263. return $result;
  264. }
  265. /**
  266. * Checks if a new order of the blocks children is valid or not (only applicable for reordering of item and feedback blocks yet)
  267. * @param array List of block ids indicating which block will be saved at which position
  268. * @param array Array for saving problems
  269. * @param string Performed action
  270. */
  271. function validateOrder($list, &$problems, $action = 'unknown')
  272. {
  273. $result = true;
  274. if(!is_array($problems)) {
  275. trigger_error('<b>ContainerBlock::checkIntegrity()</b>: $problems is no valid array');
  276. return false;
  277. }
  278. // Get the current block and page structure of the test/block
  279. require_once(CORE.'types/TestStructure.php');
  280. $testBlocks = TestStructure::getBlockStructure($this->id);
  281. $testPages = TestStructure::getPageStructure($this->id);
  282. // First build a list of parent id -> child id relations
  283. $parentIds = array();
  284. $blockIds = array();
  285. foreach($testBlocks as $block)
  286. {
  287. $keyParent = $block['parent_id'];
  288. $keyId = $block['block_id'];
  289. if(!array_key_exists($keyParent, $parentIds)) $parentIds[$keyParent] = array();
  290. if(!array_key_exists($keyId, $parentIds)) $blockIds[$keyId] = array();
  291. $parentIds[$keyParent][] = $block['block_id'];
  292. $blockIds[$keyId] = true;
  293. }
  294. // Now build a list with the new virtual order of the blocks
  295. $orderChanged = false;
  296. $newTestBlocks = array();
  297. foreach($list as $index => $listItem)
  298. {
  299. if(array_key_exists($index, $parentIds[$this->id]) && $listItem['id'] != $parentIds[$this->id][$index]) $orderChanged = true;
  300. $lastId = $listItem['id'];
  301. // Add the blocks of the structure according to the new order
  302. if (array_key_exists($lastId, $testBlocks))
  303. $newTestBlocks[] = $testBlocks[$lastId];
  304. // Reorder the child blocks
  305. unset($testBlocks[$lastId]);
  306. if(array_key_exists($lastId, $parentIds))
  307. {
  308. foreach($parentIds[$lastId] as $childId)
  309. {
  310. $newTestBlocks[] = $testBlocks[$childId];
  311. }
  312. }
  313. }
  314. // Nothing has been changed, don't do reordering
  315. if(!$orderChanged) return false;
  316. // Build a list with item -> condition relations
  317. $blockItemConditions = array();
  318. foreach($testPages[0] as $page)
  319. {
  320. $itemId = $page->getId();
  321. $parentId = $page->parentNode->id;
  322. if(is_subclass_of($page, "Item"))
  323. {
  324. $condArray = $page->getConditions();
  325. if(!empty($condArray))
  326. {
  327. if(!array_key_exists($parentId, $blockItemConditions)) $blockItemConditions[$parentId] = array();
  328. foreach($condArray as $cond)
  329. {
  330. $blockItemConditions[$parentId][] = $cond['item_block_id'];
  331. }
  332. }
  333. }
  334. }
  335. // Check the requirements for the feedback blocks (source blocks before feedback blocks)...
  336. $ids = array();
  337. foreach($newTestBlocks as $block)
  338. {
  339. $ids[$block['block_id']] = true;
  340. if($block['block_type'] == "ItemBlock")
  341. {
  342. // Get the conditions of all items in the block
  343. if(array_key_exists($block['block_id'], $blockItemConditions))
  344. {
  345. foreach($blockItemConditions[$block['block_id']] as $condBlock)
  346. {
  347. if(array_key_exists($condBlock, $blockIds) && (!array_key_exists($condBlock, $ids) || $ids[$condBlock] != true) && $block['block_id'] != $condBlock)
  348. {
  349. // Indicate problems
  350. $problem['public'] = $this->id;
  351. $problem['id'] = $block['block_id'];
  352. $problem['type'] = PROBLEM_MISSING;
  353. $problem['source'] = $condBlock;
  354. $problem['action'] = $action;
  355. $problems[] = $problem;
  356. $result = false;
  357. }
  358. }
  359. }
  360. }
  361. elseif($block['block_type'] == "FeedbackBlock")
  362. {
  363. // Indicate problems
  364. if(array_key_exists('source_ids', $block))
  365. {
  366. foreach($block['source_ids'] as $sourceId)
  367. {
  368. if(array_key_exists($sourceId, $blockIds) && (!array_key_exists($sourceId, $ids) || $ids[$sourceId] != true))
  369. {
  370. $problem['public'] = $this->id;
  371. $problem['id'] = $block['block_id'];
  372. $problem['type'] = PROBLEM_MISSING;
  373. $problem['source'] = $sourceId;
  374. $problem['action'] = $action;
  375. $problems[] = $problem;
  376. $result = false;
  377. }
  378. }
  379. }
  380. }
  381. }
  382. return $result;
  383. }
  384. /**
  385. * Reorder child item/text/feedback blocks;
  386. */
  387. function reorderChildren($order, &$problems, $checkIntegrity = true)
  388. {
  389. foreach($order as $position => $child)
  390. {
  391. if(!$GLOBALS['BLOCK_LIST']->getBlockById($child['id'])->setPosition($position+1, $this->id, $problems, $checkIntegrity)) return false;
  392. }
  393. }
  394. /**
  395. * prepare given array with data for database insertion
  396. * @static
  397. * @param array associative array with database fields and content
  398. * @return array associative array with database fields and content
  399. */
  400. function prepareDBData($data) {
  401. if(!is_array($data)) {
  402. trigger_error('<b>ContainerBlock:prepareDBData</b>: $data is no valid array');
  403. }
  404. $data2 = array();
  405. if(!isset($data['direct_access_key'])) {
  406. $data['direct_access_key'] = '';
  407. }
  408. $data2['direct_access_key'] = $data['direct_access_key'];
  409. if(!isset($data['permissions_recursive'])) {
  410. $data['permissions_recursive'] = 1;
  411. }
  412. $data2['permissions_recursive'] = $data['permissions_recursive'];
  413. if(!isset($data['progress_bar'])) {
  414. $data['progress_bar'] = 1;
  415. }
  416. $data2['progress_bar'] = $data['progress_bar'];
  417. if(!isset($data['show_subtests'])) {
  418. $data['show_subtests'] = 0;
  419. }
  420. $data2['show_subtests'] = $data['show_subtests'];
  421. if(!isset($data['enable_skip'])) {
  422. $data['enable_skip'] = 1;
  423. }
  424. $data2['enable_skip'] = $data['enable_skip'];
  425. if(!isset($data['pause_button']) || $data['pause_button'] == 1) {
  426. $data['pause_button'] = 1;
  427. }
  428. $data2['pause_button'] = $data['pause_button'];
  429. if(!isset($data['tan_ask_email']) || $data['tan_ask_email'] == 1) {
  430. $data['tan_ask_email'] = 1;
  431. }
  432. $data2['tan_ask_email'] = $data['tan_ask_email'];
  433. return $data2;
  434. }
  435. /**
  436. * Returns a copy of the current block
  437. * @param integer Parent ID to copy block into
  438. * @param array 2-dimensional array where to store changed ids from item blocks and item answers for feedback blocks and dimensions format: array('blocks' => array(id1, id2, ...), 'item_answers' => array(id1, id2, ...))
  439. * On user call only an empty array has to be given. The array is filled by the different copy functions of the nodes.
  440. * @param array Where to store problems if the block would be copied (checkIntegrity)
  441. * @return Block
  442. */
  443. function copyNode($parentId, &$changedIds, &$problems)
  444. {
  445. $block = parent::copyNode($parentId, $changedIds, NULL, $problems);
  446. if(count($problems) == 0)
  447. {
  448. $query = 'SELECT * FROM '.DB_PREFIX.'item_style WHERE test_id = ?';
  449. $results = $this->db->getAll($query, array($this->id));
  450. if($this->db->isError($results)) return false;
  451. foreach($results as $result) {
  452. $rows = array();
  453. $values = array();
  454. for(reset($result); list($key, $value) = each($result);)
  455. {
  456. switch($key)
  457. {
  458. case 'test_id':
  459. $value = $block->getId();
  460. break;
  461. case 'id':
  462. $value = $this->db->nextId(DB_PREFIX.'item_style');
  463. break;
  464. }
  465. $rows[] = $key;
  466. $values[] = $value;
  467. }
  468. $query = 'INSERT INTO '.DB_PREFIX.'item_style ('.implode(', ', $rows).') VALUES ('.ltrim(str_repeat(', ?', count($values)), ', ').')';
  469. if($this->db->isError($this->db->query($query, $values))) return false;
  470. }
  471. }
  472. return $block;
  473. }
  474. /**
  475. * modify the given informations in current block
  476. * @param mixed[] $modification values to be modified
  477. * @return bool
  478. */
  479. function modify($modifications)
  480. {
  481. if(!is_array($modifications)) {
  482. trigger_error('<b>Block:modify</b>: $modifications is no valid array');
  483. }
  484. $avalues = array();
  485. if(array_key_exists('title', $modifications)) {
  486. $avalues['title'] = $modifications['title'];
  487. }
  488. if(array_key_exists('description', $modifications)) {
  489. $avalues['description'] = $modifications['description'];
  490. }
  491. if(array_key_exists('permissions_recursive', $modifications)) {
  492. $avalues['permissions_recursive'] = $modifications['permissions_recursive'];
  493. }
  494. if(array_key_exists('pos', $modifications)) {
  495. $avalues['pos'] = $modifications['pos'];
  496. }
  497. if(array_key_exists('language', $modifications)) {
  498. $avalues['def_language'] = $modifications['language'];
  499. }
  500. if(array_key_exists('progress_bar', $modifications)) {
  501. $avalues['progress_bar'] = $modifications['progress_bar'];
  502. }
  503. if(array_key_exists('pause_button', $modifications)) {
  504. $avalues['pause_button'] = $modifications['pause_button'];
  505. }
  506. if(array_key_exists('show_subtests', $modifications)) {
  507. $avalues['show_subtests'] = $modifications['show_subtests'];
  508. }
  509. if(array_key_exists('enable_skip', $modifications)) {
  510. $avalues['enable_skip'] = $modifications['enable_skip'];
  511. }
  512. if(array_key_exists('password', $modifications)) {
  513. $avalues['password'] = $modifications['password'];
  514. }
  515. if(array_key_exists('open_date', $modifications)) {
  516. $avalues['open_date'] = $modifications['open_date'];
  517. }
  518. if(array_key_exists('close_date', $modifications)) {
  519. $avalues['close_date'] = $modifications['close_date'];
  520. }
  521. if(array_key_exists('subtest_inactive', $modifications)) {
  522. $avalues['subtest_inactive'] = $modifications['subtest_inactive'];
  523. }
  524. if(array_key_exists('random_order', $modifications)) {
  525. $avalues['random_order'] = (int)$modifications['random_order'];
  526. }
  527. if(array_key_exists('disabled', $modifications)) {
  528. $avalues['disabled'] = $modifications['disabled'] ? 1 : 0;
  529. }
  530. if(array_key_exists('tan_ask_email', $modifications)) {
  531. $avalues['tan_ask_email'] = $modifications['tan_ask_email'];
  532. }
  533. return $this->_modify($avalues);
  534. }
  535. /**
  536. * Creates and returns a new block of the given type
  537. * @param integer type of block
  538. * @param array Associative array with required informations (key names same as database column names of given type)
  539. * @return Block
  540. */
  541. function createChild($type, $informations = array())
  542. {
  543. if(!preg_match('/^[0-4]{1}$/', $type))
  544. {
  545. trigger_error('<b>ContainerBlock:createBlock</b>: $type is no valid block type');
  546. return NULL;
  547. }
  548. if(!is_array($informations))
  549. {
  550. trigger_error('<b>ContainerBlock:createBlock</b>: $informations is no valid array');
  551. return NULL;
  552. }
  553. if(isset($informations['pos']) && !preg_match('/^[0-9]+$/', $informations['pos']))
  554. {
  555. trigger_error('<b>ContainerBlock:createBlock</b>: $informations[\'pos\'] is not valid');
  556. return NULL;
  557. }
  558. //prepare given array for given type
  559. switch($type) {
  560. case BLOCK_TYPE_CONTAINER:
  561. $avalues = ContainerBlock::prepareDBData($informations);
  562. break;
  563. case BLOCK_TYPE_INFO:
  564. $avalues = InfoBlock::prepareDBData($informations);
  565. break;
  566. case BLOCK_TYPE_FEEDBACK:
  567. $avalues = FeedbackBlock::prepareDBData($informations);
  568. break;
  569. case BLOCK_TYPE_ITEM:
  570. $avalues = ItemBlock::prepareDBData($informations);
  571. break;
  572. }
  573. //prepare informations with type independent values
  574. $id = $this->db->nextId($this->childrenSequence);
  575. if($this->db->isError($id)) {
  576. return NULL;
  577. }
  578. $avalues['id'] = $id;
  579. $avalues['owner'] = $GLOBALS['PORTAL']->getUserId();
  580. if(isset($informations['title'])) {
  581. $avalues['title'] = $informations['title'];
  582. }
  583. if(isset($informations['description'])) {
  584. $avalues['description'] = $informations['description'];
  585. }
  586. //insert type information of block
  587. $query = 'INSERT INTO '.DB_PREFIX.'blocks_type (id, type) VALUES (?, ?)';
  588. $result = $this->db->query($query, array($avalues['id'], $type));
  589. if($this->db->isError($result)) {
  590. return NULL;
  591. }
  592. $child = $this->_createChild($type, $avalues);
  593. $child->setParentPermissions($this->id);
  594. if ($type == BLOCK_TYPE_CONTAINER) {
  595. $fields = array(
  596. 'background_color' => "#ffffff",
  597. 'font_family' => "",
  598. 'font_size' => "",
  599. 'font_style' => "",
  600. 'font_weight' => "",
  601. 'color' => "#000000",
  602. 'item_background_color' => "#ffffff",
  603. 'dist_background_color' => "#ffffff",
  604. 'logo_align' => "left",
  605. 'page_width' => "0",
  606. 'item_borders' => 7,
  607. 'use_parent_style' => 1,
  608. );
  609. $child->setStyle($fields);
  610. }
  611. return $child;
  612. }
  613. /**
  614. * deletes the given block from the current block and removes all saved data of this block and its children if they are not children of any other block
  615. * @param integer $id id of block
  616. * @param array $problems problems of integrity (see checkIntegrity)
  617. * @return boolean
  618. */
  619. function deleteChild($id, &$problems, $simulate = false)
  620. {
  621. if(!$this->existsChild($id))
  622. {
  623. trigger_error('<b>ContainerBlock:deleteChild</b>: $id does not exist');
  624. return false;
  625. }
  626. if(!is_array($problems))
  627. {
  628. trigger_error('<b>ContainerBlock::deleteChild</b>: $problems is no valid array');
  629. return false;
  630. }
  631. if(count($problems) > 0)
  632. {
  633. trigger_error('<b>ContainerBlock::deleteChild</b>: $problems is not empty');
  634. return false;
  635. }
  636. $block = $this->getChildById($id);
  637. //simulate in all Public Blocks containing this Block the situation if the block is already deleted
  638. $modification['parent'] = $this->id;
  639. $modification['id'] = $id;
  640. $modification['type'] = MODIFICATION_REMOVE;
  641. $modifications[] = $modification;
  642. $parentUpperPublicBlocks = $this->getUpperPublicBlocks();
  643. for($i = 0; $i < sizeof($parentUpperPublicBlocks); $i++) {
  644. $parentUpperPublicBlocks[$i]->checkIntegrity($modifications, array(), $problems, 'delete');
  645. }
  646. //interrupt deletion if problem appears
  647. if(count($problems) > 0)
  648. {
  649. $problems = array_unique($problems);
  650. return false;
  651. }
  652. if ($simulate) return true;
  653. //delete all test runs for given child
  654. require_once(CORE."types/TestRunList.php");
  655. $testRunList = new TestRunList();
  656. $testRuns = $testRunList->getTestRunsForTest($id);
  657. for ($i = 0; $i < count($testRuns); $i++) {
  658. $testRun = &$testRuns[$i];
  659. $testRun->delete();
  660. }
  661. return parent::deleteChild($id);
  662. }
  663. /**
  664. * Return stylesheet informations for this test
  665. *
  666. * @return string Stylesheet definitions
  667. */
  668. function getStyle()
  669. {
  670. $style = array();
  671. $query = "SELECT * FROM ".DB_PREFIX."item_style WHERE test_id=? LIMIT 1";
  672. $res = $this->db->query($query, array($this->getId()));
  673. if (!PEAR::isError($res) && $res->numRows())
  674. {
  675. while ($row = $res->fetchRow())
  676. {
  677. $style['Top']['text-align'] = $row['logo_align'];
  678. $style['wrapper']['width'] = $row['page_width'] == 1 ? '800px' : 0;
  679. $style['wrapper']['margin'] = $row['page_width'] == 1 ? 'auto' : 0;
  680. $style['wrapper']['text-align'] = 'left';
  681. $style['body']['background-color'] = $row['background_color'];
  682. $style['Question']['font-family'] = $row['font_family'];
  683. $style['Question']['font-size'] = $row['font_size'];
  684. $style['Question']['font-style'] = $row['font_style'];
  685. $style['Question']['font-weight'] = $row['font_weight'];
  686. $style['Question']['color'] = $row['color'];
  687. $style['Answers']['font-family'] = $row['font_family'];
  688. $style['Answers']['font-size'] = $row['font_size'];
  689. $style['Answers']['font-style'] = $row['font_style'];
  690. $style['Answers']['font-weight'] = $row['font_weight'];
  691. $style['Answers']['color'] = $row['color'];
  692. $style['Question']['background-color'] = $row['item_background_color'];
  693. $style['Answers']['background-color'] = $row['dist_background_color'];
  694. // process item borders, which are stored as a 3-digit binary number
  695. if ($row['item_borders'] != NULL) {
  696. $tmp = decbin($row['item_borders']);
  697. $tmp = substr("000",0,3 - strlen($tmp)) . $tmp;
  698. $style['Question']['border'] = $tmp[0] != 0 ? '1px solid #cccccc' : 0;
  699. $style['Answers table.Border']['border'] = $tmp[1] != 0 ? '1px solid #cccccc' : 0;
  700. $style['Answers td.Border']['border'] = $tmp[2] != 0 ? '1px solid #cccccc' : 0;
  701. } else {
  702. $style['Question']['border'] = '1px solid #cccccc';
  703. $style['Answers table.Border']['border'] = '1px solid #cccccc';
  704. $style['Answers td.Border']['border'] = '1px solid #cccccc';
  705. }
  706. }
  707. } else if (! $res->numRows()) {
  708. //Default values
  709. $style['Top']['text-align'] = 'left';
  710. $style['wrapper']['width'] = 0;
  711. $style['wrapper']['margin'] = 0;
  712. $style['wrapper']['text-align'] = 'left';
  713. $style['body']['background-color'] = '#ffffff';
  714. $style['Question']['font-family'] = 'Verdana';
  715. $style['Question']['font-size'] = 'none';
  716. $style['Question']['font-style'] = 'none';
  717. $style['Question']['font-weight'] = 'none';
  718. $style['Question']['color'] = '#000000';
  719. $style['Answers']['font-family'] = 'Verdana';
  720. $style['Answers']['font-size'] = 'none';
  721. $style['Answers']['font-style'] = 'none';
  722. $style['Answers']['font-weight'] = 'none';
  723. $style['Answers']['color'] = '#000000';
  724. $style['Question']['background-color'] = '#ffffff';
  725. $style['Answers']['background-color'] = '#ffffff';
  726. $style['Question']['border'] = '1px solid #cccccc';
  727. $style['Answers table.Border']['border'] = '1px solid #cccccc';
  728. $style['Answers td.Border']['border'] = '1px solid #cccccc';
  729. }
  730. return $style;
  731. }
  732. /**
  733. * Return the default language of a test
  734. *
  735. * @return string Default Language
  736. */
  737. function getLanguage()
  738. {
  739. $language = $this->_getField('def_language');
  740. return $language;
  741. }
  742. /**
  743. * Return media_connect id of a logo
  744. *
  745. * @return mixed Media_connect_id or false
  746. */
  747. function getLogo()
  748. {
  749. $query = "SELECT logo FROM ".DB_PREFIX."item_style WHERE test_id = ? LIMIT 1";
  750. $logo = $this->db->getOne($query, array($this->getId()));
  751. if (PEAR::isError($logo))
  752. {
  753. $logo = false;
  754. }
  755. return $logo;
  756. }
  757. /**
  758. * set stylesheet informations for this test
  759. *
  760. * @param mixed Style informations
  761. * @return boolean
  762. */
  763. function setStyle($style)
  764. {
  765. if ($this->hasStyle())
  766. {
  767. $styles = '';
  768. $values = array();
  769. $first = true;
  770. foreach ($style as $key => $value)
  771. {
  772. if (!$first) $styles .= ',';
  773. $styles .= $key.' = ?';
  774. $values[] = $value;
  775. $first = false;
  776. }
  777. $values[] = $this->getId();
  778. $query = "UPDATE ".DB_PREFIX."item_style SET ".$styles." WHERE test_id=? LIMIT 1";
  779. $result = $this->db->query($query, $values);
  780. }
  781. else
  782. {
  783. $styles = '';
  784. $marks = '?,?,';
  785. $values = array($this->db->nextId(DB_PREFIX."item_style"), $this->getId());
  786. $first = true;
  787. if (!isset($style['logo_align'])) {
  788. $style['logo_align'] = '';
  789. }
  790. foreach ($style as $key => $value)
  791. {
  792. $values[] = $value;
  793. if (!$first) {
  794. $styles .= ',';
  795. $marks .= ',';
  796. }
  797. $styles .= $key;
  798. $marks .= '?';
  799. $first = false;
  800. }
  801. $query = "INSERT INTO ".DB_PREFIX."item_style (id, test_id, ".$styles.") VALUES(".$marks.")";
  802. $result = $this->db->query($query, $values);
  803. }
  804. if (PEAR::isError($result))
  805. {
  806. return false;
  807. }
  808. return true;
  809. }
  810. /**
  811. * Checks if this block has a stylesheet associated with it.
  812. * @return boolean
  813. */
  814. function hasStyle()
  815. {
  816. $query = "SELECT count(id) FROM ".DB_PREFIX."item_style WHERE test_id = ? LIMIT 1";
  817. $has = $this->db->getOne($query, array($this->getId()));
  818. if (PEAR::isError($has) || !$has)
  819. {
  820. return false;
  821. }
  822. return true;
  823. }
  824. function hasRandomItemOrder()
  825. {
  826. return $this->_getField('random_order') == 1 ? true : false;
  827. }
  828. /**
  829. * Sets the logo for this block to a certain media file.
  830. * @param integer media_connect_id of the file
  831. */
  832. function setLogo($media_connect_id = 0)
  833. {
  834. if ($this->hasStyle())
  835. {
  836. $query = "UPDATE ".DB_PREFIX."item_style SET logo=? WHERE test_id=?";
  837. $result = $this->db->query($query, array($media_connect_id, $this->getId()));
  838. }
  839. else
  840. {
  841. $query = "INSERT INTO ".DB_PREFIX."item_style (id, test_id, logo) VALUES(?,?,?)";
  842. $result = $this->db->query($query, array($this->db->nextId(DB_PREFIX."item_style"), $this->getId(), $media_connect_id));
  843. }
  844. if (PEAR::isError($result))
  845. {
  846. return false;
  847. }
  848. return true;
  849. }
  850. function getItemDisplayConditions($completeOnly = FALSE)
  851. {
  852. $conditions = array();
  853. foreach ($this->getChildren() as $child)
  854. {
  855. if ($child->isContainerBlock() || $child->isItemBlock()) {
  856. $conditions = array_merge($conditions, $child->getItemDisplayConditions($completeOnly));
  857. }
  858. }
  859. return $conditions;
  860. }
  861. function getItemIds($adaptive = TRUE)
  862. {
  863. $itemIds = array();
  864. foreach ($this->getChildren() as $child)
  865. {
  866. if ($child->isContainerBlock() || ($child->isItemBlock() && $adaptive)) {
  867. $itemIds = array_merge($itemIds, $child->getItemIds());
  868. }
  869. }
  870. return $itemIds;
  871. }
  872. /**
  873. * Counts the number of children with a certain type
  874. * @param integer The type of children to count
  875. * @return integer
  876. */
  877. function countChildrenByType($type)
  878. {
  879. $count = 0;
  880. foreach ($this->getChildren() as $child) {
  881. if ($child->getBlockType() == $type) {
  882. $count++;
  883. }
  884. }
  885. return $count;
  886. }
  887. /**
  888. * Prepares this block for deletion; cleans stylesheet table if exists
  889. */
  890. function cleanUp()
  891. {
  892. // Remove style record
  893. if ($this->hasStyle()) {
  894. // Remove logo
  895. if ($logo = $this->getLogo()) {
  896. $fh = new FileHandling();
  897. $fh->deleteMediaConnection($logo);
  898. }
  899. $query = "DELETE FROM ".DB_PREFIX."item_style WHERE test_id = ? LIMIT 1";
  900. $delete = $this->db->query($query, array($this->getId()));
  901. if (PEAR::isError($delete))
  902. {
  903. return false;
  904. }
  905. }
  906. parent::cleanUp();
  907. }
  908. /**
  909. * \return string Text open date
  910. */
  911. function getOpenDate()
  912. {
  913. return $this->_getField('open_date');
  914. }
  915. /**
  916. * \return string Text close date
  917. */
  918. function getCloseDate()
  919. {
  920. return $this->_getField('close_date');
  921. }
  922. /**
  923. *Return the number of shown Items of the container block
  924. *@return int
  925. */
  926. function getShownItems($testRun) {
  927. $childs = $this->getChildren();
  928. $total = 0;
  929. foreach ($childs as $child) {
  930. if ($child->isFeedbackBlock() == false)
  931. $total += $child->getShownItems($testRun);
  932. }
  933. return $total;
  934. }
  935. }
  936. ?>