|
Moodle
2.2.1
http://www.collinsharper.com
|
00001 <?php 00002 00003 // This file is part of Moodle - http://moodle.org/ 00004 // 00005 // Moodle is free software: you can redistribute it and/or modify 00006 // it under the terms of the GNU General Public License as published by 00007 // the Free Software Foundation, either version 3 of the License, or 00008 // (at your option) any later version. 00009 // 00010 // Moodle is distributed in the hope that it will be useful, 00011 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00013 // GNU General Public License for more details. 00014 // 00015 // You should have received a copy of the GNU General Public License 00016 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 00017 00027 defined('MOODLE_INTERNAL') || die(); 00028 00029 global $CFG; // access to global variables during unit test 00030 00031 require_once(dirname(dirname(__FILE__)) . '/lib.php'); // interface definition 00032 require_once(dirname(dirname(dirname(__FILE__))) . '/locallib.php'); // workshop internal API 00033 require_once(dirname(__FILE__) . '/settings_form.php'); // settings form 00034 00038 class workshop_random_allocator implements workshop_allocator { 00039 00041 const MSG_SUCCESS = 1; 00042 00044 const USERTYPE_AUTHOR = 1; 00045 const USERTYPE_REVIEWER = 2; 00046 00048 protected $workshop; 00049 00051 protected $mform; 00052 00056 public function __construct(workshop $workshop) { 00057 $this->workshop = $workshop; 00058 } 00059 00063 public function init() { 00064 global $PAGE; 00065 00066 $customdata = array(); 00067 $customdata['workshop'] = $this->workshop; 00068 $this->mform = new workshop_random_allocator_form($PAGE->url, $customdata); 00069 if ($this->mform->is_cancelled()) { 00070 redirect($PAGE->url->out(false)); 00071 } else if ($settings = $this->mform->get_data()) { 00072 // process validated data 00073 if (!confirm_sesskey()) { 00074 throw new moodle_exception('confirmsesskeybad'); 00075 } 00076 $o = array(); // list of output messages 00077 $numofreviews = required_param('numofreviews', PARAM_INT); 00078 $numper = required_param('numper', PARAM_INT); 00079 $excludesamegroup = optional_param('excludesamegroup', false, PARAM_BOOL); 00080 $removecurrent = optional_param('removecurrent', false, PARAM_BOOL); 00081 $assesswosubmission = optional_param('assesswosubmission', false, PARAM_BOOL); 00082 $addselfassessment = optional_param('addselfassessment', false, PARAM_BOOL); 00083 $musthavesubmission = empty($assesswosubmission); 00084 00085 $authors = $this->workshop->get_potential_authors(); 00086 $authors = $this->workshop->get_grouped($authors); 00087 $reviewers = $this->workshop->get_potential_reviewers($musthavesubmission); 00088 $reviewers = $this->workshop->get_grouped($reviewers); 00089 $assessments = $this->workshop->get_all_assessments(); 00090 00091 $newallocations = array(); // array of array(reviewer => reviewee) 00092 00093 if ($numofreviews) { 00094 if ($removecurrent) { 00095 // behave as if there were no current assessments 00096 $curassessments = array(); 00097 } else { 00098 $curassessments = $assessments; 00099 } 00100 $options = array(); 00101 $options['numofreviews'] = $numofreviews; 00102 $options['numper'] = $numper; 00103 $options['excludesamegroup'] = $excludesamegroup; 00104 $randomallocations = $this->random_allocation($authors, $reviewers, $curassessments, $o, $options); 00105 $newallocations = array_merge($newallocations, $randomallocations); 00106 $o[] = 'ok::' . get_string('numofrandomlyallocatedsubmissions', 'workshopallocation_random', count($randomallocations)); 00107 unset($randomallocations); 00108 } 00109 if ($addselfassessment) { 00110 $selfallocations = $this->self_allocation($authors, $reviewers, $assessments); 00111 $newallocations = array_merge($newallocations, $selfallocations); 00112 $o[] = 'ok::' . get_string('numofselfallocatedsubmissions', 'workshopallocation_random', count($selfallocations)); 00113 unset($selfallocations); 00114 } 00115 if (empty($newallocations)) { 00116 $o[] = 'info::' . get_string('noallocationtoadd', 'workshopallocation_random'); 00117 } else { 00118 $newnonexistingallocations = $newallocations; 00119 $this->filter_current_assessments($newnonexistingallocations, $assessments); 00120 $this->add_new_allocations($newnonexistingallocations, $authors, $reviewers); 00121 $allreviewers = $reviewers[0]; 00122 $allreviewersreloaded = false; 00123 foreach ($newallocations as $newallocation) { 00124 list($reviewerid, $authorid) = each($newallocation); 00125 $a = new stdClass(); 00126 if (isset($allreviewers[$reviewerid])) { 00127 $a->reviewername = fullname($allreviewers[$reviewerid]); 00128 } else { 00129 // this may happen if $musthavesubmission is true but the reviewer 00130 // of the re-used assessment has not submitted anything. let us reload 00131 // the list of reviewers name including those without their submission 00132 if (!$allreviewersreloaded) { 00133 $allreviewers = $this->workshop->get_potential_reviewers(false); 00134 $allreviewersreloaded = true; 00135 } 00136 if (isset($allreviewers[$reviewerid])) { 00137 $a->reviewername = fullname($allreviewers[$reviewerid]); 00138 } else { 00139 // this should not happen usually unless the list of participants was changed 00140 // in between two cycles of allocations 00141 $a->reviewername = '#'.$reviewerid; 00142 } 00143 } 00144 if (isset($authors[0][$authorid])) { 00145 $a->authorname = fullname($authors[0][$authorid]); 00146 } else { 00147 $a->authorname = '#'.$authorid; 00148 } 00149 if (in_array($newallocation, $newnonexistingallocations)) { 00150 $o[] = 'ok::indent::' . get_string('allocationaddeddetail', 'workshopallocation_random', $a); 00151 } else { 00152 $o[] = 'ok::indent::' . get_string('allocationreuseddetail', 'workshopallocation_random', $a); 00153 } 00154 } 00155 } 00156 if ($removecurrent) { 00157 $delassessments = $this->get_unkept_assessments($assessments, $newallocations, $addselfassessment); 00158 // random allocator should not be able to delete assessments that have already been graded 00159 // by reviewer 00160 $o[] = 'info::' . get_string('numofdeallocatedassessment', 'workshopallocation_random', count($delassessments)); 00161 foreach ($delassessments as $delassessmentkey => $delassessmentid) { 00162 $a = new stdclass(); 00163 $a->authorname = fullname((object)array( 00164 'lastname' => $assessments[$delassessmentid]->authorlastname, 00165 'firstname' => $assessments[$delassessmentid]->authorfirstname)); 00166 $a->reviewername = fullname((object)array( 00167 'lastname' => $assessments[$delassessmentid]->reviewerlastname, 00168 'firstname' => $assessments[$delassessmentid]->reviewerfirstname)); 00169 if (!is_null($assessments[$delassessmentid]->grade)) { 00170 $o[] = 'error::indent::' . get_string('allocationdeallocategraded', 'workshopallocation_random', $a); 00171 unset($delassessments[$delassessmentkey]); 00172 } else { 00173 $o[] = 'info::indent::' . get_string('assessmentdeleteddetail', 'workshopallocation_random', $a); 00174 } 00175 } 00176 $this->workshop->delete_assessment($delassessments); 00177 } 00178 return $o; 00179 } else { 00180 // this branch is executed if the form is submitted but the data 00181 // doesn't validate and the form should be redisplayed 00182 // or on the first display of the form. 00183 } 00184 } 00185 00189 public function ui() { 00190 global $PAGE; 00191 00192 $output = $PAGE->get_renderer('mod_workshop'); 00193 00194 $m = optional_param('m', null, PARAM_INT); // status message code 00195 $message = new workshop_message(); 00196 if ($m == self::MSG_SUCCESS) { 00197 $message->set_text(get_string('randomallocationdone', 'workshopallocation_random')); 00198 $message->set_type(workshop_message::TYPE_OK); 00199 } 00200 00201 $out = $output->container_start('random-allocator'); 00202 $out .= $output->render($message); 00203 // the nasty hack follows to bypass the sad fact that moodle quickforms do not allow to actually 00204 // return the HTML content, just to display it 00205 ob_start(); 00206 $this->mform->display(); 00207 $out .= ob_get_contents(); 00208 ob_end_clean(); 00209 00210 // if there are some not-grouped participant in a group mode, warn the user 00211 $gmode = groups_get_activity_groupmode($this->workshop->cm, $this->workshop->course); 00212 if (VISIBLEGROUPS == $gmode or SEPARATEGROUPS == $gmode) { 00213 $users = $this->workshop->get_potential_authors() + $this->workshop->get_potential_reviewers(); 00214 $users = $this->workshop->get_grouped($users); 00215 if (isset($users[0])) { 00216 $nogroupusers = $users[0]; 00217 foreach ($users as $groupid => $groupusers) { 00218 if ($groupid == 0) { 00219 continue; 00220 } 00221 foreach ($groupusers as $groupuserid => $groupuser) { 00222 unset($nogroupusers[$groupuserid]); 00223 } 00224 } 00225 if (!empty($nogroupusers)) { 00226 $list = array(); 00227 foreach ($nogroupusers as $nogroupuser) { 00228 $list[] = fullname($nogroupuser); 00229 } 00230 $a = implode(', ', $list); 00231 $out .= $output->box(get_string('nogroupusers', 'workshopallocation_random', $a), 'generalbox warning nogroupusers'); 00232 } 00233 } 00234 } 00235 00236 // TODO $out .= $output->heading(get_string('stats', 'workshopallocation_random')); 00237 00238 $out .= $output->container_end(); 00239 00240 return $out; 00241 } 00242 00252 public static function delete_instance($workshopid) { 00253 return; 00254 } 00255 00263 public static function available_numofreviews_list() { 00264 $options = array(); 00265 $options[30] = 30; 00266 $options[20] = 20; 00267 $options[15] = 15; 00268 for ($i = 10; $i >= 0; $i--) { 00269 $options[$i] = $i; 00270 } 00271 return $options; 00272 } 00273 00285 protected function self_allocation($authors=array(), $reviewers=array(), $assessments=array()) { 00286 if (!isset($authors[0]) || !isset($reviewers[0])) { 00287 // no authors or no reviewers 00288 return array(); 00289 } 00290 $alreadyallocated = array(); 00291 foreach ($assessments as $assessment) { 00292 if ($assessment->authorid == $assessment->reviewerid) { 00293 $alreadyallocated[$assessment->authorid] = 1; 00294 } 00295 } 00296 $add = array(); // list of new allocations to be created 00297 foreach ($authors[0] as $authorid => $author) { 00298 // for all authors in all groups 00299 if (isset($reviewers[0][$authorid])) { 00300 // if the author can be reviewer 00301 if (!isset($alreadyallocated[$authorid])) { 00302 // and the allocation does not exist yet, then 00303 $add[] = array($authorid => $authorid); 00304 } 00305 } 00306 } 00307 return $add; 00308 } 00309 00318 protected function add_new_allocations(array $newallocations, array $dataauthors, array $datareviewers) { 00319 global $DB; 00320 00321 $newallocations = $this->get_unique_allocations($newallocations); 00322 $authorids = $this->get_author_ids($newallocations); 00323 $submissions = $this->workshop->get_submissions($authorids); 00324 $submissions = $this->index_submissions_by_authors($submissions); 00325 foreach ($newallocations as $newallocation) { 00326 list($reviewerid, $authorid) = each($newallocation); 00327 if (!isset($submissions[$authorid])) { 00328 throw new moodle_exception('unabletoallocateauthorwithoutsubmission', 'workshop'); 00329 } 00330 $submission = $submissions[$authorid]; 00331 $status = $this->workshop->add_allocation($submission, $reviewerid, 1, true); // todo configurable weight? 00332 if (workshop::ALLOCATION_EXISTS == $status) { 00333 debugging('newallocations array contains existing allocation, this should not happen'); 00334 } 00335 } 00336 } 00337 00347 protected function index_submissions_by_authors($submissions) { 00348 $byauthor = array(); 00349 if (is_array($submissions)) { 00350 foreach ($submissions as $submissionid => $submission) { 00351 if (isset($byauthor[$submission->authorid])) { 00352 throw new moodle_exception('moresubmissionsbyauthor', 'workshop'); 00353 } 00354 $byauthor[$submission->authorid] = $submission; 00355 } 00356 } 00357 return $byauthor; 00358 } 00359 00366 protected function get_author_ids($newallocations) { 00367 $authors = array(); 00368 foreach ($newallocations as $newallocation) { 00369 $authorid = reset($newallocation); 00370 if (!in_array($authorid, $authors)) { 00371 $authors[] = $authorid; 00372 } 00373 } 00374 return $authors; 00375 } 00376 00383 protected function get_unique_allocations($newallocations) { 00384 return array_merge(array_map('unserialize', array_unique(array_map('serialize', $newallocations)))); 00385 } 00386 00400 protected function get_unkept_assessments($assessments, $newallocations, $keepselfassessments) { 00401 $keepids = array(); // keep these assessments 00402 foreach ($assessments as $assessmentid => $assessment) { 00403 $aaid = $assessment->authorid; 00404 $arid = $assessment->reviewerid; 00405 if (($keepselfassessments) && ($aaid == $arid)) { 00406 $keepids[$assessmentid] = null; 00407 continue; 00408 } 00409 foreach ($newallocations as $newallocation) { 00410 list($nrid, $naid) = each($newallocation); 00411 if (array($arid, $aaid) == array($nrid, $naid)) { 00412 // re-allocation found - let us continue with the next assessment 00413 $keepids[$assessmentid] = null; 00414 continue 2; 00415 } 00416 } 00417 } 00418 return array_keys(array_diff_key($assessments, $keepids)); 00419 } 00420 00441 protected function random_allocation($authors, $reviewers, $assessments, &$o, array $options) { 00442 if (empty($authors) || empty($reviewers)) { 00443 // nothing to be done 00444 return array(); 00445 } 00446 00447 $numofreviews = $options['numofreviews']; 00448 $numper = $options['numper']; 00449 00450 if (self::USERTYPE_AUTHOR == $numper) { 00451 // circles are authors, squares are reviewers 00452 $o[] = 'info::'.get_string('resultnumperauthor', 'workshopallocation_random', $numofreviews); 00453 $allcircles = $authors; 00454 $allsquares = $reviewers; 00455 // get current workload 00456 list($circlelinks, $squarelinks) = $this->convert_assessments_to_links($assessments); 00457 } elseif (self::USERTYPE_REVIEWER == $numper) { 00458 // circles are reviewers, squares are authors 00459 $o[] = 'info::'.get_string('resultnumperreviewer', 'workshopallocation_random', $numofreviews); 00460 $allcircles = $reviewers; 00461 $allsquares = $authors; 00462 // get current workload 00463 list($squarelinks, $circlelinks) = $this->convert_assessments_to_links($assessments); 00464 } else { 00465 throw new moodle_exception('unknownusertypepassed', 'workshop'); 00466 } 00467 // get the users that are not in any group. in visible groups mode, these users are exluded 00468 // from allocation by this method 00469 // $nogroupcircles is array (int)$userid => undefined 00470 if (isset($allcircles[0])) { 00471 $nogroupcircles = array_flip(array_keys($allcircles[0])); 00472 } else { 00473 $nogroupcircles = array(); 00474 } 00475 foreach ($allcircles as $circlegroupid => $circles) { 00476 if ($circlegroupid == 0) { 00477 continue; 00478 } 00479 foreach ($circles as $circleid => $circle) { 00480 unset($nogroupcircles[$circleid]); 00481 } 00482 } 00483 // $o[] = 'debug::circle links = ' . json_encode($circlelinks); 00484 // $o[] = 'debug::square links = ' . json_encode($squarelinks); 00485 $squareworkload = array(); // individual workload indexed by squareid 00486 $squaregroupsworkload = array(); // group workload indexed by squaregroupid 00487 foreach ($allsquares as $squaregroupid => $squares) { 00488 $squaregroupsworkload[$squaregroupid] = 0; 00489 foreach ($squares as $squareid => $square) { 00490 if (!isset($squarelinks[$squareid])) { 00491 $squarelinks[$squareid] = array(); 00492 } 00493 $squareworkload[$squareid] = count($squarelinks[$squareid]); 00494 $squaregroupsworkload[$squaregroupid] += $squareworkload[$squareid]; 00495 } 00496 $squaregroupsworkload[$squaregroupid] /= count($squares); 00497 } 00498 unset($squaregroupsworkload[0]); // [0] is not real group, it contains all users 00499 // $o[] = 'debug::square workload = ' . json_encode($squareworkload); 00500 // $o[] = 'debug::square group workload = ' . json_encode($squaregroupsworkload); 00501 $gmode = groups_get_activity_groupmode($this->workshop->cm, $this->workshop->course); 00502 if (SEPARATEGROUPS == $gmode) { 00503 // shuffle all groups but [0] which means "all users" 00504 $circlegroups = array_keys(array_diff_key($allcircles, array(0 => null))); 00505 shuffle($circlegroups); 00506 } else { 00507 // all users will be processed at once 00508 $circlegroups = array(0); 00509 } 00510 // $o[] = 'debug::circle groups = ' . json_encode($circlegroups); 00511 foreach ($circlegroups as $circlegroupid) { 00512 $o[] = 'debug::processing circle group id ' . $circlegroupid; 00513 $circles = $allcircles[$circlegroupid]; 00514 // iterate over all circles in the group until the requested number of links per circle exists 00515 // or it is not possible to fulfill that requirment 00516 // during the first iteration, we try to make sure that at least one circlelink exists. during the 00517 // second iteration, we try to allocate two, etc. 00518 for ($requiredreviews = 1; $requiredreviews <= $numofreviews; $requiredreviews++) { 00519 $this->shuffle_assoc($circles); 00520 $o[] = 'debug::iteration ' . $requiredreviews; 00521 foreach ($circles as $circleid => $circle) { 00522 if (VISIBLEGROUPS == $gmode and isset($nogroupcircles[$circleid])) { 00523 $o[] = 'debug::skipping circle id ' . $circleid; 00524 continue; 00525 } 00526 $o[] = 'debug::processing circle id ' . $circleid; 00527 if (!isset($circlelinks[$circleid])) { 00528 $circlelinks[$circleid] = array(); 00529 } 00530 $keeptrying = true; // is there a chance to find a square for this circle? 00531 $failedgroups = array(); // array of groupids where the square should be chosen from (because 00532 // of their group workload) but it was not possible (for example there 00533 // was the only square and it had been already connected 00534 while ($keeptrying && (count($circlelinks[$circleid]) < $requiredreviews)) { 00535 // firstly, choose a group to pick the square from 00536 if (NOGROUPS == $gmode) { 00537 if (in_array(0, $failedgroups)) { 00538 $keeptrying = false; 00539 $o[] = 'error::indent::'.get_string('resultnomorepeers', 'workshopallocation_random'); 00540 break; 00541 } 00542 $targetgroup = 0; 00543 } elseif (SEPARATEGROUPS == $gmode) { 00544 if (in_array($circlegroupid, $failedgroups)) { 00545 $keeptrying = false; 00546 $o[] = 'error::indent::'.get_string('resultnomorepeersingroup', 'workshopallocation_random'); 00547 break; 00548 } 00549 $targetgroup = $circlegroupid; 00550 } elseif (VISIBLEGROUPS == $gmode) { 00551 $trygroups = array_diff_key($squaregroupsworkload, array(0 => null)); // all but [0] 00552 $trygroups = array_diff_key($trygroups, array_flip($failedgroups)); // without previous failures 00553 if ($options['excludesamegroup']) { 00554 // exclude groups the circle is member of 00555 $excludegroups = array(); 00556 foreach (array_diff_key($allcircles, array(0 => null)) as $exgroupid => $exgroupmembers) { 00557 if (array_key_exists($circleid, $exgroupmembers)) { 00558 $excludegroups[$exgroupid] = null; 00559 } 00560 } 00561 $trygroups = array_diff_key($trygroups, $excludegroups); 00562 } 00563 $targetgroup = $this->get_element_with_lowest_workload($trygroups); 00564 } 00565 if ($targetgroup === false) { 00566 $keeptrying = false; 00567 $o[] = 'error::indent::'.get_string('resultnotenoughpeers', 'workshopallocation_random'); 00568 break; 00569 } 00570 $o[] = 'debug::indent::next square should be from group id ' . $targetgroup; 00571 // now, choose a square from the target group 00572 $trysquares = array_intersect_key($squareworkload, $allsquares[$targetgroup]); 00573 // $o[] = 'debug::indent::individual workloads in this group are ' . json_encode($trysquares); 00574 unset($trysquares[$circleid]); // can't allocate to self 00575 $trysquares = array_diff_key($trysquares, array_flip($circlelinks[$circleid])); // can't re-allocate the same 00576 $targetsquare = $this->get_element_with_lowest_workload($trysquares); 00577 if (false === $targetsquare) { 00578 $o[] = 'debug::indent::unable to find an available square. trying another group'; 00579 $failedgroups[] = $targetgroup; 00580 continue; 00581 } 00582 $o[] = 'debug::indent::target square = ' . $targetsquare; 00583 // ok - we have found the square 00584 $circlelinks[$circleid][] = $targetsquare; 00585 $squarelinks[$targetsquare][] = $circleid; 00586 $squareworkload[$targetsquare]++; 00587 $o[] = 'debug::indent::increasing square workload to ' . $squareworkload[$targetsquare]; 00588 if ($targetgroup) { 00589 // recalculate the group workload 00590 $squaregroupsworkload[$targetgroup] = 0; 00591 foreach ($allsquares[$targetgroup] as $squareid => $square) { 00592 $squaregroupsworkload[$targetgroup] += $squareworkload[$squareid]; 00593 } 00594 $squaregroupsworkload[$targetgroup] /= count($allsquares[$targetgroup]); 00595 $o[] = 'debug::indent::increasing group workload to ' . $squaregroupsworkload[$targetgroup]; 00596 } 00597 } // end of processing this circle 00598 } // end of one iteration of processing circles in the group 00599 } // end of all iterations over circles in the group 00600 } // end of processing circle groups 00601 $returned = array(); 00602 if (self::USERTYPE_AUTHOR == $numper) { 00603 // circles are authors, squares are reviewers 00604 foreach ($circlelinks as $circleid => $squares) { 00605 foreach ($squares as $squareid) { 00606 $returned[] = array($squareid => $circleid); 00607 } 00608 } 00609 } 00610 if (self::USERTYPE_REVIEWER == $numper) { 00611 // circles are reviewers, squares are authors 00612 foreach ($circlelinks as $circleid => $squares) { 00613 foreach ($squares as $squareid) { 00614 $returned[] = array($circleid => $squareid); 00615 } 00616 } 00617 } 00618 return $returned; 00619 } 00620 00627 protected function convert_assessments_to_links($assessments) { 00628 $authorlinks = array(); // [authorid] => array(reviewerid, reviewerid, ...) 00629 $reviewerlinks = array(); // [reviewerid] => array(authorid, authorid, ...) 00630 foreach ($assessments as $assessment) { 00631 if (!isset($authorlinks[$assessment->authorid])) { 00632 $authorlinks[$assessment->authorid] = array(); 00633 } 00634 if (!isset($reviewerlinks[$assessment->reviewerid])) { 00635 $reviewerlinks[$assessment->reviewerid] = array(); 00636 } 00637 $authorlinks[$assessment->authorid][] = $assessment->reviewerid; 00638 $reviewerlinks[$assessment->reviewerid][] = $assessment->authorid; 00639 } 00640 return array($authorlinks, $reviewerlinks); 00641 } 00642 00652 protected function get_element_with_lowest_workload($workload) { 00653 $precision = 10; 00654 00655 if (empty($workload)) { 00656 return false; 00657 } 00658 $minload = round(min($workload), $precision); 00659 $minkeys = array(); 00660 foreach ($workload as $key => $val) { 00661 if (round($val, $precision) == $minload) { 00662 $minkeys[$key] = $val; 00663 } 00664 } 00665 return array_rand($minkeys); 00666 } 00667 00674 protected function shuffle_assoc(&$array) { 00675 if (count($array) > 1) { 00676 // $keys needs to be an array, no need to shuffle 1 item or empty arrays, anyway 00677 $keys = array_keys($array); 00678 shuffle($keys); 00679 foreach($keys as $key) { 00680 $new[$key] = $array[$key]; 00681 } 00682 $array = $new; 00683 } 00684 return true; // because this behaves like in-built shuffle(), which returns true 00685 } 00686 00694 protected function filter_current_assessments(&$newallocations, $assessments) { 00695 foreach ($assessments as $assessment) { 00696 $allocation = array($assessment->reviewerid => $assessment->authorid); 00697 $foundat = array_keys($newallocations, $allocation); 00698 $newallocations = array_diff_key($newallocations, array_flip($foundat)); 00699 } 00700 } 00701 }