Project

General

Profile

Paste
Download (19.7 KB) Statistics
| Branch: | Revision:

root / drupal7 / sites / all / modules / makemeeting / makemeeting.field.inc @ b75b6b8b

1
<?php
2

    
3
/**
4
 * @file
5
 * This file is mostly about the field configuration.
6
 */
7

    
8
/**
9
 * Implements hook_field_info().
10
 */
11
function makemeeting_field_info() {
12
  return array(
13
    'makemeeting' => array(
14
      'label'             => t('Makemeeting form'),
15
      'description'       => t('This field stores user choices about some dates in the database.'),
16
      'default_widget'    => 'makemeeting_choices',
17
      'default_formatter' => 'makemeeting_answers',
18
    ),
19
  );
20
}
21

    
22
/**
23
 * Implements hook_field_widget_info().
24
 */
25
function makemeeting_field_widget_info() {
26
  return array(
27
    'makemeeting_choices' => array(
28
      'label'       => t('Makemeeting choices'),
29
      'field types' => array('makemeeting'),
30
      'behaviors'   => array(
31
        'default value' => FIELD_BEHAVIOR_NONE,
32
      ),
33
    ),
34
  );
35
}
36

    
37
/**
38
 * Implements hook_field_widget_settings_form().
39
 */
40
function makemeeting_field_widget_settings_form($field, $instance) {
41
  $widget = $instance['widget'];
42
  $settings = $widget['settings'];
43

    
44
  $form = array();
45
  if ($widget['type'] == 'makemeeting_choices') {
46
    $form['size'] = array(
47
      '#type'             => 'textfield',
48
      '#title'            => t('Number of dates'),
49
      '#default_value'    => isset($settings['size']) ? $settings['size'] : 1,
50
      '#element_validate' => array('element_validate_integer'),
51
      '#required'         => TRUE,
52
      '#size'             => 5,
53
      '#description'      => t('Default number of date choices to display. Must be positive or equal to 0.')
54
    );
55
  }
56

    
57
  return $form;
58
}
59

    
60
/**
61
 * Implements hook_field_widget_form().
62
 */
63
function makemeeting_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
64
  $element += array(
65
    '#type' => 'fieldset',
66
  );
67

    
68
  $item =& $items[$delta];
69

    
70
  // Simple options form elements about the poll
71
  $element['hidden'] = array(
72
    '#title'         => t('Hidden poll'),
73
    '#description'   => t('Confidential participation: only you and administrators can see the answers.'),
74
    '#type'          => 'checkbox',
75
    '#default_value' => isset($item['hidden']) ? $item['hidden'] : '',
76
  );
77
  $element['one_option'] = array(
78
    '#title'         => t('Participant can only choose one option'),
79
    '#description'   => t('By default all options are selectable. This setting limits the choice to one option per participant.'),
80
    '#type'          => 'checkbox',
81
    '#default_value' => isset($item['one_option']) ? $item['one_option'] : '',
82
  );
83
  $element['limit'] = array(
84
    '#title'            => t('Limit the number of participants per option'),
85
    '#description'      => t('Poll as registration form: As soon as the indicated limit has been reached, the respective option is no longer available. 0 for unlimited'),
86
    '#type'             => 'textfield',
87
    '#default_value'    => isset($item['limit']) ? $item['limit'] : 0,
88
    '#element_validate' => array('element_validate_integer'),
89
  );
90
  $element['yesnomaybe'] = array(
91
    '#title'         => t('Provide a "Maybe" option'),
92
    '#description'   => t('Provide a third "Maybe" option in case users may be available.'),
93
    '#type'          => 'checkbox',
94
    '#default_value' => isset($item['yesnomaybe']) ? $item['yesnomaybe'] : '',
95
    // Hide if one_option is checked
96
    '#states'        => array(
97
      'visible' => array(
98
        ':input[name$="[one_option]"]' => array('checked' => FALSE),
99
      ),
100
    ),
101
  );
102
  $element['closed'] = array(
103
    '#title'         => t('Closed'),
104
    '#description'   => t('Check this when you want to close the poll.'),
105
    '#type'          => 'checkbox',
106
    '#default_value' => isset($item['closed']) ? $item['closed'] : '',
107
  );
108

    
109
  // Now let's take care of the choices part
110
  // $process is a callback that we will attach to the wrapper in order
111
  // to add parents to the form elements. This is so we can store
112
  // nested values in the DB table with field storage API.
113
  $fieldset_info = element_info('fieldset');
114
  $process = array_merge($fieldset_info['#process'], array('_makemeeting_rebuild_parents'));
