PageRenderTime 57ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 0ms

/Modifier.class.php

https://bitbucket.org/kljungkvist/test-fork
PHP | 1077 lines | 491 code | 155 blank | 431 comment | 113 complexity | 943e62f0165526cbf96b570d9c8932e4 MD5 | raw file
  1. <?php
  2. /**
  3. * Recommender Ranking Modifier Base Class
  4. *
  5. * The ModifierFactory implements a factory to create
  6. * concrete Modifier objects of various types.
  7. *
  8. * Each Modifier object subclasses the Modifier base class.
  9. *
  10. * Kristian Ljungkvist Created 08/20/07
  11. * Note -- Requires PHP 5
  12. *
  13. * Copyright 2007 Science Buddies. All Rights Reserved
  14. */
  15. class Modifier {
  16. /**
  17. * Constructor
  18. *
  19. *
  20. * @author Kristian Ljungkvist
  21. */
  22. function __construct() {
  23. }
  24. /**
  25. * process method
  26. *
  27. * Each Modifier must override this method.
  28. * @author Kristian Ljungkvist
  29. */
  30. function process($ranking_list,$individual_profile,$state_array,$configuration,$sk_summary)
  31. {
  32. return $ranking_list;
  33. }
  34. /**
  35. * array_move
  36. *
  37. * Moves an element of an associative array to a new location in that array.
  38. *
  39. * Kristian Ljungkvist new rewrite 04/23/2009.
  40. **/
  41. function array_move($array,$from,$key,$to) {
  42. // echo "array_move: $key from position $from to position $to<br/>";
  43. $org_count = count($array);
  44. if( ($from < 1) || ($to < 1)) {return $array;}
  45. if($from == $to) {return $array;}
  46. if(!$key) {return $array;}
  47. if($to > count($array)) {return $array;}
  48. $value_to_move = $array[$key];
  49. // 1. copy array minus item to move
  50. foreach($array as $cur_key => $val) {
  51. if($cur_key != $key) {
  52. $copied_array[$cur_key] = $val;
  53. }
  54. }
  55. // 2. copy up to 'to' position from that new array
  56. $index = 1;
  57. foreach($copied_array as $cur_key => $val) {
  58. if($index >= $to) {break;}
  59. $copied_array_2[$cur_key] = $val;
  60. $index++;
  61. }
  62. // 3. add item to move to 'to' position
  63. $copied_array_2[$key] = $value_to_move;
  64. // 4. copy anything left after 'to' in copied array
  65. $index = 1;
  66. foreach($copied_array as $cur_key => $val) {
  67. if($index < $to) {$index++; continue;}
  68. $copied_array_2[$cur_key] = $val;
  69. $index++;
  70. }
  71. // Check that the resulting array is of the same length as the original. If not, notify me and return the original.
  72. if(count($copied_array_2) != $org_count) {
  73. // Notify me by email if this happens-- KEL 09/24/08
  74. $msg = "org_count: $org_count\n";
  75. $msg .= "result_count: ".count($copied_array_2)."\n";
  76. $msg .= "Original array:\n";
  77. $msg .= print_r($array,true);
  78. $msg .= "Resulting array:\n";
  79. $msg .= print_r($copied_array_2,true);
  80. mail("kristian.ljungkvist@gmail.com","Modifier.class.php -- array_move -- Counts Differ",$msg);
  81. return $array; // return original, unmodified array in this case.
  82. }
  83. return($copied_array_2);
  84. }
  85. /**
  86. * array_move
  87. *
  88. * Moves an element of an associative array to a new location in that array.
  89. *
  90. * Kristian Ljungkvist 09/19/2007.
  91. **/
  92. function recent_defunct_array_move($array,$from,$key,$to) {
  93. $org_count = count($array);
  94. // echo "from:$from, key:$key, to:$to, count:".count($array)."\n";
  95. if($from == $to) {return $array;}
  96. if(!$key) {return $array;}
  97. if($to >= count($array)) {return $array;}
  98. // if($array[$from] != $key) {return $array;}
  99. $tmp_array = array();
  100. // get the item to be moved
  101. $contents = $array[$key];
  102. $index = 0;
  103. $moved = false;
  104. foreach($array as $cur_key =>$val) {
  105. if(!$cur_key) {continue;}
  106. if($cur_key == $key) {continue;}
  107. if(($index != $to) || $moved) {
  108. $tmp_array[$cur_key] = $val;
  109. } else {
  110. $tmp_array[$key] = $contents;
  111. $tmp_array[$cur_key] = $val;
  112. $moved = true;
  113. }
  114. $index++;
  115. }
  116. if(count($tmp_array) != $org_count) {
  117. // Notify me by email if this happens-- KEL 09/24/08
  118. $msg = "org_count: $org_count\n";
  119. $msg .= "result_count: ".count($tmp_array)."\n";
  120. $msg .= "Original array:\n";
  121. $msg .= print_r($array,true);
  122. $msg .= "Resulting array:\n";
  123. $msg .= print_r($tmp_array,true);
  124. mail("kristian.ljungkvist@gmail.com","Modifier.class.php -- array_move -- Counts Differ",$msg);
  125. return $array; // return original, unmodified array in this case.
  126. }
  127. return $tmp_array;
  128. }
  129. function defunct_array_move($array,$from,$key,$to) {
  130. // Do some sanity checks on the parameters passed in.
  131. // echo "array_move(array,$from,$key,$to)<br/>";
  132. if(!is_array($array)) {
  133. // echo "invalid array argument passed to array_move!";
  134. return $array;
  135. }
  136. if(!$key) {
  137. // echo "key argument is empty in array_move!";
  138. return $array;
  139. }
  140. if($from == $to) {return $array;}
  141. if($from > count($array) || $from < 0) {
  142. // echo "'from' argument ($from) passed to array_move out of bounds!";
  143. return $array;
  144. }
  145. if($to > count($array) || $to < 0) {
  146. // echo "'to' argument ($to) passed to array_move out of bounds!";
  147. return $array;
  148. }
  149. /*
  150. if(array_search($key,$array,1) != ($from)) {
  151. echo "value $key is not at index ".($from)." in original array! (it's at index:".array_search($key,$array,1).")<br/>\n";
  152. return($array);
  153. }
  154. */
  155. if($this->index_of_key($array,$key) != $from) {
  156. // echo "value $key is not at index ".($from)." in original array! (it's at index:".$this->index_of_key($array,$key).")<br/>\n";
  157. return($array);
  158. }
  159. // OK, things look reasonable. Now copy the item to be moved
  160. $contents = $array[$key];
  161. // First, remove the original instance
  162. $left = array_slice ($array, 0, $from-1);
  163. $right = array_slice ($array, $from);
  164. // Put the array back together, sans the removed item.
  165. $array = array_merge ($left, $right);
  166. // Now, insert the removed item in it's new position
  167. $left = array_slice ($array, 0, $to-1);
  168. $right = array_slice ($array, $to-1);
  169. $left[$key] = $contents;
  170. // Put the arrray back together again.
  171. $array = array_merge ($left,$right);
  172. // Now, test that the item was indeed inserted at the correct spot.
  173. if($this->index_of_key($array,$key) != ($to -1)) {
  174. // echo "array_move:: item $key was inserted into position ".$this->index_of_key($array,$key)." rather than ".($to -1)."! Error?<br/>\n";
  175. } else {
  176. // echo "array_move: Success!<br/>\n";
  177. }
  178. return $array;
  179. }
  180. /**
  181. * index_of_key
  182. *
  183. * Finds the index of a given key in an associative array., After much research, it doesn't appear that
  184. * PHP provides an easy way to get this value.
  185. *
  186. * Kristian Ljungkvist 09/19/2007.
  187. **/
  188. function index_of_key($array,$key) {
  189. $cur_i = 0;
  190. foreach($array as $SK => $MSD) {
  191. if(strpos($SK, '_') === 0) {
  192. continue;
  193. }
  194. if($SK == $key) {
  195. return $cur_i;
  196. }
  197. $cur_i++;
  198. }
  199. return $cur_i;
  200. }
  201. /**
  202. * key_at_index
  203. *
  204. * Finds the key of a given entry in an associative array., After much research, it doesn't appear that
  205. * PHP provides an easy way to get this value.
  206. *
  207. * Kristian Ljungkvist 09/19/2007.
  208. **/
  209. function key_at_index($array,$index) {
  210. $cur_index = 0;
  211. if(index > count($array)) {
  212. // echo "index passed to key_at_index out of bounds";
  213. return "Error";
  214. }
  215. $i=0;
  216. foreach($array as $SK => $MSD) {
  217. if(strpos($SK, '_') === 0) {
  218. continue;
  219. }
  220. if($i == $index) {
  221. return $SK;
  222. }
  223. $i++;
  224. }
  225. }
  226. }
  227. /**
  228. * Recommender Ranking Modifier Factory Class
  229. *
  230. * The ModifierFactory implements the factory design pattern
  231. * Creates concrete Modifier objects of various types.
  232. *
  233. * Kristian Ljungkvist Created 08/20/07
  234. * Note -- Requires PHP 5
  235. *
  236. * Copyright 2007 Science Buddies. All Rights Reserved
  237. */
  238. class ModifierFactory {
  239. function createModifier($modifier_name) {
  240. switch($modifier_name) {
  241. case 'NewProjectRandomizer':
  242. return new NewProjectRandomizerModifier;
  243. break;
  244. case 'ProjectRandomizer':
  245. return new ProjectRandomizerModifier;
  246. break;
  247. case 'Paginator':
  248. return new PaginatorModifier;
  249. break;
  250. case 'AreaAssignment':
  251. return new AreaAssignmentModifier;
  252. break;
  253. case 'DifficultyLevel':
  254. return new DifficultyLevelModifier;
  255. break;
  256. case 'TimeRequired':
  257. return new TimeRequiredModifier;
  258. break;
  259. case 'Deduper':
  260. return new DeduperModifier;
  261. break;
  262. case 'FirstPageNotRandom':
  263. return new FirstPageNotRandomModifier;
  264. break;
  265. case 'HoneyPot':
  266. return new HoneyPotModifier;
  267. break;
  268. case 'InterestArea':
  269. return new InterestAreaModifier;
  270. break;
  271. }
  272. //echo "Invalid Modifier name passed to createModifier Factory method!<br/>";
  273. return false;
  274. }
  275. }
  276. class PaginatorModifier extends Modifier {
  277. /**
  278. * process method
  279. *
  280. * Overrides the Modifier Base class process method.
  281. *
  282. * The Paginator grabs the correct chunk of the ranking list based on the current page
  283. * and the configuration variable MaxProjectsPerPage in the recommender_config table.
  284. *
  285. * @author Kristian Ljungkvist
  286. */
  287. function process($ranking_list,$individual_profile,$state_array,$configuration,$sk_summary)
  288. {
  289. //echo "In PaginatorModifier<br/>";
  290. $result_array = array();
  291. // Grab the current page and the MaxProjectsPerPage configuration setting
  292. $projects_per_page = $configuration['MaxProjectsPerPage'];
  293. $page = $state_array['p'];
  294. if(!$page) {
  295. $page = 1; // default to page # 1.
  296. }
  297. // Now, copy only the chunk of projects that correspond to the current page.
  298. $cur_index = 0;
  299. foreach($ranking_list as $SK => $MSD) {
  300. // escape the header fields that are prefixed with an underline, -- KEL 10/18/07
  301. if(strpos($SK, '_') === 0) {
  302. continue;
  303. }
  304. $cur_index++;
  305. if($cur_index > (($page -1 )* $projects_per_page) && $cur_index <= ($page * $projects_per_page)) {
  306. $result_array[$SK] = $MSD;
  307. }
  308. }
  309. return $result_array;
  310. }
  311. }
  312. class AreaAssignmentModifier extends Modifier {
  313. var $interest_area_array = array('phys_sci' => array('Aero','Astro','Chem','Music','Phys','PhysSci','AeroEng','ApMech','ChemE','CE','CompEng','Elec','EnvEng','MatSci','ME','Robotics','Engr','CompSci','FoodSci','Photo'),
  314. 'life_sci' => array('BioChem','Genom','MamBio','MicroBio','Pharm','PlantBio','Zoo','LifeSci','EnvSci','Sports'),
  315. 'earth_sci' => array('EnvSci','Geo','OceanSci','Weather','EarthSci'),
  316. 'env_sci' => array('EnvSci','Geo','OceanSci','Weather','EarthSci'),
  317. 'engr' => array('Aero','AeroEng','ApMech','ChemE','CE','CompEng','Elec','EnvEng','MatSci','ME','Robotics','Engr','CompSci','Photo')
  318. );
  319. /**
  320. * process method
  321. *
  322. * Overrides the Modifier Base class process method.
  323. *
  324. * The AreaAssignment modifier filters out project ideas from the ranking list that don't relate to the individual's
  325. * interest area assignment by their teacher.
  326. *
  327. * @author Kristian Ljungkvist
  328. */
  329. function process($ranking_list,$individual_profile,$state_array,$configuration,$sk_summary)
  330. {
  331. //echo "In AreaAssignmentModifier<br/>";
  332. $result_array = array();
  333. // Grab this user's AreaAssignment from the individual_profile.
  334. $area_assignment = $individual_profile['AreaAssignment'];
  335. // If the teacher didn't restrict the interest area, just do nothing.
  336. if(!$area_assignment || $area_assignment == 'none') {
  337. return $ranking_list;
  338. }
  339. // OK, we have an interest area restriction. Loop through the ranking list, filtering out any projects that do
  340. // not map to that interest area, based on a lookup in the interest_area array.
  341. foreach($ranking_list as $SK => $MSD) {
  342. // escape the header fields that are prefixed with an underline, -- KEL 10/18/07
  343. if(strpos($SK, '_') === 0) {
  344. continue;
  345. }
  346. // get the subarea from the file name
  347. preg_match('/^([^_]+)/', $SK, $match);
  348. $subarea = $match[0];
  349. if($sk_summary[$subarea]['SKs'][$SK]['Questions']['Type'] == 'Test') {
  350. $result_array[$SK] = $MSD; // Don't filter out "Test" type projects, since these are the neighborhood identifiers.
  351. continue;
  352. }
  353. // Check if this subarea is listed for the individual's area_assignment
  354. if(in_array($subarea,$this->interest_area_array[$area_assignment])) {
  355. $result_array[$SK] = $MSD;
  356. }
  357. }
  358. return $result_array;
  359. }
  360. }
  361. class InterestAreaModifier extends Modifier {
  362. /**
  363. * process method
  364. *
  365. * Overrides the Modifier Base class process method.
  366. *
  367. * The InterestArea modifier filters out project ideas from the ranking list that are not in the specified interest area.
  368. *
  369. * @author Kristian Ljungkvist 05/09/2008
  370. */
  371. function process($ranking_list,$individual_profile,$state_array,$configuration,$sk_summary)
  372. {
  373. //echo "In InterestAreaModifier<br/>";
  374. $result_array = array();
  375. $interest_area = $state_array['ia'];
  376. // If no interest area was specified, just do nothing.
  377. if(!$interest_area || $interest_area == 'none') {
  378. return $ranking_list;
  379. }
  380. // OK, we have an interest area restriction. Loop through the ranking list, filtering out any projects that are
  381. // not in that interest area.
  382. foreach($ranking_list as $SK => $MSD) {
  383. // escape the header fields that are prefixed with an underline, -- KEL 10/18/07
  384. if(strpos($SK, '_') === 0) {
  385. continue;
  386. }
  387. // get the subarea from the file name
  388. preg_match('/^([^_]+)/', $SK, $match);
  389. $subarea = $match[0];
  390. // Check if this subarea matches the current one
  391. if($subarea == $interest_area) {
  392. $result_array[$SK] = $MSD;
  393. }
  394. }
  395. return $result_array;
  396. }
  397. }
  398. class NewProjectRandomizerModifier extends Modifier {
  399. /**
  400. * process method
  401. *
  402. * Each Modifier must override this method.
  403. * @author Kristian Ljungkvist
  404. */
  405. /*
  406. New Project Idea Randomizer Modifier
  407. We want to perturb (up or down) the ranking for projects that have few measures of satisfaction and that are loosely related to the top ranked project.
  408. We define related projects by a lookup table that maps interest areas. For now, we'll use the same table as for
  409. the Area of Science Limitations.
  410. This randomizer only moves low-MOS projects that are in the same category (based on the above table) as the
  411. top-ranked project. This way, we avoid potentially promoting projects that are totally unrelated to the user's area of interest.
  412. Note also that this is separate from the global Area Of Science filtering that happens when the user has been given an area of science by his or her teacher.
  413. So, for those low-MOS projects within the right range of subareas:
  414. Let (MaturityDefinitionCutoff - nMOS) represent the percentage of times a project is randomized. Let's say nMOSCutoff is 100.
  415. So, a project with 0 MOS is randomized each time. A project with 50 MOS is randomized half the time, etc.
  416. This would essentially be a linear function. Each time a ranking is produced, those projects with
  417. nMOS < MaturityDefinitionCutoff are given the chance to be randomized. The randomizer "tosses a coin" based on
  418. nMos/MaturityDefinitionCutoff for each such project to decide if it should be randomized.
  419. As the Measures of Satisfaction increase, the project's ranking behavior becomes more stable. Once
  420. the project has 100 (or whatever cut-off we decide on) MOS, it is no longer randomized by this modifier, and is always ranked
  421. per it's profile as all other projects are. Note that a separate randomizer modifier might perturb the global ranking later.
  422. The project profile data includes the # of measures of satisfaction. This data would be computed by
  423. the batch process that updates the project profile data. By doing this, the recommender doesn't have to do expensive
  424. database lookups to properly randomize rankings.
  425. The range within which a project can be shuffled is determined by a field in the recommender_config table: NewProjectShuffleRange.
  426. The range is the original location plus/minus this constant. Any location within that range is equally likely.
  427. For example: Project A is originally ranked at slot #25. NewProjectShuffleRange is 10. The new location can be
  428. anywhere from 15 to 35.
  429. */
  430. function process($ranking_list,$individual_profile,$state_array,$configuration,$sk_summary)
  431. {
  432. //echo "In NewProjectRandomizerModifier<br/>";
  433. // First, determine if we're being called the first time this ranking has been processed.
  434. if($state_array['p'] > 1) {
  435. // Do nothing if this list has already been processed.
  436. return $ranking_list;
  437. }
  438. $mos_cutoff = $configuration['MaturityDefinitionCutoff'];
  439. $shuffle_range = $configuration['NewProjectShuffleRange'];
  440. $first_page_projects_not_random = $configuration['FirstPageProjectsNotRandom'];
  441. $projects_per_page = $configuration['MaxProjectsPerPage'];
  442. $only_seed_in_top_interest_area = $configuration['OnlySeedInTopInterestArea'];
  443. // Get the interest area for the top-ranked project.
  444. $top_project = key($ranking_list);
  445. preg_match('/^([^_]+)/', $top_project, $match);
  446. $top_project_interest_area = $match[0];
  447. //echo "top project interest area: $top_project_interest_area<BR/>";
  448. // As we traverse the assoc array, we keep an internal index or counter.
  449. // If we're at index = 20 and we need to move it to 15, we first remove the current item, and then insert it at index=15.
  450. $cur_index = 0;
  451. $reordered_ranking_list = $ranking_list; // Copy the original ranking list.
  452. foreach($ranking_list as $SK => $MSD) {
  453. // escape the header fields that are prefixed with an underline, -- KEL 10/18/07
  454. if(strpos($SK, '_') === 0) {
  455. continue;
  456. }
  457. // KEL 10/04/07 -- Skip any items on the first page if recommender_config.FirstPageProjectsNotRandom is 1.
  458. if($first_page_projects_not_random && ($cur_index < $projects_per_page)) {
  459. $cur_index++;
  460. continue;
  461. }
  462. // get the subarea from the file name
  463. preg_match('/^([^_]+)/', $SK, $match);
  464. $subarea = $match[0];
  465. // Check if this subarea matches that of the top+ranked project (if switch is set in config to do so.)
  466. if(!$only_seed_in_top_interest_area || ($subarea == $top_project_interest_area)) {
  467. // Check if the project is "immature".
  468. // This is defined as "Maturity < MaturityDefinitionCutoff
  469. //echo "$SK:Maturity = ".$sk_summary[$subarea]['SKs'][$SK]['Questions']['Maturity']."<br/>";
  470. if($sk_summary[$subarea]['SKs'][$SK]['Questions']['Maturity'] < $mos_cutoff) {
  471. // toss a coin (preportional to (MaturityDefinitionCutoff - nMOS) )to determine if this project should be shuffled this time.
  472. if(rand(0,$mos_cutoff) >= $sk_summary[$subarea]['SKs'][$SK]['Questions']['Maturity']) {
  473. // Shuffle this project in the ranking by a random offset in the range +/- shuffle_range.
  474. // Get the current position in the reordered ranking list
  475. $current_position = $this->index_of_key($reordered_ranking_list,$SK);
  476. $low_shuffle_range = $current_position - $shuffle_range;
  477. if($low_shuffle_range < 0) { $low_shuffle_range = 0;}
  478. // KEL 10/04/07 -- Make sure no randomized projects end up getting promoted to the first page if configured not to.
  479. if($first_page_projects_not_random && ($low_shuffle_range < $projects_per_page)) {
  480. $low_shuffle_range = $projects_per_page;
  481. }
  482. $high_shuffle_range = $current_position + $shuffle_range;
  483. if($high_shuffle_range > count($ranking_list)) {
  484. $high_shuffle_range = count($ranking_list);
  485. }
  486. $random_new_index = rand($low_shuffle_range,$high_shuffle_range);
  487. // move the entry to this new position.
  488. //echo "NewProjectRandomizer: moving $SK from position $current_position to position $random_new_index<br/>";
  489. $reordered_ranking_list = $this->array_move($reordered_ranking_list,$current_position,$SK,$random_new_index);
  490. }
  491. }
  492. }
  493. $cur_index++;
  494. }
  495. /*
  496. echo "Original ranking list:<br/>";
  497. print_r($ranking_list);
  498. echo "<hr/>";
  499. echo "Reordered ranking list:<br/>";
  500. print_r($reordered_ranking_list);
  501. echo "<hr/>";
  502. */
  503. return $reordered_ranking_list;
  504. }
  505. }
  506. class ProjectRandomizerModifier extends Modifier {
  507. /**
  508. * process method
  509. *
  510. * Each Modifier must override this method.
  511. * @author Kristian Ljungkvist
  512. */
  513. /**
  514. "Random" Randomizer Modifier
  515. This randomizer differs from NewProjectRandomizer in that it randomizes the list without regard to maturity. It also
  516. works differently in that it limits the number of items to perturb by the RandomProjectsRatio.
  517. The idea behind this modifier is to perturb the ranking of more mature projects as well so that the rankings don't
  518. ossify but have a chance to evolve.
  519. */
  520. function process($ranking_list,$individual_profile,$state_array,$configuration,$sk_summary)
  521. {
  522. //echo "In RandomizerModifier<br/>";
  523. // First, determine if we're being called the first time this ranking has been processed.
  524. if($state_array['p'] > 1) {
  525. // Do nothing if this list has already been processed.
  526. return $ranking_list;
  527. }
  528. $shuffle_range = $configuration['ProjectShuffleRange'];
  529. $first_page_projects_not_random = $configuration['FirstPageProjectsNotRandom'];
  530. $projects_per_page = $configuration['MaxProjectsPerPage'];
  531. $random_projects_ratio = $configuration['RandomProjectsRatio'];
  532. /**
  533. Algorithm:
  534. Find total # of items in ranking
  535. subtract one page worth (if FirstPageProjectsNotRandom is true)
  536. Now multiply this number by RandomProjectsRatio
  537. This is the number of items to randomize (randomized_items).
  538. loop for randomized_items iterations:
  539. find a random number between 1+PageLength and total lines in ranking
  540. Move it a random number of steps +/- $shuffle_range.
  541. end loop;
  542. */
  543. $length_of_ranking = count($ranking_list);
  544. $beginning_of_range_to_randomize = 0;
  545. if($first_page_projects_not_random) {
  546. $length_of_ranking -= $projects_per_page;
  547. $beginning_of_range_to_randomize = ($projects_per_page);
  548. }
  549. //echo "<hr/>ProjectRandomizer:length of ranking: $length_of_ranking, beginning_of_range_to_randomize: $beginning_of_range_to_randomize, projects per page: $projects_per_page<hr/>";
  550. $number_of_items_to_randomize = $random_projects_ratio * $length_of_ranking;
  551. $reordered_ranking_list = $ranking_list; // Copy the original ranking list.
  552. for($i=1;$i < $number_of_items_to_randomize;$i++) {
  553. $cur_index = rand($beginning_of_range_to_randomize,(count($reordered_ranking_list)));
  554. // Shuffle this project in the ranking by a random offset in the range +/- shuffle_range.
  555. $low_shuffle_range = $cur_index - $shuffle_range;
  556. if($low_shuffle_range < 0) { $low_shuffle_range = 0;}
  557. // KEL 10/04/07 -- Make sure no randomized projects end up getting promoted to the first page if configured not to.
  558. if($first_page_projects_not_random && ($low_shuffle_range < $projects_per_page)) {
  559. $low_shuffle_range = $projects_per_page;
  560. }
  561. $high_shuffle_range = $cur_index + $shuffle_range;
  562. if($high_shuffle_range > count($reordered_ranking_list)) {
  563. $high_shuffle_range = count($reordered_ranking_list);
  564. }
  565. $random_new_index = rand($low_shuffle_range,$high_shuffle_range);
  566. // Get the current position in the reordered ranking list
  567. // move the entry to this new position.
  568. $SK = Modifier::key_at_index($reordered_ranking_list,$cur_index);
  569. //echo "ProjectRandomizer: moving $SK from position $cur_index to position $random_new_index<br/>";
  570. $reordered_ranking_list = $this->array_move($reordered_ranking_list,$cur_index,$SK,$random_new_index);
  571. }
  572. return $reordered_ranking_list;
  573. }
  574. }
  575. class DifficultyLevelModifier extends Modifier {
  576. var $grade_to_difficulty_map = array('K' => array(1,2,3,4),
  577. '1' => array(1,2,3,4),
  578. '2' => array(1,2,3,4),
  579. '3' => array(1,2,3,4),
  580. '4' => array(1,2,3,4),
  581. '5' => array(1,2,3,4),
  582. '6' => array(4,5,6,7),
  583. '7' => array(4,5,6,7),
  584. '8' => array(4,5,6,7),
  585. '9' => array(6,7,8,9,10),
  586. '10' => array(6,7,8,9,10),
  587. '11' => array(6,7,8,9,10),
  588. '12' => array(6,7,8,9,10),
  589. 'Adult' => array(1,2,3,4,5,6,7,8,9,10)
  590. );
  591. /**
  592. * process method
  593. *
  594. * Overrides the Modifier Base class process method.
  595. *
  596. * The AreaAssignment modifier filters out project ideas from the ranking list that don't relate to the individual's
  597. * interest area assignment by their teacher.
  598. *
  599. * @author Kristian Ljungkvist
  600. */
  601. function process($ranking_list,$individual_profile,$state_array,$configuration,$sk_summary)
  602. {
  603. //echo "In DifficultyLevelModifier<br/>";
  604. $result_array = array();
  605. // Grab this user's GradeLevel or currently set difficultylevel override based on "show harder/easier" from the individual_profile.
  606. if($individual_profile['DifficultyLevel']) {
  607. $difficulty_level = $individual_profile['DifficultyLevel'];
  608. } else {
  609. $difficulty_level = $individual_profile['GradeLevel'];
  610. }
  611. //echo "DifficultyLevelModifier::difficulty_level = $difficulty_level<br/>";
  612. // If the user is an adult, just do nothing.
  613. if($difficulty_level == 'Adult' || !$difficulty_level) {
  614. return $ranking_list;
  615. }
  616. // OK, we have a difficulty level restriction. Loop through the ranking list, filtering out any projects that do
  617. // not map to the appropriate range, ased on a lookup in the grade_to_difficulty_map.
  618. foreach($ranking_list as $SK => $MSD) {
  619. // escape the header fields that are prefixed with an underline, -- KEL 10/18/07
  620. if(strpos($SK, '_') === 0) {
  621. continue;
  622. }
  623. // get the subarea from the file name
  624. preg_match('/^([^_]+)/', $SK, $match);
  625. $subarea = $match[0];
  626. // Get the DifficultyLevel_Low from the sk_profile.
  627. $project_difficulty_low = $sk_summary[$subarea]['SKs'][$SK]['Questions']['DifficultyLevel_Low'];
  628. $project_difficulty_high = $sk_summary[$subarea]['SKs'][$SK]['Questions']['DifficultyLevel_High'];
  629. //echo "project_difficulty[$SK] = $project_difficulty<br/>";
  630. // echo "project:$SK -- DifficultyLevel_Low = $project_difficulty_low, DifficultyLevel_High = $project_difficulty_high<br/>";
  631. if($project_difficulty_low == 1 && $project_difficulty_high == 10) {
  632. $result_array[$SK] = $MSD;
  633. continue;
  634. }
  635. // Check if this subarea is listed for the individual's area_assignment
  636. if((in_array($project_difficulty_low,$this->grade_to_difficulty_map[$difficulty_level]) || (in_array($project_difficulty_high,$this->grade_to_difficulty_map[$difficulty_level])))) {
  637. $result_array[$SK] = $MSD;
  638. }
  639. }
  640. return $result_array;
  641. }
  642. }
  643. class TimeRequiredModifier extends Modifier {
  644. var $time_required_map = array('tomorrow' => array('Very short'),
  645. 'week' => array('Very short','Short','Average'),
  646. 'several_weeks' => array('Very short','Short','Average','Long'),
  647. 'month' => array('Very short','Short','Average','Long','Very long'));
  648. /**
  649. * process method
  650. *
  651. * Overrides the Modifier Base class process method.
  652. *
  653. * TheTimeRequred modifier filters out project ideas from the ranking list that require more time than the user indicated he or she has.
  654. *
  655. * @author Kristian Ljungkvist
  656. */
  657. function process($ranking_list,$individual_profile,$state_array,$configuration,$sk_summary)
  658. {
  659. //echo "In TimeRequiredModifier<br/>";
  660. $result_array = array();
  661. // Grab this user's ProjectDuration
  662. $project_duration = $individual_profile['ProjectDuration'];
  663. // If time available is a month or more, return the full list.
  664. if($project_duration == 'month' || !$project_duration) {
  665. return $ranking_list;
  666. }
  667. // OK, we have a project duration restriction. Loop through the ranking list, filtering out any projects that do
  668. // not map to the appropriate range, ased on a lookup in the time_required_map.
  669. foreach($ranking_list as $SK => $MSD) {
  670. // escape the header fields that are prefixed with an underline, -- KEL 10/18/07
  671. if(strpos($SK, '_') === 0) {
  672. continue;
  673. }
  674. // get the subarea from the file name
  675. preg_match('/^([^_]+)/', $SK, $match);
  676. $subarea = $match[0];
  677. // Get the TimeRequired from the sk_profile.
  678. if($sk_summary[$subarea]['SKs'][$SK]['Questions']['Type'] == 'Test') {
  679. $result_array[$SK] = $MSD;
  680. continue;
  681. }
  682. $project_time = $sk_summary[$subarea]['SKs'][$SK]['Questions']['TimeRequired'];
  683. //echo "project_time[$SK] = $project_time<br/>";
  684. // Check if this subarea is listed for the individual's area_assignment
  685. foreach($this->time_required_map[$project_duration] as $cur_time) {
  686. //echo "Haystack:$project_time, Needle:$cur_time<br/>";
  687. if(stripos($project_time,$cur_time)!== false) {
  688. // echo "MATCH!<br/>";
  689. $result_array[$SK] = $MSD;
  690. break;
  691. }
  692. }
  693. }
  694. return $result_array;
  695. }
  696. }
  697. class DeduperModifier extends Modifier {
  698. var $groups_seen = array();
  699. /**
  700. * process method
  701. *
  702. * Overrides the Modifier Base class process method.
  703. *
  704. * Deduper modifier filters out project ideas with the same GroupID as one already shown.
  705. *
  706. * @author Kristian Ljungkvist
  707. */
  708. function process($ranking_list,$individual_profile,$state_array,$configuration,$sk_summary)
  709. {
  710. // echo "In DeduperModifier<br/>";
  711. // echo "ranking_list passed in:<br/>";
  712. // print_r($ranking_list);
  713. $result_array = array();
  714. // Loop through the ranking list, remembering each newly encountered GroupID. If an item has a non-null GroupID that we've
  715. // already seen, remove it from the ranking.
  716. foreach($ranking_list as $SK => $MSD) {
  717. // escape the header fields that are prefixed with an underline, -- KEL 10/18/07
  718. if(strpos($SK, '_') === 0) {
  719. continue;
  720. }
  721. // get the subarea from the file name
  722. preg_match('/^([^_]+)/', $SK, $match);
  723. $subarea = $match[0];
  724. // Get the GroupID from the sk_profile.
  725. $group_id = $sk_summary[$subarea]['SKs'][$SK]['Questions']['GroupID'];
  726. //echo "GroupID[$SK] = $group_id<br/>";
  727. if($group_id && $group_id != 'NULL' && $group_id != 'null') {
  728. //echo "GroupID[$SK] = $group_id<br/>";
  729. // Check if this GroupID has already been included
  730. if(in_array($group_id,$this->groups_seen)) {
  731. //echo "$group_id already seen. Skipping...<br/>";
  732. continue;
  733. }
  734. // OK, so we have a new group ID. SImply add it to the list.
  735. $this->groups_seen[] = $group_id;
  736. }
  737. $result_array[$SK] = $MSD;
  738. }
  739. // echo "<br/>groups seen: <br/>";
  740. // print_r($this->groups_seen);
  741. // echo "<br/>ranking list returned:<br/>";
  742. // print_r($result_array);
  743. return $result_array;
  744. }
  745. }
  746. class HoneyPotModifier extends Modifier {
  747. var $groups_seen = array();
  748. /**
  749. * process method
  750. *
  751. * Overrides the Modifier Base class process method.
  752. *
  753. * Honeypot modifier filters out honey pot projects. Will eventually trigger some awesome code.
  754. *
  755. * @author Kristian Ljungkvist
  756. */
  757. function process($ranking_list,$individual_profile,$state_array,$configuration,$sk_summary)
  758. {
  759. //echo "In DeduperModifier<br/>";
  760. $result_array = array();
  761. // Loop through the ranking list, remembering each newly encountered GroupID. If an item has a non-null GroupID that we've
  762. // already seen, remove it from the ranking.
  763. foreach($ranking_list as $SK => $MSD) {
  764. // escape the header fields that are prefixed with an underline, -- KEL 10/18/07
  765. if(strpos($SK, '_') === 0) {
  766. continue;
  767. }
  768. // get the subarea from the file name
  769. preg_match('/^([^_]+)/', $SK, $match);
  770. $subarea = $match[0];
  771. // Check if this is a Honey pot.
  772. if($subarea == 'HoneyPot') {
  773. continue; // Skip it.
  774. }
  775. $result_array[$SK] = $MSD; // Add non-honeypots to our results.
  776. }
  777. return $result_array;
  778. }
  779. }
  780. class FirstPageNotRandomModifier extends Modifier {
  781. var $groups_seen = array();
  782. /**
  783. * process method
  784. *
  785. * Overrides the Modifier Base class process method.
  786. *
  787. * FirstPageNotRandom modifier ensures that all items on the first page are in order if the configuration flag is set.
  788. *
  789. * @author Kristian Ljungkvist
  790. */
  791. /*
  792. function process($ranking_list,$individual_profile,$state_array,$configuration,$sk_summary)
  793. {
  794. $result_array = array();
  795. echo "<hr/>In FirstPageNotRandomModifier<hr/>";
  796. $shuffle_range = $configuration['ProjectShuffleRange'];
  797. $first_page_projects_not_random = $configuration['FirstPageProjectsNotRandom'];
  798. $projects_per_page = $configuration['MaxProjectsPerPage'];
  799. // First, check if FirstPageNotRandom flag is set. If not, do nothing.
  800. if(!$first_page_projects_not_random) {
  801. return $ranking_list;
  802. }
  803. // Loop through the items on the first page, as defined by $projects_per_page.
  804. // If an item has a higher MSD than the one following it, it was inserted randomly and needs to be moved.
  805. // If so, move the item to a spot in the range from $projects_per_page to $projects_per_page + $shuffle_range.
  806. $cur_index = 0;
  807. $prev_msd = 0;
  808. $prev_sk = '';
  809. $cur_msd = 0;
  810. $reordered_ranking_list = $ranking_list; // Copy the original ranking list.
  811. echo "<hr/>before:<hr/>";
  812. print_r(array_slice($reordered_ranking_list,0,10));
  813. foreach($ranking_list as $SK => $MSD) {
  814. // escape the header fields that are prefixed with an underline, -- KEL 10/18/07
  815. if(strpos($SK, '_') === 0) {
  816. continue;
  817. }
  818. if($cur_index > $projects_per_page) {
  819. //print_r($reordered_ranking_list);
  820. echo "<hr/>after:<hr/>";
  821. print_r(array_slice($reordered_ranking_list,0,10));
  822. return $reordered_ranking_list;
  823. }
  824. $cur_msd = $MSD;
  825. // echo "MSD[$cur_index]($SK):$cur_msd -- MSD[".($cur_index-1)."]($prev_sk):$prev_msd<br/>";
  826. if($cur_msd < $prev_msd) {
  827. //echo "FirstPageNotRandomModifier:: Item $SK is out of order on the first page. Moving it. <br/>";
  828. // previous item was out of order. Move it.
  829. $random_new_index = rand($projects_per_page,$projects_per_page + $shuffle_range);
  830. // Get the current position in the reordered ranking list
  831. $current_position = $this->index_of_key($reordered_ranking_list,$prev_sk);
  832. echo "FirstPageNotRandomModifier:: Moving Item $prev_sk from position ".($current_position)." in the reordered list to position $random_new_index. <br/>";
  833. echo "<hr/>before array_move:<hr/>";
  834. print_r(array_slice($reordered_ranking_list,0,10));
  835. $reordered_ranking_list = $this->array_move($reordered_ranking_list,($current_position+1),$prev_sk,$random_new_index);
  836. echo "<hr/>after array_move:<hr/>";
  837. print_r(array_slice($reordered_ranking_list,0,10));
  838. // print_r($reordered_ranking_list);
  839. }
  840. $prev_msd = $cur_msd;
  841. $prev_sk = $SK;
  842. $cur_index++;
  843. }
  844. return $reordered_ranking_list;
  845. }
  846. */
  847. function process($ranking_list,$individual_profile,$state_array,$configuration,$sk_summary)
  848. {
  849. $result_array = array();
  850. // echo "<hr/>In FirstPageNotRandomModifier<hr/>";
  851. $shuffle_range = $configuration['ProjectShuffleRange'];
  852. $first_page_projects_not_random = $configuration['FirstPageProjectsNotRandom'];
  853. $projects_per_page = $configuration['MaxProjectsPerPage'];
  854. // First, check if FirstPageNotRandom flag is set. If not, do nothing.
  855. if(!$first_page_projects_not_random) {
  856. return $ranking_list;
  857. }
  858. // echo "<hr/>before:<hr/>";
  859. // print_r(array_slice($ranking_list,0,10));
  860. // Copy the ranking list
  861. $copy_ranking_list = $ranking_list;
  862. // Sort the copy
  863. $copy_ranking_list = array_slice($copy_ranking_list,0,$projects_per_page);
  864. // echo "<hr/>FirstPageNotRandomNotifier::unsorted top-ten:<br/>";
  865. // $this->print_r_html(array_slice($copy_ranking_list,0,10));
  866. asort($copy_ranking_list);
  867. // For the top $projects_per_page items:
  868. // Move the SK into the corresponding slot in the reordered ranking.
  869. $copy_ranking_list = array_slice($copy_ranking_list,0,$projects_per_page);
  870. // echo "<hr/>FirstPageNotRandomNotifier::sorted top-ten:<br/>";
  871. // $this->print_r_html($copy_ranking_list);
  872. //$cur_index = 1;
  873. foreach($copy_ranking_list as $SK => $MSD) {
  874. // Find the original location of this SK in the ranking list.
  875. $current_position = $this->index_of_key($ranking_list,$SK);
  876. $location_in_sorted_list = $this->index_of_key($copy_ranking_list,$SK);
  877. $ranking_list = $this->array_move($ranking_list,($current_position),$SK,$location_in_sorted_list);
  878. // $this->print_r_html(array_slice($ranking_list,0,10));
  879. // $cur_index++;
  880. }
  881. // echo "<hr/>after:<hr/>";
  882. // $this->print_r_html(array_slice($ranking_list,0,10));
  883. return $ranking_list;
  884. }
  885. function print_r_html($data,$return_data=false)
  886. {
  887. $data = print_r($data,true);
  888. $data = str_replace( " ","&nbsp;", $data);
  889. $data = str_replace( "\r\n","<br>\r\n", $data);
  890. $data = str_replace( "\r","<br>\r", $data);
  891. $data = str_replace( "\n","<br>\n", $data);
  892. if (!$return_data)
  893. echo $data;
  894. else
  895. return $data;
  896. }
  897. }
  898. ?>