/[drupal]/contributions/modules/signup/includes/date.inc
ViewVC logotype

Contents of /contributions/modules/signup/includes/date.inc

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.19 - (show annotations) (download) (as text)
Wed Mar 4 19:19:31 2009 UTC (8 months, 3 weeks ago) by dww
Branch: MAIN
CVS Tags: HEAD
Changes since 1.18: +5 -4 lines
File MIME type: text/x-php
#390000 by KarenS, dww: Ported signup to the Date 6.x-2.0 API for date
format strings that was introduced at #318008.
1 <?php
2 // $Id: date.inc,v 1.18 2009/01/06 21:22:03 dww Exp $
3
4
5 /**
6 * @file
7 * Code to support using CCK date fields for time-based signup functionality.
8 */
9
10 /**
11 *
12 * @return Array of SQL clauses for admin overview page query builder.
13 */
14 function _signup_date_admin_sql($content_type) {
15 // Get the date field information for this content type.
16 $field = signup_date_field($content_type);
17
18 // In the case where the same CCK date field is being reused on multiple
19 // content types, we'll potentially be JOINing on the same tables and
20 // columns for different content types. To defend against duplicate table
21 // names or ambiguous columns in the query, use the content type to alias.
22 $alias = db_escape_table($content_type);
23
24 // See what fields to SELECT.
25 $fields[] = $alias .'.'. $field['database']['columns']['value']['column'];
26 if (isset($field['database']['columns']['timezone']['column'])) {
27 $fields[] = $alias .'.'. $field['database']['columns']['timezone']['column'];
28 }
29 // We need to quote the alias in case someone used a reserved word for the
30 // machine-readable name of a content type.
31 $table = '{'. $field['database']['table'] .'} `'. $alias .'`';
32 return array(
33 'fields' => $fields,
34 'joins' => array("LEFT JOIN $table ON $alias.vid = n.vid"),
35 );
36 }
37
38 function signup_field_names($content_type = NULL) {
39 $fields = array();
40 $content_type_info = _content_type_info();
41 if ($content_type_info['content types'][$content_type]) {
42 foreach ($content_type_info['content types'][$content_type]['fields'] as $field) {
43 if (in_array($field['type'], array('date', 'datestamp', 'datetime'))) {
44 $fields[$field['field_name']] = $field['widget']['label'];
45 }
46 }
47 }
48 return $fields;
49 }
50
51 function signup_date_field($content_type) {
52 $field_name = variable_get('signup_date_field_'. $content_type, 0);
53 if ($field_name === 0 || $field_name == '0') {
54 // PHP is completely evil and 'none' == 0 is TRUE, hence the extra checks.
55 return FALSE;
56 }
57 if ($field_name == 'none') {
58 return 'none';
59 }
60 $field = content_fields($field_name, $content_type);
61 if (empty($field)) {
62 return array();
63 }
64 $field['database'] = content_database_info($field);
65 return $field;
66 }
67
68 /**
69 * Returns a list of all cck fields that have been set for use in signups
70 */
71 function signup_content_type_fields() {
72 $fields = array();
73 foreach (signup_content_types() as $content_type) {
74 $field = signup_date_field($content_type);
75 if (!empty($field) && $field != 'none') {
76 $fields[] = $field;
77 }
78 }
79 return $fields;
80 }
81
82 /**
83 * Determine if the specific node is date-enabled.
84 */
85 function _signup_date_get_node_scheduler($node) {
86 $field = signup_date_field($node->type);
87 if (isset($node->{$field['field_name']})) {
88 return 'date';
89 }
90 if (isset($node->{$field['database']['columns']['value']['column']})) {
91 return 'date';
92 }
93 return 'none';
94 }
95
96 /**
97 * Alter the form for configuring CCK date fields on node types.
98 *
99 * Hooks into CCK Date fields to provide an option to use the current
100 * field as the Signup date field (for autoclose and reminder emails).
101 *
102 */
103 function signup_form_content_field_edit_form_alter(&$form, &$form_state) {
104 $type = $form['type_name']['#value'];
105 if (in_array($form['#field']['type'], array('date', 'datestamp', 'datetime')) && variable_get('signup_node_default_state_'. $type, 'disabled') != 'disabled') {
106 $form['signup'] = array(
107 '#type' => 'fieldset',
108 '#title' => t('Signup settings'),
109 '#collapsible' => TRUE,
110 '#weight' => 1,
111 );
112 $form['signup']['signup_date_field'] = _signup_date_field_element($type);
113 $form['#submit'][] = '_signup_date_field_form_submit';
114 // Make sure the submit button comes after the signup settings fieldset.
115 $form['submit']['#weight'] = 50;
116 }
117 }
118
119 /**
120 * Custom submit handler for the CCK date field editing form.
121 *
122 * @see _signup_date_field_form_alter()
123 */
124 function _signup_date_field_form_submit($form, &$form_state) {
125 $type = $form_state['values']['type_name'];
126 if (empty($form_state['values']['signup_date_field'])) {
127 variable_del('signup_date_field_'. $type);
128 }
129 else {
130 variable_set('signup_date_field_'. $type, $form_state['values']['signup_date_field']);
131 }
132 }
133
134 /**
135 * Alter the node type form to add a setting to select the signup date field.
136 *
137 * @see signup_alter_node_type_form()
138 */
139 function _signup_date_alter_node_type_form(&$form, &$form_state) {
140 drupal_add_js(drupal_get_path('module', 'signup') .'/js/admin.content_types.js');
141
142 $type = $form['#node_type']->type;
143 $default_signup_state = variable_get('signup_node_default_state_'. $type, 'disabled');
144
145 // Add a div to the 'Signup options' radios for signup.date.js.
146 $form['signup']['signup_node_default_state']['#prefix'] = '<div class="signup-node-default-state-radios">';
147 $form['signup']['signup_node_default_state']['#suffix'] = '</div>';
148
149 // If event.module is enabled, add a div for those settings, too.
150 if (!empty($form['workflow']['event_nodeapi'])) {
151 $form['workflow']['event_nodeapi']['#prefix'] = '<div class="event-nodeapi-radios">';
152 $form['workflow']['event_nodeapi']['#suffix'] = '</div>';
153 $event_enabled = $form['workflow']['event_nodeapi']['#default_value'] != 'never';
154 }
155 else {
156 $event_enabled = FALSE;
157 }
158
159 // Figure out if we should hide the date field selector by default.
160 $class = 'signup-date-field-setting';
161 if ($default_signup_state == 'disabled' || $event_enabled) {
162 $class .= ' js-hide';
163 }
164
165 $form['signup']['signup_date_field'] = _signup_date_field_element($type);
166 $form['signup']['signup_date_field']['#prefix'] = '<div class="'. $class .'">';
167 $form['signup']['signup_date_field']['#suffix'] = '</div>';
168 }
169
170 /**
171 * Create the FAPI form element for the signup date field.
172 *
173 * @param $type
174 * The node type to generate the form element for.
175 *
176 * @return
177 * FAPI form array for the signup date field element.
178 *
179 * @see _signup_date_field_form_alter()
180 * @see _signup_date_alter_node_type_form()
181 */
182 function _signup_date_field_element($type) {
183 return array(
184 '#type' => 'select',
185 '#title' => t('Date field to use with signup'),
186 '#options' => _signup_get_date_field_options($type),
187 '#default_value' => variable_get('signup_date_field_'. $type, 0),
188 '#description' => t('Select the date field of this content type to use for signup time-based functionality, such as automatically closing signups when the start time has passed and sending reminder emails. Select "%none" to not use a date field for signup functionality at all.', array('%none' => t('None'))),
189 );
190 }
191
192 /**
193 * Check the signup and date configuration on node types depending on the URL.
194 *
195 * This function is invoked from signup_help() so that we can check the
196 * configuration of any signup-enabled node types to ensure that the CCK date
197 * field and signup settings make sense.
198 *
199 * @param $type
200 * The 4th element in the URL which specifies which node type is currently
201 * being configured. If this is empty, it means we're at the node type
202 * overview listing and we should test all node types.
203 *
204 * @see signup_help()
205 * @see signup_date_field_check_config()
206 */
207 function signup_date_check_node_types($type = NULL) {
208 $names = node_get_types('names');
209 if (!empty($type)) {
210 signup_date_field_check_config($type, $names[$type]);
211 }
212 else {
213 foreach ($names as $type => $name) {
214 signup_date_field_check_config($type, $name);
215 }
216 }
217 }
218
219 /**
220 * Check that the date and signup configuration for a node type makes sense.
221 *
222 * This validates that if a node type is signup enabled, that it either has a
223 * signup date field selected (for autoclose and reminder emails), or that the
224 * signup date field has been explicitly set to 'None'. It warns the site
225 * administrator if they have signup-enabled a node type and not defined any
226 * date fields at all, or if they have date fields but haven't selected the
227 * one to use for signup functionality.
228 *
229 * @param $type
230 * The node type to check signup and CCK date field configuration on.
231 * @param $name
232 * Human readable name of the node type to check.
233 *
234 * @return
235 * Nothing -- configuration errors are reported via drupal_set_message().
236 *
237 * @see signup_help()
238 */
239 function signup_date_field_check_config($type, $name) {
240 $signup_default_state = variable_get('signup_node_default_state_'. $type, 'disabled');
241 $signup_scheduler = _signup_get_node_type_scheduler($type);
242 if ($signup_scheduler != 'event' && $signup_default_state != 'disabled') {
243 // Signups aren't disabled on this node type, see if there's a date field.
244 $signup_date_field = signup_date_field($type);
245 if ($signup_date_field != 'none') {
246 $type_url = str_replace('_', '-', $type);
247 $placeholders = array(
248 '%node_type' => $name,
249 '%signup_date_field' => t('Date field to use with signup'),
250 '@type_admin_url' => url('admin/content/node-type/'. $type_url),
251 '@type_add_field_url' => url('admin/content/node-type/'. $type_url .'/fields'),
252 '%none' => t('<none>'),
253 );
254 // Administrator hasn't specifically turned off date support...
255 if (signup_field_names($type)) {
256 // Node type has some date fields...
257 if ($signup_date_field == 0) {
258 drupal_set_message(t('You have enabled the %node_type content type for signups, and have added one or more date fields, but have not selected a date field for use with signup. You can modify the %signup_date_field setting at the <a href="@type_admin_url">%node_type configuration page</a> to select a date field to use, or disable this warning by selecting %none.', $placeholders), 'warning');
259 }
260 }
261 else {
262 // No date fields at all.
263 drupal_set_message(t('You have enabled the %node_type content type for signups but have not added a date field. You can either <a href="@type_add_field_url">add a date field</a>, or disable this warning by selecting %none for the %signup_date_field setting at the <a href="@type_admin_url">%node_type configuration page</a>.', $placeholders), 'warning');
264 }
265 }
266 }
267 }
268
269 /**
270 * Helper function for the date field select to build its options.
271 *
272 * @param $type
273 * Content type whose date fields should be listed.
274 *
275 * @return
276 * Associative array with all date fields of the given content type plus
277 * 'None' and an optional 'Not specified' if the user never selected a
278 * value.
279 */
280 function _signup_get_date_field_options($type) {
281 $options = array();
282 // Add "Not specified" if the user never selected a field.
283 if (variable_get('signup_date_field_'. $type, 0) == 0) {
284 $options = array(0 => t('<Not specified>'));
285 }
286 // Add any date fields from this node type.
287 $options += signup_field_names($type);
288 // Always add 'None' as the final choice.
289 $options += array('none' => t('<None>'));
290 return $options;
291 }
292
293 /**
294 *
295 * @return Array of SQL clauses for cron reminder email query builder.
296 */
297 function _signup_date_reminder_sql($content_type) {
298 // Get the date field information for this content type.
299 $field = signup_date_field($content_type);
300 $start_field = $field['database']['columns']['value']['column'];
301
302 // Figure out what TZ we want to do the date comparisons in.
303 $compare_tz = $field['tz_handling'] == 'none' ? date_default_timezone_name() : 'UTC';
304 // Get a DateAPI SQL handler class for this field.
305 $handler = _signup_date_sql_handler($field, $compare_tz);
306
307 // Find the current time in the appropriate TZ for this field.
308 $now_date = date_now($compare_tz);
309 // Need to enclose this in ' marks to use directly in the SQL.
310 $now = "'". date_format($now_date, DATE_FORMAT_DATETIME) ."'";
311
312 // Extract the correct SQL to represent the start time.
313 $start_time = $handler->sql_field($start_field);
314
315 // Create SQL to represent the time we should start sending reminders, based
316 // on the SQL for the start time and the reminder_days_before field.
317 $reminder_start = _signup_date_sql_math($start_time, 'SUB', 's.reminder_days_before', 'DAY');
318 $reminder_stop = _signup_date_sql_math($start_time, 'ADD', 1, 'HOUR');
319
320 // The WHERE clauses are now trivial: We want to make sure a) the current
321 // time is after the time we should start sending reminders, but before the
322 // actual start time itself.
323 $where = array(
324 "$now >= $reminder_start",
325 "$now <= $reminder_stop",
326 );
327
328 // See what fields to SELECT.
329 $fields[] = $start_field;
330 if (isset($field['database']['columns']['timezone']['column'])) {
331 $fields[] = $field['database']['columns']['timezone']['column'];
332 }
333 $table = '{'. $field['database']['table'] .'}';
334 return array(
335 'fields' => $fields,
336 'joins' => array("INNER JOIN $table ON $table.vid = n.vid"),
337 'where' => $where,
338 );
339 }
340
341 /**
342 *
343 * @return Array of SQL clauses for cron auto-close query builder.
344 */
345 function _signup_date_autoclose_sql($content_type) {
346 // Get the date field information for this content type.
347 $field = signup_date_field($content_type);
348 $start_field = $field['database']['columns']['value']['column'];
349
350 // Figure out what TZ we want to do the date comparisons in.
351 $compare_tz = $field['tz_handling'] == 'none' ? date_default_timezone_name() : 'UTC';
352 // Get a DateAPI SQL handler class for this field.
353 $handler = _signup_date_sql_handler($field, $compare_tz);
354
355 // Compute a string representing the moment when signups should start
356 // auto-closing. If the field has no TZ handling, we just want to grab the
357 // current local time. If the field has any TZ handling, the date will be
358 // stored in the DB in UTC time, so start from current UTC time. Once we
359 // have the right current time, we need to add our close-in-advance offset.
360 $close_early_hours = variable_get('signup_close_early', 1);
361 $close_date = date_now($compare_tz);
362 date_modify($close_date, "+$close_early_hours hours");
363 $close_date_str = date_format($close_date, DATE_FORMAT_DATETIME);
364
365 // Use the DateAPI SQL handler to construct an appropriate WHERE clause.
366 // Make sure that the start time is <= NOW plus the auto-close window.
367 $where = $handler->sql_where_date('DATE', $start_field, '<=', $close_date_str);
368
369 // See what fields to SELECT.
370 $fields[] = $start_field;
371 if (isset($field['database']['columns']['timezone']['column'])) {
372 $fields[] = $field['database']['columns']['timezone']['column'];
373 }
374 $table = '{'. $field['database']['table'] .'}';
375 return array(
376 'fields' => $fields,
377 'joins' => array("INNER JOIN $table ON $table.vid = n.vid"),
378 'where' => $where,
379 );
380 }
381
382 /**
383 * Generate a DateAPI SQL handler for the given CCK date field.
384 *
385 * This can be removed once revision 1.61.2.4.2.32 is widely available in an
386 * official release.
387 */
388 function _signup_date_sql_handler($field, $compare_tz = NULL) {
389 // Workaround for a bug in DateAPI upto 6.x-2.0-rc5.
390 static $mysql_db_offset_set = FALSE;
391 module_load_include('inc', 'date_api', 'date_api_sql');
392
393 // Create a DateAPI SQL handler class for this field type.
394 $handler = new date_sql_handler();
395 $handler->construct($field['type']);
396
397 // If this date field stores a timezone in the DB, tell the handler about it.
398 if ($field['tz_handling'] == 'date') {
399 // The field has a date column
400 $handler->db_timezone_field = $field['database']['columns']['timezone']['column'];
401 }
402 else {
403 $handler->db_timezone = date_get_timezone_db($field['tz_handling']);
404 }
405
406 if (empty($compare_tz)) {
407 $compare_tz = date_get_timezone($field['tz_handling']);
408 }
409 $handler->local_timezone = $compare_tz;
410
411 // Now that the handler is properly initialized, tell the DB what TZ to use.
412 $handler->set_db_timezone();
413
414 // Up to and including DateAPI 6.x-2.0-rc5, the previous call only worked
415 // for mysqli and pgsql. It was a no-op on mysql itself, so in that case, we
416 // have to do the work ourselves here or datestamp fields don't work.
417 if ($GLOBALS['db_type'] == 'mysql' && !$mysql_db_offset_set && version_compare(db_version(), '4.1.3', '>=')) {
418 db_query("SET @@session.time_zone = '+00:00'");
419 $mysql_db_offset_set = TRUE;
420 }
421
422 return $handler;
423 }
424
425 /**
426 * Helper function to handle date math across DB types.
427 *
428 * This can be removed once date_api_sql.inc revision 1.9.2.3.2.28 is widely
429 * available in an official release.
430 *
431 * @param $field
432 * The field to be adjusted.
433 * @param $direction
434 * Either ADD or SUB.
435 * @param $count
436 * The number of values to adjust.
437 * @param $granularity
438 * The granularity of the adjustment, should be singular,
439 * like SECOND, MINUTE, DAY, HOUR.
440 */
441 function _signup_date_sql_math($field, $direction, $count, $granularity) {
442 $granularity = strtoupper($granularity);
443 switch ($GLOBALS['db_type']) {
444 case 'mysql':
445 case 'mysqli':
446 if ($direction == 'ADD') {
447 return "DATE_ADD($field, INTERVAL $count $granularity)";
448 }
449 else {
450 return "DATE_SUB($field, INTERVAL $count $granularity)";
451 }
452
453 case 'pgsql':
454 $granularity .= 'S';
455 if ($direction == 'ADD') {
456 return "($field + INTERVAL '$count $granularity')";
457 }
458 else {
459 return "($field - INTERVAL '$count $granularity')";
460 }
461 }
462 return $field;
463 }
464
465 /**
466 * Returns TRUE if the given node is event-enabled, and the start time
467 * has already passed the "Close x hours before" setting.
468 */
469 function _signup_date_node_completed($node) {
470 $field = signup_date_field($node->type);
471 if ($field && $field != 'none' && isset($node->{$field['field_name']})) {
472 // Grab whatever date value we actually have, regardless of format.
473 $date_value = $node->{$field['field_name']}[0]['value'];
474 // Figure out the timezone handling for this date.
475 if ($field['tz_handling'] == 'date') {
476 $tz = $node->{$field['field_name']}[0]['timezone'];
477 }
478 else {
479 $tz = date_default_timezone_name();
480 }
481 $db_tz = date_get_timezone_db($field['tz_handling'], $tz);
482 // Create a date object
483 $date = date_make_date($date_value, $db_tz, $field['type']);
484 // Make sure the date object is going to print UTC values.
485 date_timezone_set($date, timezone_open('UTC'));
486 // Find out how early signups should be automatically closed.
487 $close_early_hours = variable_get('signup_close_early', 1);
488 date_modify($date, "-$close_early_hours hours");
489 $close_time = date_format($date, 'U');
490 // Find the current UTC time.
491 $now = date_now('UTC');
492 if (date_format($now, 'U') >= $close_time) {
493 // It's now later than when this node would automatically close signups.
494 return TRUE;
495 }
496 }
497 return FALSE;
498 }
499
500 function _signup_date_format_date($node, $include_to_date = FALSE) {
501 $field = signup_date_field($node->type);
502 if (!$field || $field == 'none') {
503 return '';
504 }
505 if ($field['tz_handling'] == 'date') {
506 if (isset($node->{$field['field_name']})) {
507 $tz = $node->{$field['field_name']}[0]['timezone'];
508 }
509 else {
510 $tz = $node->{$field['database']['columns']['timezone']['column']};
511 }
512 }
513 else {
514 $tz = date_default_timezone_name();
515 }
516 $display_tz = date_get_timezone($field['tz_handling'], $tz);
517 $db_tz = date_get_timezone_db($field['tz_handling'], $tz);
518
519 if (isset($node->{$field['field_name']})) {
520 $date_value = $node->{$field['field_name']}[0]['value'];
521 }
522 else {
523 $date_value = $node->{$field['database']['columns']['value']['column']};
524 }
525 $date = date_make_date($date_value, $db_tz, $field['type']);
526 if ($db_tz != $display_tz) {
527 date_timezone_set($date, timezone_open($display_tz));
528 }
529 $format = date_formatter_format('default', $field['field_name']);
530 $date_out = date_format_date($date, 'custom', $format);
531
532 if ($include_to_date) {
533 if (isset($node->{$field['field_name']})) {
534 $date_value = $node->{$field['field_name']}[0]['value2'];
535 }
536 else {
537 $date_value = $node->{$field['database']['columns']['value2']['column']};
538 }
539 $date = date_make_date($date_value, $db_tz, $field['type']);
540 if ($db_tz != $display_tz) {
541 date_timezone_set($date, timezone_open($display_tz));
542 }
543 $date = date_format_date($date, 'custom', $format);
544 if ($date_value) {
545 $date_out .= t(' to ') . date_format_date(date_make_date($date_value), 'custom', $format);
546 }
547 }
548
549 return $date_out;
550 }
551

  ViewVC Help
Powered by ViewVC 1.1.2