115
  $element['choices_wrapper'] = array(
116
    '#tree'    => FALSE,
117
    '#prefix'  => '<div class="clearfix" id="makemeeting-choices-wrapper">',
118
    '#suffix'  => '</div>',
119
    '#process' => $process,
120
  );
121

    
122
  // Container just for the poll choices.
123
  $element['choices_wrapper']['choices'] = array(
124
    '#prefix' => '<div id="makemeeting-choices">',
125
    '#suffix' => '</div>',
126
    '#theme'  => 'makemeeting_choices',
127
  );
128

    
129
  // Fill the item values with previously submitted values
130
  if (!empty($form_state['values'])) {
131
    $submitted_values = drupal_array_get_nested_value($form_state['values'], $element['#field_parents']);
132
    $submitted_values = $submitted_values[$element['#field_name']][$element['#language']][$element['#delta']];
133
    if (isset($submitted_values['choices'])) {
134
      $item['choices'] = $submitted_values['choices'];
135
    }
136
  }
137

    
138
  // Calculate the suggestion count
139
  if (empty($form_state['suggestion_count'])) {
140
    $form_state['suggestion_count'] = 1;
141
  }
142
  if (isset($item['choices'])) {
143
    foreach ($item['choices'] as $choice) {
144
      if ($form_state['suggestion_count'] < count($choice['chsuggestions'])) {
145
        $form_state['suggestion_count'] = count($choice['chsuggestions']);
146
      }
147
    }
148
  }
149

    
150
  // Determine choice count if not provided
151
  if (!isset($form_state['choice_count'])) {
152
    try {
153
      list($entity_id) = entity_extract_ids($element['#entity_type'], $element['#entity']);
154
    } catch (Exception $e) {
155
      // Fail silently if the field is not attached to any entity yet.
156
    }
157
    // If the entity isn't created yet, offer the default number of choices,
158
    // else the number of previous choices, or 0 if none
159
    if (!isset($entity_id)) {
160
      $widget = $instance['widget'];
161
      $settings = $widget['settings'];
162
      $form_state['choice_count'] = isset($settings['size']) ? $settings['size'] : 1;
163
    }
164
    else {
165
      $form_state['choice_count'] = isset($item['choices']) ? count($item['choices']) : 0;
166
    }
167
  }
168

    
169
  // Add the current choices to the form.
170
  $delta = 0;
171
  if (isset($item['choices']) && $form_state['choice_count']) {
172
    // Sort choices by their dates
173
    asort($item['choices']);
174
    $delta = count($item['choices']);
175
    foreach ($item['choices'] as $key => $choice) {
176
      $element['choices_wrapper']['choices'][$key] =
177
        _makemeeting_choice_form($key, $choice['chdate'], $choice['chsuggestions'], $form_state['suggestion_count']);
178
    }
179
  }
180

    
181
  // Add choices as required.
182
  if ($form_state['choice_count']) {
183
    // Add a day to the last option's timestamp for the new default date
184
    $last_option_timestamp = isset($choice['chdate']) ? _makemeeting_date_timestamp($choice['chdate']) : REQUEST_TIME;
185
    $existing_delta = $delta;
186
    for (; $delta < $form_state['choice_count'] ; $delta++) {
187
      $additional_stamp = strtotime('+1 day', $last_option_timestamp);
188
      // Build default date
189
      $default_date = array(
190
        'month' => date('n', $additional_stamp),
191
        'day'   => date('j', $additional_stamp),
192
        'year'  => date('Y', $additional_stamp)
193
      );
194
      $key = 'new:' . ($delta - $existing_delta);
195
      $element['choices_wrapper']['choices'][$key] =
196
        _makemeeting_choice_form($key, $default_date, NULL, $form_state['suggestion_count']);
197
      // Repeat
198
      $last_option_timestamp = $additional_stamp;
199
    }
200
  }
201

    
202
  // We prefix our buttons with 'makemeeting' to avoid conflicts
203
  // with other modules using Ajax-enabled buttons with the id 'more'.
204
  $default_submit = array(
205
    '#type'                    => 'submit',
206
    '#limit_validation_errors' => array(array('choices')),
207
    '#submit'                  => array('makemeeting_choices_submit'),
208
    '#ajax'                    => array(
209
      'callback' => 'makemeeting_choice_js',
210
      'wrapper'  => 'makemeeting-choices-wrapper',
211
      'effect'   => 'fade',
212
    ),
213
  );
