Files
booking/booking.studygroups.inc

608 lines
20 KiB
PHP

<?php
/**
* @file
* Admin pages for calculating study group session membership
*/
/**
* Function for selecting who is available to lead or help sessions
*/
function booking_available_leadhelp_select_form() {
global $event;
//see http://www.jaypan.com/blog/themeing-drupal-7-forms-tables-checkboxes-or-radios
$form = array ();
$options = array ();
$prefix = t("<p>Specify the number of leader or helper sessions for each attendee. Note that this will overwrite the previous value.</p>");
$session_count = array();
$session_count['0'] = 0;
$session_count['1'] = 1;
$session_count['2'] = 2;
$session_count['3'] = 3;
$session_count['4'] = 4;
$session_count['5'] = 5;
$form['booking-leadhelp-count'] = array(
'#type' => 'select',
'#title' => t('Set Number of Sessions'),
'#required' => TRUE,
'#default_value' => '0',
'#options' => $session_count,
);
$header = array (
'booking_nid' => array('data' => t('Booking ID')),
'booking_name' => array('data' => t('Name')),
'booking_gender' => array('data' => t('Gender')),
'booking_baptised' => array('data' => t('Baptised?')),
'booking_age' => array('data' => t('Age at start of event')),
'booking_lead_sessions' => array('data' => t('Number of groups to lead currently')),
'booking_help_sessions' => array('data' => t('Number of groups to help currently')),
);
$result = db_query("SELECT * FROM {booking_person} p left outer join {booking_leadhelp_list} l on p.nid = l.booking_node_id WHERE p.booking_event_id = :eid ORDER BY p.booking_gender DESC, p.booking_dob, p.booking_baptised",
array(':eid' => $event->eid));
foreach($result as $data)
{
$options[$data->nid] = array (
'booking_nid' => l(t('!id', array('!id' => $data->nid)), t('node/!id', array('!id' => $data->nid))),
'booking_name' => $data->booking_firstname . " " . $data->booking_lastname,
'booking_gender' => $data->booking_gender == 'M' ? 'Male' : 'Female',
'booking_baptised' => $data->booking_baptised == 'Y' ? 'Yes' : 'No',
'booking_age' => _booking_avg_age($data->booking_dob, 1, $event->booking_event_start),
'booking_lead_sessions' => $data->booking_total_lead === NULL ? '0' : $data->booking_total_lead,
'booking_help_sessions' => $data->booking_total_help === NULL ? '0' : $data->booking_total_help,
);
}
$form['table'] = array (
'#type' => 'tableselect',
'#header' => $header,
'#options' => $options,
'#empty' => t('No attendees found.'),
);
$form['submit-leader'] = array (
'#type' => 'submit',
'#value' => t('Set Leader'),
);
$form['submit-helper'] = array (
'#type' => 'submit',
'#value' => t('Set Helper'),
);
return array (
'first_para' => array (
'#type' => 'markup',
'#markup' => $prefix,
),
'form' => $form,
);
}
/**
* Process the submission for number of study group sessions to lead/help
*/
function booking_available_leadhelp_select_form_submit($form, &$form_state) {
global $event;
$counter = 0;
$checkboxes = $form_state['values']['table'];
$session_count = $form_state['values']['booking-leadhelp-count'];
foreach($checkboxes as $key => $value)
{
if (is_numeric($key) && $value != 0)
{
//watchdog('booking', 'Checking !nid for entry in the booking_leadhelp_list table', array ('!nid' => $key));
//check if an entry already exists for this person in the booking_leadhelp_list table
$person = db_query("SELECT person.booking_node_id " .
"FROM {booking_leadhelp_list} person " .
"WHERE booking_node_id = :nid",
array(':nid' => $key))
->fetchObject();
if ($form_state['values']['op'] == 'Set Leader')
{
watchdog('booking', 'Setting group leader count to !count for !nid', array ('!count' => $session_count, '!nid' => $key));
if ($person)
{
//run an update
db_update('booking_leadhelp_list')
->fields(array (
'booking_total_lead' => $session_count,
'booking_available_lead' => $session_count,
))
->condition('booking_node_id', $key)
->execute();
}
else
{
//run an insert
db_insert('booking_leadhelp_list')
->fields(array(
'booking_eventid' => $event->eid,
'booking_node_id' => $key,
'booking_total_lead' => $session_count,
'booking_available_lead' => $session_count,
'booking_total_help' => 0,
'booking_available_help' => 0,
))
->execute();
}
}
elseif ($form_state['values']['op'] == 'Set Helper')
{
watchdog('booking', 'Setting group helper count to !count for !nid', array ('!count' => $session_count, '!nid' => $key));
if ($person)
{
//run an update
db_update('booking_leadhelp_list')
->fields(array (
'booking_total_help' => $session_count,
'booking_available_help' => $session_count,
))
->condition('booking_node_id', $key)
->execute();
}
else
{
//run an insert
db_insert('booking_leadhelp_list')
->fields(array(
'booking_eventid' => $event->eid,
'booking_node_id' => $key,
'booking_total_lead' => 0,
'booking_available_lead' => 0,
'booking_total_help' => $session_count,
'booking_available_help' => $session_count,
))
->execute();
}
}
$counter++;
}
}
drupal_set_message("Defined leader/helper numbers for $counter people.", 'status', FALSE);
watchdog('booking', "Defined leader/helper numbers for $counter people.");
}
/**
* Function for defining the number of study group sessions
* Note: This is hard-coded for now in the install file
*/
/**
* Function to correctly clone an associative array storing objects
* Taken from http://stackoverflow.com/questions/1532618/is-there-a-function-to-make-a-copy-of-a-php-array-to-another
*/
function clone_array($copied_array) {
return array_map(function($element) {
return (
((is_array($element))
? call_user_func(__FUNCTION__, $element)
: ((is_object($element))
? clone $element
: $element
)
)
);
}, $copied_array);
}
//taken from http://stackoverflow.com/questions/4102777/php-random-shuffle-array-maintaining-key-value
function shuffle_assoc($list) {
if (!is_array($list)) return $list;
$keys = array_keys($list);
shuffle($keys);
$random = array();
foreach ($keys as $key) {
$random[$key] = $list[$key];
}
return $random;
}
/**
* Function for calculating who belongs to which study group
*/
function booking_studygroups_calculate() {
global $event;
$attendees = array();
$working_list = array();
//consider using a lock as per https://api.drupal.org/api/drupal/includes!lock.inc/group/lock/7
//select all the study groups for this event id
$studygroups = db_query("SELECT * FROM {booking_studygroup_list} WHERE booking_eventid = :eid", array(':eid' => $event->eid));
//calculate the max number of attendees in a group
$firstgroup = $studygroups[0];
$limit = variable_get('booking_regn_limit','500');
$max_people = (int) ($limit / $firstgroup->booking_num_group_sessions);
drupal_set_message(t('Fitting !num people in each group.', array('!num' => $max_people)));
//select all the attendees booked in
$query = db_query("SELECT * FROM {booking_person} p left outer join {booking_leadhelp_list} l on p.nid = l.booking_node_id WHERE p.booking_event_id = :eid AND p.booking_status = 1",
array(':eid' => $event->eid));
$attendees = $query->fetchAllAssoc('nid');
//select any entries already in the mapping table
$group_mapping = db_query("SELECT * FROM {booking_studygroup_mapping} WHERE booking_eventid = :eid", array(':eid' => $event->eid));
//iterate over the attendee associative array and add some fields
foreach ($attendees as $person)
{
//flag that indicates processed or not
$person->processed = 0;
//field that indicates the session id the person is assigned to
$person->session = 0;
$person->is_leader = 0;
$person->is_helper = 0;
//convert NULLs into zero
$person->booking_total_lead = $person->booking_total_lead === NULL ? '0' : $person->booking_total_lead;
$person->booking_available_lead = $person->booking_available_lead === NULL ? '0' : $person->booking_available_lead;
$person->booking_total_help = $person->booking_total_help === NULL ? '0' : $person->booking_total_help;
$person->booking_available_help = $person->booking_available_help === NULL ? '0' : $person->booking_available_help;
}
//watchdog('booking', "Attendee list: @info", array('@info' => var_export($attendees, TRUE)));
//iterate over each study group (eg Monday Tuesday Wednesday etc)
foreach ($studygroups as $group)
{
drupal_set_message(t('Processing study group !group.', array('!group' => $group->booking_studygroup_descrip)));
//create a temporary copy of the attendee list to work with for this study group
$working_list = array();
$working_list = shuffle_assoc(clone_array($attendees));
//set up the iterator
$obj = new ArrayObject( $working_list );
$it = $obj->getIterator();
//reset the iterator to starting position
$it->rewind();
//watchdog('booking', "Attendee list: @info", array('@info' => var_export($attendees, TRUE)));
//watchdog('booking', "Attendee list working copy: @info", array('@info' => var_export($working_list, TRUE)));
$session_id = 1;
$rewound = FALSE;
$leader_found = FALSE;
$helper_found = FALSE;
while (true)
{
//if we're at the end of the attendee list, go back to the start
if (! $it->valid() )
{
if ($rewound == TRUE)
{
watchdog('booking', "Already rewound once.");
//we've already gone back to the start once, don't do it again
break;
}
else
{
watchdog('booking', "Rewinding to start of attendee list.");
$it->rewind();
$rewound = TRUE;
}
}
//check if we have reached the total number of sessions required
if ($session_id > $group->booking_num_group_sessions)
{
break;
}
//check if we can increment the session count
if ($leader_found == TRUE && $helper_found == TRUE)
{
$session_id++;
$leader_found = FALSE;
$helper_found = FALSE;
}
//get the current attendee element
$current = $it->current();
//watchdog('booking', 'Attendee before leader check has id !id.', array('!id' => $it->key()));
//check if this attendee is a leader
if ($current->processed == 0 && $current->booking_available_lead > 0 && $leader_found == FALSE)
{
drupal_set_message(t('Found available leader with id !id for session !session.', array('!id' => $it->key(), '!session' => $session_id)));
$leader_found = TRUE;
//assign leader to session and mark as processed in the temporary attendee list
$current->session = $session_id;
$current->processed = 1;
$current->is_leader = 1;
//decrement the number of available leading positions for this user in our master copy of the attendee list
$attendees[$it->key()]->booking_available_lead = $attendees[$it->key()]->booking_available_lead - 1;
//Check for spouse of leader, allocate to this group also
if ($current->booking_partner_id > 0)
{
//add the spouse to the same session and mark as processed in the temporary attendee list
drupal_set_message(t('Assigning spouse (id !spouse) of id !id to session !session.',
array('!id' => $it->key(), '!session' => $session_id, '!spouse' => $current->booking_partner_id)));
$spouse = $working_list[$current->booking_partner_id];
$spouse->session = $session_id;
$spouse->processed = 1;
}
}
//check if this attendee is a helper
elseif ($current->processed == 0 && $current->booking_available_help > 0 && $helper_found == FALSE)
{
drupal_set_message(t('Found available helper with id !id for session !session.', array('!id' => $it->key(), '!session' => $session_id)));
$helper_found = TRUE;
//assign leader to session and mark as processed in the temporary attendee list
$current->session = $session_id;
$current->processed = 1;
$current->is_helper = 1;
//decrement the number of available helping positions for this user in our master copy of the attendee list
$attendees[$it->key()]->booking_available_help = $attendees[$it->key()]->booking_available_help - 1;
//Check for spouse of helper, allocate to this group also
if ($current->booking_partner_id > 0)
{
//add the spouse to the same session and mark as processed in the temporary attendee list
drupal_set_message(t('Assigning spouse (id !spouse) of id !id to session !session.',
array('!id' => $it->key(), '!session' => $session_id, '!spouse' => $current->booking_partner_id)));
$spouse = $working_list[$current->booking_partner_id];
$spouse->session = $session_id;
$spouse->processed = 1;
}
}
//otherwise go to the next one
else
{
$it->next();
}
}
//reset the iterator to starting position
$it->rewind();
//iterate over the attendee list
while ( $it->valid() )
{
//go to the first unprocessed attendee
while ($it->current()->processed == 1)
{
drupal_set_message(t('Person with id !id has already been processed earlier.', array('!id' => $it->key())));
$it->next();
//double check we didn't run out of people right here
if (! $it->valid() )
{
drupal_set_message("Ran out of attendees to process.");
break;
}
}
//iterate over the session list
for ($i = 1; $i <= $group->booking_num_group_sessions; $i++)
//for ($i = 1; $i <= 16; $i++)
{
//check we have attendees left
if (! $it->valid() )
{
drupal_set_message("Ran out of attendees to process.");
break;
}
//get the current attendee element
$current = $it->current();
//assign this attendee to this session if unprocessed so far
if ($current->processed == 0)
{
drupal_set_message(t('Assigning person with id !id to session !session.', array('!id' => $it->key(), '!session' => $i)));
$current->session = $i;
$current->processed = 1;
//check if the attendee was married
if ($current->booking_partner_id > 0)
{
//add the spouse to the same session and mark as processed in the temporary attendee list
drupal_set_message(t('Assigning spouse (id !spouse) of id !id to session !session.',
array('!id' => $it->key(), '!session' => $i, '!spouse' => $current->booking_partner_id)));
$spouse = $working_list[$current->booking_partner_id];
$spouse->session = $i;
$spouse->processed = 1;
}
}
//move to the next unprocessed attendee in the list
while ($it->valid() && $it->current()->processed == 1)
$it->next();
} //finished looping through session list
} //finished looping through attendees for this study group
//iterate over the attendee list and write to the database the session they're assigned to
//use the multi-insert query type at https://drupal.org/node/310079
$insert_query = db_insert('booking_studygroup_mapping')->fields(array('booking_eventid', 'booking_node_id',
'booking_studygroup_id', 'booking_session_id', 'booking_is_leader', 'booking_is_helper'));
foreach($working_list as $person)
{
//TODO: a check to update existing records rather than inserting new one
// if already in $group_mapping then just run an update query here
$record = array(
'booking_eventid' => $event->eid,
'booking_node_id' => $person->nid,
'booking_studygroup_id' => $group->sid,
'booking_session_id' => $person->session,
'booking_is_leader' => $person->is_leader,
'booking_is_helper' => $person->is_helper,
);
$query->values($record);
}
$query->execute();
} //finished processing study groups
//watchdog('booking', "Attendee list final version: @info", array('@info' => var_export($attendees, TRUE)));
}
/**
* Function for listing the study groups and links to view each one
*/
function booking_studygroups_view_summary() {
}
/**
* Function for viewing who belongs to which study group
*/
function booking_studygroups_view_form($node, &$form_state, $group_id) {
global $event;
$form = array();
$options = array();
$last_session = "";
//attach the custom css
$form['#attached']['css'] = array(
drupal_get_path('module', 'booking') . '/booking.css',
);
if ($single_view == true)
{
//verify that $group_id is a number
if (! preg_match('/^[0-9]+$/', $group_id)) {
drupal_set_message("Error: Invalid study group ID supplied. Unable to view group membership.", 'error', FALSE);
drupal_goto('admin/config/booking');
return "";
}
//collect information on the study group
$group = db_query("SELECT * FROM {booking_studygroup_list} WHERE booking_eventid = :eid and sid = :sid",
array(':eid' => $event->eid, ':sid' => $group_id))
->fetchObject();
if (! $group)
{
drupal_set_message("Error: Could not find matching study group ID. Unable to view group membership.", 'error', FALSE);
drupal_goto('admin/config/booking');
return "";
}
}
else
{
drupal_set_message("Page not yet implemented.", 'error', FALSE);
drupal_goto('admin/config/booking');
return "";
}
$header = array(
'booking_session_id' => array('data' => t('Study Group Session'), 'field' => 'm.booking_session_id', 'sort' => 'asc'),
'booking_name' => array('data' => t('Name'), 'field' => 'p.booking_lastname', 'sort' => 'asc'),
'booking_is_leader' => array('data' => t('Leader?'), 'field' => 'm.booking_is_leader', 'sort' => 'asc'),
'booking_is_helper' => array('data' => t('Helper?'), 'field' => 'm.booking_is_helper', 'sort' => 'asc'),
);
$query = db_select('booking_person', 'p');
$query->leftJoin('booking_studygroup_mapping', 'm', 'm.booking_node_id = p.nid');
$db_and = db_and();
$db_and->condition('p.booking_event_id', $event->eid, '=');
$db_and->condition('m.booking_studygroup_id', $group_id, '=');
$query->condition($db_and);
$query->fields('p')->fields('m');
$table_sort = $query->extend('TableSort')->orderbyHeader($header);
$result = $table_sort->execute();
//watchdog('booking', 'Study groups raw data: @info', array ('@info' => var_export($result, TRUE)));
foreach($result as $data)
{
$class = "";
//apply theme as required
if ($data->booking_is_leader == 1)
$class = "leader-row";
elseif ($data->booking_is_helper == 1)
$class = "helper-row";
else
$class = "normal-row";
//Add a different id for first entry of new session, with a border-top to distinguish it
if ($last_session <> $data->booking_session_id && $last_session <> "")
{
if ($class === "leader-row")
$class = "leader-new-group-row";
elseif ($class === "helper-row")
$class = "helper-new-group-row";
else
$class = "new-group-row";
}
$options[$data->nid] = array (
'booking_session_id' => $data->booking_session_id,
'booking_name' => $data->booking_firstname . " " . $data->booking_lastname,
'booking_is_leader' => $data->booking_is_leader == 1 ? 'Yes' : 'No',
'booking_is_helper' => $data->booking_is_helper == 1 ? 'Yes' : 'No',
'#attributes' => array('id' => array($class))
);
$last_session = $data->booking_session_id;
}
$prefix = t("<h2>Study Group !descrip</h2>", array('!descrip' => $group->booking_studygroup_descrip));
$form['table'] = array (
'#type' => 'tableselect',
'#header' => $header,
'#options' => $options,
//'#attributes' => array('id' => 'sort-table'),
);
return array (
'first_para' => array (
'#type' => 'markup',
'#markup' => $prefix,
),
'form' => $form,
);
}