diff -up availability_calendars.css availability_calendars.css --- availability_calendars.css 2011-01-18 05:10:01.000000000 +0100 +++ availability_calendars.css 2011-01-25 11:52:52.000000000 +0100 @@ -2,7 +2,7 @@ table.sticky-header{font-size:80%;z-index:2;} table.cal{width:200px;display:inline;display:inline-table;/* for Safari */float:left;font-size:80%;margin:0 6px 6px;} table.cal tr.odd,table.cal tr.even{background:#FFFFFF;height:29px;} -div.month_title{margin:6px 6px 0;border-bottom:1px solid #DDDDDD;} +div.month_title{margin:6px 6px 0;border-bottom:1px solid #DDDDDD;width:200px} div.calmonth-wrapper{float:left;} tr.caldays,tr.odd.caldays,tr.even.caldays{color:#CCCCCC;} th.dayofweek{text-align:center;} @@ -31,3 +31,6 @@ td.cal-notavailableprov_available,tr.odd td.cal-notavailableprov_available > .cal-split-bg{border-color:#FFFFE0 #90EE90 #90EE90 #FFFFE0;} td.cal-notavailableprov_notavailable,tr.odd td.cal-notavailableprov_notavailable,tr.even td.cal-notavailableprov_notavailable{background:#FFB6C1;} td.cal-notavailableprov_notavailable > .cal-split-bg{border-color:#FFFFE0 #FFB6C1 #FFB6C1 #FFFFE0;} +/* custom label form elements */ +input.delete-label {background-color: #c0c0c0;} +input.new-label {border: 2px solid #4685c6;background-color: #cbe5ff;} diff -up availability_calendars.install availability_calendars.install --- availability_calendars.install 2011-01-21 08:16:11.000000000 +0100 +++ availability_calendars.install 2011-01-25 12:47:33.000000000 +0100 @@ -17,39 +17,39 @@ function availability_calendars_schema() { $schema = array(); $schema['availability_calendars_day'] = array( - 'description' => 'The table for calendar days.', + 'description' => t('The table for calendar days.'), 'fields' => array( 'nid' => array( - 'description' => 'The primary identifier for a node.', + 'description' => t('The primary identifier for a node.'), 'type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE ), 'year' => array( - 'description' => 'The number of the year.', + 'description' => t('The number of the year.'), 'type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE ), 'month' => array( - 'description' => 'The number of the month.', + 'description' => t('The number of the month.'), 'type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE ), 'day' => array( - 'description' => 'The number of the day.', + 'description' => t('The number of the day.'), 'type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE ), 'status' => array( - 'description' => 'The status.', + 'description' => t('The status.'), 'type' => 'text', 'size' => 'medium', ), 'date' => array( - 'description' => 'Datetime representation of availability', + 'description' => t('Datetime representation of availability'), 'type' => 'datetime' ) ) @@ -59,36 +59,46 @@ function availability_calendars_schema() 'description' => 'The table for calendar days.', 'fields' => array( 'nid' => array( - 'description' => 'The primary identifier for a node.', + 'description' => t('The primary identifier for a node.'), 'type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE ), 'year' => array( - 'description' => 'The number of the year.', + 'description' => t('The number of the year.'), 'type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE ), 'month' => array( - 'description' => 'The number of the month.', + 'description' => t('The number of the month.'), 'type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE ), 'week' => array( - 'description' => 'The number of the week.', + 'description' => t('The number of the week.'), 'type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE ), 'note' => array( - 'description' => 'The status.', + 'description' => t('The status.'), 'type' => 'varchar', 'length' => 64 ) ) ); + + $schema['availability_calendars_labels'] = array( + 'description' => 'Store custom labels for Availability Calendars', + 'fields' => array( + 'lid' => array('type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE), + 'label' => array('type' => 'varchar', 'not null' => TRUE, 'length' => 255) + ), + 'primary key' => array('lid') + ); + return $schema; } @@ -198,6 +208,26 @@ function availability_calendars_update_6 return $ret; } + +/** + * Add custom labels + */ +function availability_calendars_update_6104(&$sandbox) { + $ret = array(); + + $table = array( + 'description' => 'Store custom labels for Availability Calendars', + 'fields' => array( + 'lid' => array('type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE), + 'label' => array('type' => 'varchar', 'not null' => TRUE, 'length' => 255) + ), + 'primary key' => array('lid') + ); + + db_create_table($ret, 'availability_calendars_labels', $table); + return $ret; +} + /** * Implementation of hook_update_N(). * Change statuses to now use class strings instead of integers and alter defaultstatus setting if set. diff -up availability_calendars.module availability_calendars.module --- availability_calendars.module 2011-01-23 05:35:46.000000000 +0100 +++ availability_calendars.module 2011-01-25 12:42:19.000000000 +0100 @@ -101,53 +101,202 @@ function availability_calendars_perm() { * * @return array */ -function availability_calendars_admin_settings() { - $form = array(); - $settings = availability_calendar_getsettings(); - $form['display'] = array( - '#type' => 'fieldset', - '#title' => t('View settings'), - '#description' => t('Check where you want availability calendars to be displayed. If you choose none of the suggested places below you will need to output it manually using %func function.', array('%func' => 'theme_availability_calendars_node()')) - ); - $form['display']['availability_calendars_settings_system_nodeview'] = array( - '#type' => 'checkbox', - '#title' => t('Node view page'), - '#default_value' => $settings->nodeview - ); - $form['display']['availability_calendars_settings_system_monthcount'] = array( - '#type' => 'textfield', - '#title' => t('Number of months to display'), - '#default_value' => $settings->monthcount, - '#description' => t("Your calendars will show this number of months to all users except those with the 'edit own availability calendars' or 'edit availability calendars' who will always see 3 extra months on the calendars they can edit. This is to allow them to enter information into future calendars before it is made publicly available.") - ); - $form['display']['availability_calendars_settings_system_splitday'] = array( - '#type' => 'checkbox', - '#title' => t('Allow split day statuses.'), - '#default_value' => $settings->splitday - ); - $form['display']['availability_calendars_settings_system_defaultstatus'] = array( - '#type' => 'select', - '#options' => availability_calendars_options(), - '#title' => t("Set the default status in which to set all newly created nodes' statuses."), - '#default_value' => $settings->defaultstatus - ); - $form['global'] = array( - '#type' => 'fieldset', - '#title' => t('Global settings'), - '#description' => t('The following settings toggle their setting globally, leave them unchecked to allow configuration per node.') - ); - $form['global']['availability_calendars_settings_system_hideold'] = array( - '#type' => 'checkbox', - '#title' => t('Set past dates to fully booked.'), - '#default_value' => $settings->hideold, - ); - $form['global']['availability_calendars_settings_system_showteaser'] = array( - '#type' => 'checkbox', - '#title' => t('Show availability calendars within teasers.'), - '#default_value' => $settings->showteaser, +function availability_calendars_admin_settings(&$form_state) { + + $form = array( + '#submit' => array('availability_calendars_admin_submit') + ); + + $form['availability_calendars'] = array( + '#prefix' => '
', + '#suffix' => '
', + '#type' => 'fieldset', + '#title' => 'Availability Calendars Settings', + '#tree' => TRUE + ); + + $form['availability_calendars']['display'] = array( + '#type' => 'fieldset', + '#title' => t('View settings'), + '#description' => t('Check where you want availability calendars to be displayed. If you choose none of the suggested places below you will need to output it manually using %func function.', array('%func' => 'theme_availability_calendars_node()')) + ); + $form['availability_calendars']['display']['availability_calendars_display_nodeview'] = array( + '#type' => 'checkbox', + '#title' => t('Node view page'), + '#default_value' => variable_get('availability_calendars_display_nodeview', TRUE) + ); + $form['availability_calendars']['display']['availability_calendars_display_monthcount'] = array( + '#type' => 'textfield', + '#title' => t('Number of months to display'), + '#default_value' => variable_get('availability_calendars_display_monthcount', 12), + '#description' => t("Your calendars will show this number of months to all users except those with the 'edit own availability calendars' or 'edit availability calendars' who will always see 3 extra months on the calendars they can edit. This is to allow them to enter information into future calendars before it is made publicly available.") + ); + + // labels from the database, if this is the first load, otherwise, use the form input + $labels = array(); + $names = array(); // for sorting later + $db_labels = availability_calendars_custom_options(); + if (!$form_state['storage']){ + foreach ($db_labels as $id => $label){ + $labels[] = array('id' => $id, 'label' => $label, 'title' => $label); + $names[$label] = count($labels) - 1; + } + } else { + $new_id = -1; + foreach ($form_state['storage']['availability_calendars']['display_labels'] as $k => $v){ + if ($k == 'add_label') + continue; + + $new = ((int) $k < 1); + $label = NULL; + if (!$v) { // user has emptied this field to indicate removal + if (!$new) { // skip newly added ones that never made it to the database + $label = array('id' => $k, 'label' => $v, 'delete' => TRUE, 'title' => '[deleted] ' . $db_labels[$k]); + } + } else { + $id = ($new)? $new_id-- : $k; + $label = array('id' => $id, 'label' => $v, 'new' => $new, 'title' => (($new)? '[new] ' : '') . $v); + } + + if ($label){ + $labels[] = $label; + $names[$v] = count($labels) - 1; + } + } + } + + $a = array_keys($names); + $sorted_labels = array(); + sort($a); + + foreach ($a as $name){ + $sorted_labels[] = $labels[$names[$name]]; + } + + $form['availability_calendars']['display_labels'] = array( + '#type' => 'fieldset', + '#title' => t('Custom Availability Key'), + '#collapsible' => TRUE, + '#description' => t("You can create custom availability labels here. Add a new item and enter it's label. A CSS class will automatically be generated from this label. If you define no labels, the default set will be used. If you define at least one label, the default set will be ignored, and you must re-define any labels you wish to use from the default set.") + ); + + foreach ($sorted_labels as $label){ + $form['availability_calendars']['display_labels'][$label['id']] = availability_calendars_label_form_field($label); + } + // one for new labels + $form['availability_calendars']['display_labels']['new'] = availability_calendars_label_form_field(array( + 'id' => 'new', + 'name' => '' + )); + +// $form['availability_calendars']['display_labels']['add_label'] = array( +// '#type' => 'button', +// '#value' => t('Add new label'), +// '#ahah' => array( +// 'path' => ahah_helper_path(array('availability_calendars')), +// 'wrapper' => 'availability-calendars-wrapper' +// ) +// ); + + if ($sorted_labels){ + $options = array(); + foreach ($sorted_labels as $label){ + $options[$label['id']] = $label['label']; + } + + $form['availability_calendars']['default_label'] = array( + '#type' => 'fieldset', + '#title' => 'Default Option', + 'default_label' => array( + '#type' => 'select', + '#options' => $options, + '#default_value' => variable_get('availability_calendars_default_option', '') + ) + ); + } + + //TODO: add css classes in here + + $form['save'] = array( + '#type' => 'submit', + '#value' => t('Save'), + ); + + return $form; +} + +function availability_calendars_admin_submit($form_id, &$form) { + if ($form['clicked_button']['#id'] != 'edit-save') + return; + + $values = $form['values']['availability_calendars']; + $nkey = 'availability_calendars_display_nodeview'; + $mkey = 'availability_calendars_display_monthcount'; + $dkey = 'availability_calendars_default_option'; + variable_set($nkey, $values['display'][$nkey]); + variable_set($mkey, $values['display'][$mkey]); + variable_set($dkey, $values['default_label']['default_label']); + + $delete_ids = array(); + $new_labels = array(); + $labels = array(); + $existing = availability_calendars_custom_options('label'); + + foreach ($values['display_labels'] as $id => $label){ + if ($id == 'add_label') + continue; + + $new = ((int) $id < 1); + + if (!$label) { // user has emptied this field to indicate removal + if ((int) $id) + $delete_ids[] = $id; + } else { + if ($new && !in_array($label, $existing)) + $new_labels[] = $label; + else + $labels[] = array('lid' => $id, 'label' => $label); + } + } + + if ($delete_ids) + db_query("DELETE FROM {availability_calendars_labels} WHERE lid IN(" . join(',', $delete_ids) . ")"); + + foreach ($new_labels as $label) { + db_query("INSERT INTO {availability_calendars_labels} (label) VALUES ('%s')", $label); + } + foreach ($labels as $label) { + db_query("UPDATE {availability_calendars_labels} SET label = '%s' WHERE lid = %d", $label['label'], $label['lid']); + } +} + +/** + * Get a field definition for a calendar key label. + * + * @param $label + * An array containing label info, as: array('id' => $id, 'name' => $name, ['delete' => TRUE], ['new' => TRUE | FALSE]) + * + * @return + * An array with a Form field definition + */ +function availability_calendars_label_form_field($label) { + $class = ''; + if (isset($label['delete']) && $label['delete'] == TRUE) { + $class = 'delete-label'; + } + if (isset($label['new']) && $label['new'] == TRUE) { + $class = 'new-label'; + } + + $field = array( + '#type' => 'textfield', + '#title' => $label['title'], + '#default_value' => $label['label'], + '#description' => t("You may change the name of this label, or delete it. To delete this label, clear this field, and then save. The CSS class for this label is: '%css'", array('%css' => availability_calendars_option_css($label['label']))), + '#attributes' => array('class' => $class) ); - //TODO: add status codes and css classes in here - return system_settings_form($form); + + return $field; } /** @@ -246,11 +395,13 @@ function availability_calendars_node_edi $month_meta = availability_calendars_month_meta($year, $month, $settings); // find all entries in database for this month ($availability, $notes) and pre-populate + $notes = array(); $notes_result = db_query('SELECT week, note FROM {availability_calendars_week} WHERE nid = %d AND year = %d and month = %d', $nid, $year, $month); while ($note = db_fetch_array($notes_result)) { $notes[$note['week']] = $note['note']; } + $day_status = array(); $status_result = db_query('SELECT day, status FROM {availability_calendars_day} WHERE nid = %d AND year = %d AND month = %d', $nid, $year, $month); while ($status = db_fetch_array($status_result)) { $day_status[$status['day']] = $status['status']; @@ -274,14 +425,22 @@ function availability_calendars_node_edi else { $daysinweekremaining = 7; } + + $db_options = availability_calendars_options(TRUE); while ($daysinweekremaining > 0 && $day <= $month_meta['daysinmonth']) { - $form['week-' . $week]['day-' . $day] = array( - '#type' => 'select', - '#title' => date('l d F', strtotime("$year-$month-$day")), - '#options' => availability_calendars_options(), - '#default_value' => ($day_status[$day] === NULL) ? $settings->defaultstatus : $day_status[$day] + $day_state = $day_status[$day]; + + if (!$day_state) { + $day_state = variable_get('availability_calendars_default_option', 0); + } + + $form['week-'. $week]['day-'. $day] = array( + '#type' => 'select', + '#title' => date('l d F', strtotime("$year-$month-$day")), + '#options' => $db_options, + '#default_value' => "$day_state" ); - $day++; + $day++; $daysinweekremaining--; } } @@ -323,16 +482,15 @@ function availability_calendars_node_edi } // save $days - $day = 1; - $nomoredays = FALSE; - while (!$nomoredays) { - if (isset($form_state['values']['day-' . $day])) { - $days[$day] = $form_state['values']['day-' . $day]; - $day++; - } - else { - $nomoredays = TRUE; + $days = array(); + $day = 1; + while (TRUE) { + if (!isset($form_state['values']['day-' . $day])) { + break; } + + $days[$day] = $form_state['values']['day-' . $day]; + $day++; } db_query('DELETE FROM {availability_calendars_week} WHERE nid = %d AND year = %d AND month = %d', $nid, $year, $month); @@ -434,10 +592,28 @@ function theme_availability_calendars_no $output = ''; // Create our key for the availability calendar if the node has it set to do so - if ($settings->showkey === 1) { - $output .= availability_calendars_key(); - } + if($showkey == 1) { //use all the same classes for cells and table, so it styles the same as the calendars + $keytitle = '
'.t('Key').'
'; + $headers = array( + t('Color'), + t('Availability') + ); + + $rows = array(); + $options = availability_calendars_options(); + + foreach ($options as $id => $label){ + $css = availability_calendars_option_css($label); + $rows[] = array( + array('data' => ' ', 'class' => $css), + array('data' => t($label), 'class' => 'keystatus') + ); + } + $key = theme_table($headers, $rows, array('class' => 'cal')); + $output .= '
'.$keytitle.$key.'
'; + } + $monthsremaining = $monthstodisplay; while ($monthsremaining > 0) { $output .= theme('availability_calendars_month', $node, $year, $month, $settings); @@ -579,6 +755,9 @@ function theme_availability_calendars_mo 1 => t('Sat'), 0 => t('Sun'), ); + + $states = availability_calendars_custom_options(); + for ($j = 0; $j < $month_meta['weeksinmonth']; $j++) { for ($i = 0; $i < 7; $i++) { $counter++; @@ -634,7 +813,7 @@ function theme_availability_calendars_mo if ($settings->hideold === 1 && $daystamp < $today) $classes[] = 'calnotavailable'; // past dates should be "fully booked" else { if ($day_status[$day] === NULL) $classes[] = ($settings->defaultstatus) ? $settings->defaultstatus : $options[0]; - else $classes[] = $day_status[$day]; + else $classes[] = availability_calendars_id_safe(availability_calendars_label_from_lid($day_status[$day])); } $classes = implode(' ', $classes); $day = (strpos($day_status[$day], 'calsplit ') === 0) ? ' 
' . $day . '
' : $day; @@ -661,30 +840,116 @@ function theme_availability_calendars_mo /** * availability_calendars status options. * + * @param $include_unselected = FALSE + * Whether or not to include an unselected value at the top of the array + * * @return array */ -function availability_calendars_options() { - // TODO: make these configurable - $settings = availability_calendar_getsettings(); - $statuses = array( - 'available' => t('Available'), - 'notavailable' => t('Fully booked'), - 'notavailableprov' => t('Provisionally booked'), - ); - $ret = array(); - foreach ($statuses as $class => $label) { - $ret['cal' . $class] = $label; +function availability_calendars_options($include_unselected = FALSE) { + $labels = availability_calendars_custom_options('all', $include_unselected); + + if (!$labels) { + if ($include_unselected) { + $labels['-1'] = t('selected availability'); + } + + $labels[0] = t('Available'); + $labels[1] = t('Fully booked'); + $labels[2] = t('Provisionally booked'); } - if ($settings->splitday === 1) { - foreach ($statuses as $class => $label) { - $sub = $statuses; - unset($sub[$class]); - foreach ($sub as $subclass => $sublabel) { - $ret['calsplit cal-' . $class . '_' . $subclass] = t('@a (am)/@b (pm)', array('@a' => $label, '@b' => $sublabel)); - } + + return $labels; +} + +/** + * Get custom labels for calendars, or an empty array if none have been set. + * + * @param $field + * Optionally, the field you'd like to retrieve + * + * @param $include_unselected = FALSE + * Optionally, include an unselected option at the top of the array + * + * @return + * An array of labels, in the form array(label_id => t('label name')) + * If $field is set to 'label' or 'lid', returns an array of those field values + */ +function availability_calendars_custom_options($field = 'all', $include_unselected = FALSE) { + $result = db_query("SELECT * FROM {availability_calendars_labels} ORDER BY label"); + + $labels = array(); + if ($include_unselected) { + $labels['-1'] = t('selected availability'); + } + + $row = NULL; + while ($row = db_fetch_array($result)) { + if ($field != 'all'){ + $labels[] = ($field == 'label')? t($row['label']) : $row[$field]; + } else { + $labels[$row['lid']] = t($row['label']); } } - return $ret; + + return $labels; +} + +/** + * Get a custom label by state id + */ +function availability_calendars_label_from_lid($lid) { + $options = availability_calendars_options(); + foreach ($options as $test_id => $label) { + if ((int) $lid == (int) $test_id) { + return $label; + } + } + + return ""; +} +/** +* Converts a string to a suitable html ID attribute. +* +* http://www.w3.org/TR/html4/struct/global.html#h-7.5.2 specifies what makes a +* valid ID attribute in HTML. This function: +* +* - Ensure an ID starts with an alpha character by optionally adding an 'id'. +* - Replaces any character except A-Z, numbers, and underscores with dashes. +* - Converts entire string to lowercase. +* +* @param $string +* The string +* @return +* The converted string +*/ +function availability_calendars_id_safe($string) { + // Replace with dashes anything that isn't A-Z, numbers, dashes, or underscores. + $string = strtolower(preg_replace('/[^a-zA-Z0-9_-]+/', '-', $string)); + // If the first character is not a-z, add 'n' in front. + if (!ctype_lower($string{0})) { // Don't use ctype_alpha since its locale aware. + $string = 'id' . $string; + } + return $string; +} + +/** + * Get the CSS class for this option label + * + * @param $label + * The label for this option + * + * @return + * A string representing a CSS class. For backwards compatibility, default values get their historical css class + */ +function availability_calendars_option_css($label) { + if (strtolower($label) == 'available') + return 'calavailable'; + if (strtolower($label) == 'fully booked') + return 'calnotavailable'; + if (strtolower($label) == 'provisionally booked') + return 'calnotavailableprov'; + + return "availability_calendar_state " . availability_calendars_id_safe($label); }