214
  $element['choices_wrapper']['makemeeting_more_choices'] = $default_submit + array(
215
      '#value'      => t('More choices'),
216
      '#attributes' => array(
217
        'title' => t("If the amount of rows above isn't enough, click here to add more choices."),
218
      ),
219
      '#weight'     => 1,
220
    );
221
  $element['choices_wrapper']['makemeeting_more_suggestions'] = $default_submit + array(
222
      '#value'      => t('More suggestions'),
223
      '#attributes' => array(
224
        'title' => t("If the amount of boxes above isn't enough, click here to add more suggestions."),
225
      ),
226
      '#weight'     => 2,
227
    );
228
  if ($form_state['choice_count'] > 1) {
229
    $element['choices_wrapper']['makemeeting_copy_suggestions'] = $default_submit + array(
230
        '#value'      => t('Copy and paste first row'),
231
        '#attributes' => array(
232
          'title' => t("Copy the suggestions from the first to the other rows."),
233
        ),
234
        '#weight'     => 3,
235
      );
236
  }
237
  return $element;
238
}
239

    
240
/**
241
 * Helper function to construct a row of the widget form
242
 *
243
 * @param $key
244
 * @param string $value
245
 * @param array $suggestions
246
 * @param int $size
247
 * @return array
248
 */
249
function _makemeeting_choice_form($key, $value = '', $suggestions = array(), $size = 1) {
250
  $form = array(
251
    '#tree' => TRUE,
252
  );
253

    
254
  // We'll manually set the #parents property of these fields so that
255
  // their values appear in the $form_state['values']['choices'] array
256
  // They are to be updated later in #process as we don't know here the
257
  // hierarchy of the parents
258
  $form['chdate'] = array(
259
    '#type'          => 'date',
260
    '#title'         => t('Date'),
261
    '#title_display' => 'invisible',
262
    '#default_value' => $value,
263
    '#parents'       => array('choices', $key, 'chdate'),
264
  );
265
  $form['chremove'] = array(
266
    '#type'                    => 'submit',
267
    '#parents'                 => array(),
268
    '#submit'                  => array('makemeeting_choices_submit'),
269
    '#limit_validation_errors' => array(array('choices')),
270
    '#ajax'                    => array(
271
      'callback' => 'makemeeting_choice_js',
272
      'wrapper'  => 'makemeeting-choices',
273
      'effect'   => 'fade',
274
    ),
275
    '#value'                   => t('Remove'),
276
    // Assigning key as name to make each 'Remove' button unique
277
    '#name'                    => $key,
278
  );
279

    
280
  $form['chsuggestions'] = array(
281
    '#tree'    => TRUE,
282
    '#parents' => array('choices', $key, 'chsuggestions'),
283
  );
284

    
285
  for ($i = 0 ; $i < $size ; $i++) {
286
    $key2 = 'sugg:' . ($i);
287
    $form['chsuggestions'][$key2] = array(
288
      '#type'          => 'textfield',
289
      '#title_display' => 'invisible',
290
      '#default_value' => isset($suggestions[$key2]) ? $suggestions[$key2] : '',
291
      '#size'          => 5,
292
      '#maxlength'     => 255,
293
      '#parents'       => array('choices', $key, 'chsuggestions', $key2),
294
    );
295
  }
296
  return $form;
297
}
298

    
299
/**
300
 * Helper function to update the form elements parents. This is required
301
 * for the element to be saved properly by the Field API (see #process)
302
 *
303
 * @param $element
304
 * @return mixed
305
 */
306
function _makemeeting_rebuild_parents(&$element) {
307
  $parents = array_diff($element['#array_parents'], $element['#parents']);
308
  $elements = array(
309
    'makemeeting_more_choices',
310
    'makemeeting_more_suggestions',
311
    'makemeeting_copy_suggestions'
312
  );
313
  foreach ($elements as $e) {
314
    $element[$e]['#parents'] = $parents;
315
    if (isset($element[$e]['#limit_validation_errors'])) {
316
      $element[$e]['#limit_validation_errors'][0] = array_merge($parents,
317
        $element[$e]['#limit_validation_errors'][0]);
318
    }
319
  }
320
  $element['makemeeting_more_suggestions']['#parents'] = $parents;
321
  $element['makemeeting_copy_suggestions']['#parents'] = $parents;
322
  foreach (element_children($element['choices']) as $key1) {
323
    foreach (element_children($element['choices'][$key1]) as $key2) {
324
      $keys = element_children($element['choices'][$key1][$key2]);
325
      if (empty($keys)) {
326
        $element['choices'][$key1][$key2]['#parents'] = array_merge($parents,
327
          $element['choices'][$key1][$key2]['#parents']);
328
      }
329
      else {
330
        foreach ($keys as $key3) {
331
          $element['choices'][$key1][$key2][$key3]['#parents'] = array_merge($parents,
332
            $element['choices'][$key1][$key2][$key3]['#parents']);
333
        }
334
      }
335
    }
336
  }
337
  return $element;
338
}
339

    
340
/**
341
 * Implements hook_field_validate().
342
 */
343
function makemeeting_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {
344
  foreach ($items as $delta => $item) {
345
    if (isset($item['choices'])) {
346
      // Verify that we do not have duplicate dates
347
      $dates = array();
348
      foreach ($item['choices'] as $chid => $chvalue) {
349
        $date = implode('', $chvalue['chdate']);
350
        if (in_array($date, $dates)) {
351
          // Register an error with the choice id
352
          $errors[$field['field_name']][$langcode][$delta][] = array(
353
            'error'   => 'duplicate_dates',
354
            'message' => t('%name: you can\'t have duplicate dates.', array('%name' => $instance['label'])),
355
            'chid'    => $chid,
356
          );
357
        }
358
        else {
359
          $dates[] = $date;
360
        }
361
      }
362
    }
363
  }
364
}
365

    
366
/**
367
 * Implements hook_field_widget_error().
368
 */
369
function makemeeting_field_widget_error($element, $error) {
370
  // Show our error concerning duplicate dates here
371
  $choices = $element['choices_wrapper']['choices'];
372
  form_error($choices[$error['chid']]['chdate'], $error['message']);
373
}
374

    
375

    
376
/**
377
 * Implements hook_field_is_empty().
378
 */
379
function makemeeting_field_is_empty($item) {
380
  return empty($item['choices']);
381
}
382

    
383
/**
384
 * Implements hook_field_formatter_info().
385
 */
386
function makemeeting_field_formatter_info() {
387
  return array(
388
    'makemeeting_answers' => array(
389
      'label'       => t('Makemeeting answers'),
390
      'field types' => array('makemeeting'),
391
    ),
392
  );
393
}
394

    
395
/**
396
 * Implements hook_field_formatter_view().
397
 *
398
 * Build a renderable array for a field value.
399
 *
400
 * @param $entity_type
401
 *   The type of $entity.
402
 * @param $entity
403
 *   The entity being displayed.
404
 * @param $field
405
 *   The field structure.
406
 * @param $instance
407
 *   The field instance.
408
 * @param $langcode
409
 *   The language associated with $items.
410
 * @param $items
411
 *   Array of values for this field.
412
 * @param $display
413
 *   The display settings to use, as found in the 'display' entry of instance
414
 *   definitions. The array notably contains the following keys and values;
415
 *   - type: The name of the formatter to use.
416
 *   - settings: The array of formatter settings.
417
 *
418
 * @return array
419
 *  A renderable array for the $items, as an array of child elements keyed
420
 */
421
function makemeeting_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
422
  $content = array();
423
  foreach ($items as $delta => $item) {
424
    list($entity_id) = entity_extract_ids($entity_type, $entity);
425
    $instance += array(
426
      'entity_id' => $entity_id,
427
      'language'  => $langcode,
428
      'delta'     => $delta
429
    );
430
    // Define answers hidden status here as we have $field variable available
431
    $item['is_hidden'] = (bool) $item['hidden'];
432
    if ($item['is_hidden']) {
433
      $access = field_access('edit', $field, $entity_type, $entity);
434
      if ($entity_type == 'node') {
435
        $access = $access && node_access('update', $entity);
436
      }
437
      elseif (module_exists('entity')) {
438
        $access = $access && entity_access('edit', $entity_type, $entity);
439
      }
440
      $item['is_hidden'] = $item['is_hidden'] && !$access;
441
    }
442
    $content[] = drupal_get_form('makemeeting_answers_form_' . $instance['entity_id'], $item, $instance);
443
  }
444
  return $content;
445
}
446

    
447
/**
448
 * Implements hook_field_load().
449
 */
450
function makemeeting_field_load($entity_type, $entities, $field, $instances, $langcode, &$items, $age) {
451
  // Unserialize array values
452
  foreach ($entities as $id => $entity) {
453
    foreach ($items[$id] as $delta => $item) {
454
      if ($field['type'] == 'makemeeting' && is_string($items[$id][$delta]['choices'])) {
455
        $items[$id][$delta]['choices'] = unserialize($items[$id][$delta]['choices']);
456
      }
457
    }
458
  }
459
}
460

    
461
/**
462
 * Implements hook_field_presave().
463
 */
464
function makemeeting_field_presave($entity_type, $entity, $field, $instance, $langcode, &$items) {
465
  if ($field['type'] == 'makemeeting') {
466
    foreach ($items as $delta => $item) {
467
      if (isset($item['choices'])) {
468
        // Sort the choices by date
469
        $choices = array_values($item['choices']);
470
        usort($choices, '_makemeeting_sort_choices');
471
        // Affect timestamp as keys
472
        $timestamped_choices = array();
473
        foreach ($choices as $choice) {
474
          $timestamped_choices[_makemeeting_date_timestamp($choice['chdate'])] = $choice;
475
        }
476
        // Serialize the whole array
477
        $items[$delta]['choices'] = serialize($timestamped_choices);
478
      }
479
    }
480
  }
481
}
482

    
483
/**
484
 * Helper function to sort choice dates
485
 */
486
function _makemeeting_sort_choices($a, $b) {
487
  if ($a['chdate']['year'] == $b['chdate']['year']) {
488
    if ($a['chdate']['month'] == $b['chdate']['month']) {
489
      if ($a['chdate']['day'] == $b['chdate']['day']) {
490
        return 0;
491
      }
492
      else {
493
        return (int) $a['chdate']['day'] < (int) $b['chdate']['day'] ? -1 : 1;
494
      }
495
    }
496
    else {
497
      return (int) $a['chdate']['month'] < (int) $b['chdate']['month'] ? -1 : 1;
498
    }
499
  }
500
  else {
501
    return (int) $a['chdate']['year'] < (int) $b['chdate']['year'] ? -1 : 1;
502
  }
503
}
504

    
505

    
506
/**
507
 * Submit handler to add more choices or suggestions to a poll form,
508
 * or to copy the first row of suggestions to the others
509
 *
510
 * This handler is run regardless of whether JS is enabled or not. It makes
511
 * changes to the form state. If the button was clicked with JS disabled, then
512
 * the page is reloaded with the complete rebuilt form. If the button was
513
 * clicked with JS enabled, then ajax_form_callback() calls makemeeting_choice_js() to
514
 * return just the changed part of the form.
515
 */
516
function makemeeting_choices_submit($form, &$form_state) {
517
  $clicked_button = $form_state['clicked_button'];
518

    
519
  // Get the element
520
  $element =& drupal_array_get_nested_value($form_state['input'], $clicked_button['#parents']);
521

    
522
  // If clicked button is 'Remove', unset corresponding choice
523
  if ($clicked_button['#value'] == t('Remove')) {
524
    unset($element['choices'][$clicked_button['#name']]);
525
    $form_state['choice_count'] = count($element['choices']);
526
  }
527

    
528
  // Affect timestamp as key to each choice
529
  $choices = array();
530
  if (isset($element['choices'])) {
531
    foreach ($element['choices'] as $choice) {
532
      $choices['choices'][_makemeeting_date_timestamp($choice['chdate'])] = $choice;
533
    }
534
  }
535

    
536
  // Handle other operations
537
  if (!empty($form_state['values']['op'])) {
538
    switch ($form_state['values']['op']) {
539
      // Add 1 more choice to the form.
540
      case t('More choices'):
541
        // Increment choice count
542
        $form_state['choice_count'] = count($element['choices']) + 1;
543
        break;
544

    
545
      // Add 1 more suggestion to the form.
546
      case t('More suggestions'):
547
        $form_state['suggestion_count']++;
548
        break;
549

    
550
      // Copy first row suggestions to the other rows.
551
      case t('Copy and paste first row');
552
        $first = reset($choices['choices']);
553
        foreach (array_keys($choices['choices']) as $key) {
554
          $choices['choices'][$key]['chsuggestions'] = $first['chsuggestions'];
555
        }
556
        break;
557
    }
558
  }
559

    
560
  // Replace submitted values by our own work
561
  drupal_array_set_nested_value($form_state['input'], $clicked_button['#parents'], $choices);
562
  drupal_array_set_nested_value($form_state['values'], $clicked_button['#parents'], $choices);
563

    
564
  // Require the widget form to rebuild the choices
565
  // with the values in $form_state
566
  $form_state['rebuild'] = TRUE;
567
}
568

    
569
/**
570
 * Ajax callback in response to new choices being added to the form.
571
 *
572
 * This returns the new page content to replace the page content made obsolete
573
 * by the form submission.
574
 */
575
function makemeeting_choice_js($form, $form_state) {
576
  // Get the element from the parents of the clicked button
577
  $element = drupal_array_get_nested_value($form, $form_state['clicked_button']['#parents']);
578
  return $element['choices_wrapper'];
579
}