Projet

Général

Profil

Paste
Télécharger (18,6 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / makemeeting / makemeeting.module @ b75b6b8b

1
<?php
2

    
3
/**
4
 * @file
5
 * Hooks and field implementations
6
 */
7

    
8
// Define choice constants
9
define('MAKEMEETING_ANSWER_PERM', 'answer makemeeting form');
10
define('MAKEMEETING_EDIT_PERM', 'edit any makemeeting answer');
11
define('MAKEMEETING_DELETE_PERM', 'delete any makemeeting answer');
12
define('MAKEMEETING_NO', 0);
13
define('MAKEMEETING_YES', 1);
14
define('MAKEMEETING_MAYBE', 2);
15

    
16
/**
17
 * Include required files.
18
 */
19
include_once 'makemeeting.field.inc';
20

    
21
/**
22
 * Implements hook_permission().
23
 */
24
function makemeeting_permission() {
25
  return array(
26
    MAKEMEETING_ANSWER_PERM => array(
27
      'title' => t('Provide an answer to makemeeting forms'),
28
      'description' => t('Give the ability to make a choice in makemeeting answer forms.'),
29
    ),
30
    MAKEMEETING_EDIT_PERM => array(
31
      'title' => t('Edit any makemeeting answers'),
32
      'description' => t('Edit answers given by other users.'),
33
    ),
34
    MAKEMEETING_DELETE_PERM => array(
35
      'title' => t('Delete any makemeeting answers'),
36
      'description' => t('Delete answers given by other users.'),
37
    ),
38
  );
39
}
40

    
41
/**
42
 * Implements hook_menu().
43
 */
44
function makemeeting_menu() {
45
  $items['makemeeting/delete-answer/%makemeeting_answer'] = array(
46
    'title' => 'Remove an answer',
47
    'page callback' => 'drupal_get_form',
48
    'page arguments' => array('makemeeting_delete_answer', 2),
49
    'access callback' => 'makemeeting_delete_answer_access',
50
    'access arguments' => array(2),
51
    'type' => MENU_CALLBACK,
52
  );
53
  $items['makemeeting/edit-answer/%makemeeting_answer'] = array(
54
    'title' => 'Edit an answer',
55
    'page callback' => 'makemeeting_edit_answer',
56
    'page arguments' => array(2),
57
    'access callback' => 'makemeeting_edit_answer_access',
58
    'access arguments' => array(2),
59
    'type' => MENU_CALLBACK,
60
  );
61
  return $items;
62
}
63

    
64
/**
65
 * Load an answer
66
 *
67
 * @param $answer_id
68
 * @return mixed
69
 */
70
function makemeeting_answer_load($answer_id) {
71
  $answer = db_select('makemeeting_answers', 'm')
72
            ->fields('m')
73
            ->condition('answer_id', $answer_id)
74
            ->execute()
75
            ->fetchAssoc();
76
  $answer['value'] = unserialize($answer['value']);
77
  return $answer ? (object) $answer : FALSE;
78
}
79

    
80
/**
81
 * Access callback: delete an answer
82
 *
83
 * @param $answer
84
 * @return bool
85
 */
86
function makemeeting_delete_answer_access($answer) {
87
  global $user;
88
  return user_access(MAKEMEETING_DELETE_PERM) || (user_is_logged_in() && $answer->uid == $user->uid);
89
}
90

    
91
/**
92
 * Access callback: edit an answer
93
 *
94
 * @param $answer
95
 * @return bool
96
 */
97
function makemeeting_edit_answer_access($answer) {
98
  global $user;
99
  return user_access(MAKEMEETING_EDIT_PERM) || (user_is_logged_in() && $answer->uid == $user->uid);
100
}
101

    
102
/**
103
 * Menu callback: delete an answer
104
 *
105
 * @param $form
106
 * @param $form_state
107
 * @param $answer
108
 * @param bool $redirect
109
 * @return
110
 */
111
function makemeeting_delete_answer($form, &$form_state, $answer, $redirect = TRUE) {
112
  $form['#answer'] = $answer;
113
  return confirm_form(
114
    $form,
115
    t('Are you sure you want to delete the answer?'),
116
    ''
117
  );
118
}
119

    
120
/**
121
 * Form callback: confirm answer deletion
122
 */
123
function makemeeting_delete_answer_submit($form, &$form_state) {
124
  $answer = $form['#answer'];
125
  db_delete('makemeeting_answers')
126
  ->condition('answer_id', $answer->answer_id)
127
  ->execute();
128

    
129
  _makemeeting_clear_related_entity_cache($answer->entity_type, $answer->entity_id);
130
}
131

    
132
/**
133
 * Menu callback: edit an answer
134
 *
135
 * @param object $answer
136
 *  The loaded answer
137
 * @param string $type
138
 *  Ajax or nojs
139
 * @return array|void
140
 *  The entire form or just the row depending on ajax
141
 */
142
function makemeeting_edit_answer($answer, $type = 'ajax') {
143
  $entities = entity_load($answer->entity_type, array($answer->entity_id));
144
  if (empty($entities)) {
145
    return drupal_not_found();
146
  }
147
  $entity = $entities[$answer->entity_id];
148
  $field = $entity->{$answer->field_name}[$answer->language][$answer->delta];
149
  $form = drupal_get_form('makemeeting_answers_form_' . $answer->entity_id, $field, (array) $answer, $answer);
150
  if ($type == 'ajax') {
151
    $form['#theme'] = 'makemeeting_answer_row';
152
    // Replace 'ajax' word in form action
153
    $form['#action'] = str_replace('/ajax/', '/nojs/', $form['#action']);
154
    $colspan = 1 + count(element_children($form['answers']));
155
    $output = '<tr class="editing"><td colspan="' . $colspan . '">' . drupal_render($form) . '</td></tr>';
156
    $commands = array();
157
    // See ajax_example_advanced.inc for more details on the available commands
158
    // and how to use them.
159
    $commands[] = ajax_command_replace('#answer-' . $answer->answer_id, $output);
160
    $page = array('#type' => 'ajax', '#commands' => $commands);
161
    return ajax_deliver($page);
162
  }
163
  return $form;
164
}
165

    
166

    
167
/**
168
 * Ajax callback in response to an answer that has been edited.
169
 *
170
 * This returns the new form content to replace the form content made obsolete
171
 * by the form submission.
172
 */
173
function makemeeting_answer_js($form, $form_state) {
174
  $values = $form_state['values'];
175
  $answer = $values['answer_edited'];
176
  $entities = entity_load($answer->entity_type, array($answer->entity_id));
177
  if (empty($entities)) {
178
    return drupal_not_found();
179
  }
180
  $entity = $entities[$answer->entity_id];
181
  $field = $entity->{$answer->field_name}[$answer->language][$answer->delta];
182
  // We need to empty POST data to prevent the form
183
  // from being processed as submitted
184
  $_POST = array();
185
  $new_form = drupal_get_form('makemeeting_answers_form_' . $answer->entity_id, $field, $values);
186
  // AJaX processing overrides form action, now retrieving the correct one
187
  $query = parse_url($form['#action']);
188
  $parameters = drupal_get_query_array($query['query']);
189
  $new_form['#action'] = $parameters['destination'];
190
  return $new_form;
191
}
192

    
193
/**
194
 * Implements hook_forms().
195
 */
196
function makemeeting_forms($form_id) {
197
  $forms = array();
198
  if (preg_match('/^makemeeting_answers_form_\d+?$/', $form_id)) {
199
    $forms = array(
200
      $form_id => array(
201
        'callback' => 'makemeeting_answers_form',
202
      )
203
    );
204
  }
205
  return $forms;
206
}
207

    
208
/**
209
 * Form callback: enables users to answer a makemeeting poll
210
 */
211
function makemeeting_answers_form($form, &$form_state, $item, $instance, $answer = NULL) {
212
  global $user;
213
  $form = array();
214
  $form['#item'] = $item;
215
  $form['#theme'] = 'makemeeting_answers';
216

    
217
  // Force the id of the form as it might get overriden in AJAX-loaded forms
218
  $form['#id'] = 'makemeeting-answers-form';
219

    
220
  // Pass entity-related values in the form
221
  foreach (array('field_name', 'entity_type', 'deleted', 'entity_id',
222
             'language', 'delta') as $info) {
223
    $form[$info] = array(
224
      '#type' => 'value',
225
      '#value' => $instance[$info],
226
    );
227
  }
228

    
229
  // Pass answer being edited
230
  if ($answer) {
231
    $form['answer_edited'] = array(
232
      '#type' => 'value',
233
      '#value' => $answer,
234
    );
235
  }
236
  // Include current destination for ajax calls
237
  if (!isset($form_state['ajax_destination'])) {
238
    $form_state['ajax_destination'] = drupal_get_destination();
239
  }
240

    
241
  // Include the name of the current user
242
  $form['name'] = array(
243
    '#type' => 'textfield',
244
    '#size' => 22,
245
  );
246
  if (!user_is_logged_in()) {
247
    // This is an HTML5 attribute
248
    $form['name']['#attributes'] = array('placeholder' => t('Your name (required)'));
249
    $form['name']['#required'] = TRUE;
250
  }
251
  else {
252
    $form['name']['#default_value'] = format_username($user);
253
    $form['name']['#disabled'] = TRUE;
254
  }
255
  if (!empty($answer)) {
256
    if ($answer->uid > 0) {
257
      $account = user_load($answer->uid);
258
      $form['name']['#default_value'] = format_username($account);
259
    }
260
    else {
261
      $form['name']['#default_value'] = $answer->name;
262
    }
263
  }
264

    
265
  // If the form is limited, fetch already submitted answers
266
  $answers = array();
267
  if ($item['limit'] > 0) {
268
    $select = db_select('makemeeting_answers', 'ma')
269
              ->fields('ma', array('value'));
270
    foreach (array('field_name', 'entity_type', 'deleted', 'entity_id', 'language', 'delta') as $info) {
271
      $select->condition($info, $instance[$info]);
272
    }
273
    // Filter out answer being edited
274
    if ($answer) {
275
      $select->condition('answer_id', $answer->answer_id, '!=');
276
    }
277
    $results = $select->execute();
278
    // And add each answer to our results array for futher use
279
    foreach ($results as $result) {
280
      $_answer = unserialize($result->value);
281
      if (is_array($_answer)) {
282
        foreach ($_answer as $key => $value) {
283
          if ($value) {
284
            $answers[$key] = empty($answers[$key]) ? 1 : $answers[$key] + 1;
285
          }
286
        }
287
      }
288
      elseif (is_string($_answer) && $_answer) {
289
        $answers[$_answer] = empty($answers[$_answer]) ? 1 : $answers[$_answer] + 1;
290
      }
291
    }
292
  }
293

    
294
  // Possible answers
295
  $form['answers'] = array();
296
  foreach ($item['choices'] as $choice) {
297
    $chdate = _makemeeting_date_timestamp($choice['chdate']);
298
    $count = 0;
299
    foreach ($choice['chsuggestions'] as $id => $text) {
300
      // Add a form element only if there's a suggestion label
301
      // or it is the first suggestion for this date
302
      if ($text || (!$text && !$count)) {
303
        _makemeeting_answer_element($form, $item, $id, $chdate, $text, $answers, $answer);
304
      }
305
      $count++;
306
    }
307
  }
308

    
309
  $form['submit'] = array(
310
    '#type' => 'submit',
311
    '#value' => t('Submit'),
312
  );
313
  if (!empty($answer)) {
314
    // Modify form submit to include ajax behavior
315
    $form['submit']['#ajax'] = array(
316
      'callback' => 'makemeeting_answer_js',
317
      'wrapper' => 'makemeeting-answers-form',
318
      'effect' => 'fade',
319
    );
320
  }
321

    
322
  return $form;
323
}
324

    
325
/**
326
 * Form validate: validate answers
327
 */
328
function makemeeting_answers_form_validate($form, &$form_state) {
329
  // The required attribute won't work, so we display a single message
330
  if (!$form_state['values']['name']) {
331
    drupal_set_message(t('You must enter your name.'), 'error');
332
  }
333
  // Check is the user has already voted
334
  if (user_is_logged_in() && empty($form_state['values']['answer_edited'])) {
335
    global $user;
336
    $select = db_select('makemeeting_answers', 'ma');
337
    foreach (array('field_name', 'entity_type', 'deleted', 'entity_id',
338
               'language', 'delta') as $info) {
339
      $select->condition($info, $form_state['values'][$info]);
340
    }
341
    $result = $select->condition('uid', $user->uid)
342
                     ->countQuery()
343
                     ->execute()
344
                     ->fetchField();
345
    if ($result) {
346
      form_error($form, t('You already voted on this poll.'));
347
    }
348
  }
349
}
350

    
351
/**
352
 * Form submit: store answers
353
 */
354
function makemeeting_answers_form_submit($form, $form_state) {
355
  global $user;
356
  if (!empty($form_state['values']['answer_edited'])) {
357
    db_update('makemeeting_answers')
358
    ->fields(array(
359
      'name' => $form_state['values']['name'],
360
      'value' => serialize($form_state['values']['answers']),
361
    ))
362
    ->condition('answer_id', $form_state['values']['answer_edited']->answer_id)
363
    ->execute();
364
  }
365
  else {
366
    $fields = array();
367
    foreach (array('field_name', 'entity_type', 'deleted', 'entity_id',
368
               'language', 'delta', 'name') as $field) {
369
      $fields[$field] = $form_state['values'][$field];
370
    }
371
    db_insert('makemeeting_answers')
372
    ->fields($fields + array(
373
        'value' => serialize($form_state['values']['answers']),
374
        'uid'   => $user->uid,
375
      ))
376
    ->execute();
377
  }
378

    
379
  // Spoof drupal_get_destination()'s cache for ajax calls
380
  if (current_path() === 'system/ajax') {
381
    $destination = &drupal_static('drupal_get_destination');
382
    $destination = $form_state['ajax_destination'];
383
  }
384

    
385
  _makemeeting_clear_related_entity_cache($form_state['values']['entity_type'], $form_state['values']['entity_id']);
386
}
387

    
388
/**
389
 * Implements hook_theme().
390
 */
391
function makemeeting_theme($existing, $type, $theme, $path) {
392
  return array(
393
    'makemeeting_choices' => array(
394
      'render element' => 'form',
395
      'file' => 'makemeeting.theme.inc',
396
    ),
397
    'makemeeting_answers' => array(
398
      'render element' => 'form',
399
      'file' => 'makemeeting.theme.inc',
400
    ),
401
    'makemeeting_answer_row' => array(
402
      'render element' => 'form',
403
      'file' => 'makemeeting.theme.inc',
404
    ),
405
  );
406
}
407

    
408

    
409
/**
410
 * Helper function to convert date field value into an Unix timestamp
411
 *
412
 * @param $array array Date field value (month, day and year)
413
 * @return int Unix timestamp
414
 */
415
function _makemeeting_date_timestamp($array) {
416
  $date_str = "{$array['day']}-{$array['month']}-{$array['year']}";
417
  $date = new DateTime($date_str);
418
  return $date->getTimestamp();
419
}
420

    
421
/**
422
 * Helper function for selecting choices
423
 *
424
 * @param bool $three_choices If should be returning three choices
425
 * @return array An array of options for radios
426
 */
427
function _makemeeting_options($three_choices = FALSE) {
428
  $options = array(
429
    MAKEMEETING_NO => t('No'),
430
    MAKEMEETING_MAYBE => t('Maybe'),
431
    MAKEMEETING_YES => t('Yes'),
432
  );
433
  if (!$three_choices) {
434
    unset($options[MAKEMEETING_MAYBE]);
435
  }
436
  return $options;
437
}
438

    
439
/**
440
 * Helper function to provide an answer form element
441
 *
442
 * @param $form
443
 *  Form to be modified
444
 * @param $item
445
 *  Item providing settings for the answer form
446
 * @param $id
447
 *  Suggestion id
448
 * @param $chdate
449
 *  Suggestion date
450
 * @param $text
451
 *  Suggestion text
452
 * @param array $answers
453
 *  Already submitted answers
454
 * @param null $answer
455
 *  Answer being edited
456
 */
457
function _makemeeting_answer_element(&$form, $item, $id, $chdate, $text, $answers = array(), $answer = NULL) {
458
  $key = $chdate . ':' . $id;
459
  // If the limit is reached for this option, display a markup text
460
  if ($item['limit'] > 0 && isset($answers[$key]) && $answers[$key] >= $item['limit']) {
461
    $form['answers'][$key] = array(
462
      '#markup' => t('Unavailable'),
463
    );
464
  }
465
  // Else add a form element
466
  else {
467
    $title = format_date($chdate, 'custom', 'l j F Y') . ' ' . $text;
468
    $form['answers'][$key] = array(
469
      '#type' => $item['one_option'] ? 'radio' : ($item['yesnomaybe'] ? 'radios' : 'checkbox'),
470
      '#attributes' => array('title' => check_plain($title)),
471
      '#parents' => array('answers', $key),
472
    );
473

    
474
    if ($item['one_option']) {
475
      $form['answers'][$key]['#parents'] = array('answers');
476
      $form['answers'][$key]['#return_value'] = $key;
477
    }
478
    else {
479
      $form['answers'][$key]['#options'] = _makemeeting_options($item['yesnomaybe']);
480
    }
481
    if ($item['yesnomaybe']) {
482
      $form['answers'][$key]['#default_value'] = MAKEMEETING_NO;
483
    }
484
    // Display previous choice if answer is being edited
485
    if ($answer && !empty($answer->value[$key])) {
486
      $form['answers'][$key]['#default_value'] = $answer->value[$key];
487
    }
488
  }
489
}
490

    
491
/**
492
 * Invalidate related entity caches.
493
 *
494
 * Clear the core page cache and the contrib Entity Cache of the entity.
495
 *
496
 * @param $entity_type
497
 * @param $entity_id
498
 */
499
function _makemeeting_clear_related_entity_cache($entity_type, $entity_id) {
500
  // Clear entity_type cache.
501
  entity_get_controller($entity_type)->resetCache();
502

    
503
  // Clear core page cache.
504
  if (variable_get('cache', FALSE)) {
505
    $entities = entity_load($entity_type, array($entity_id));
506
    $entity = reset($entities);
507

    
508
    global $base_root;
509
    $uri = entity_uri($entity_type, $entity);
510
    // Clear the non-aliased page.
511
    cache_clear_all($base_root . base_path() . $uri['path'], 'cache_page');
512
    // Clear the aliased page.
513
    cache_clear_all(url($uri['path'], array('absolute'=>TRUE)), 'cache_page');
514
  }
515

    
516
  // Clear referencing entities cache.
517
  if (module_exists('entityreference')) {
518
    _makemeeting_clear_referencing_entity_cache($entity_type, $entity_id);
519
  }
520
}
521

    
522
/**
523
 * Invalidate referencing entities caches.
524
 *
525
 * Recursive function clearing the cache for all the entities that references
526
 * the given one using the entityreference module.
527
 *
528
 * @param $entity_type
529
 * @param $entity_id
530
 */
531
function _makemeeting_clear_referencing_entity_cache($entity_type, $entity_id) {
532
  $cleared = &drupal_static(__FUNCTION__, array());
533
  // Avoid the cache to be cleared twice for the same entity.
534
  if (!empty($cleared[$entity_type][$entity_id])) {
535
    return;
536
  }
537
  $cleared[$entity_type][$entity_id] = TRUE;
538

    
539
  // Get the entity bundle to retreive the field that can reference this kind
540
  // of entities.
541
  $entity = entity_load_single($entity_type, $entity_id);
542
  list(, , $bundle) = entity_extract_ids($entity_type, $entity);
543
  $fields_referencing = _makemeeting_clear_referencing_fields($entity_type, $bundle);
544

    
545
  // For each referencing field, get the entities that references the current
546
  // entity using the field and clear their cache.
547
  foreach ($fields_referencing as $field_name) {
548
    $query = new EntityFieldQuery();
549
    $query->fieldCondition($field_name, 'target_id', $entity_id);
550
    foreach ($query->execute() as $referent_entity_type => $referent_entities) {
551
      foreach ($referent_entities as $referent_entity_id => $referent_entity) {
552
        _makemeeting_clear_related_entity_cache($referent_entity_type, $referent_entity_id);
553
      }
554
    }
555
  }
556
}
557

    
558
/**
559
 * Get all fields referencing a given bundle.
560
 *
561
 * @param $entity_type
562
 *   The entity type of the bundle.
563
 * @param $bundle
564
 *   The referenced bundle.
565
 * @return array
566
 *   An array of field names being able to reference the given bundle.
567
 */
568
function _makemeeting_clear_referencing_fields($entity_type, $bundle) {
569
  $static = &drupal_static(__FUNCTION__, array());
570
  // Use a static temporary cache to avoid using too mush resources.
571
  if (array_key_exists($entity_type, $static) && array_key_exists($bundle, $static[$entity_type])) {
572
    return $static[$entity_type][$bundle];
573
  }
574

    
575
  $fields = field_info_fields();
576
  $static[$entity_type][$bundle] = array();
577
  foreach ($fields as $field_name => $field) {
578
    // Check if the field is an entityreference field.
579
    if ($field['type'] != 'entityreference') {
580
      continue;
581
    }
582
    // Check if the entity_type match the target_type of the field.
583
    if ($field['settings']['target_type'] != $entity_type) {
584
      continue;
585
    }
586
    // If the field is using the base handler check if the bundle is allowed.
587
    if ($field['settings']['handler'] == 'base' && !in_array($bundle, $field['settings']['handler_settings']['target_bundles'])) {
588
      continue;
589
    }
590
    $static[$entity_type][$bundle][] = $field_name;
591
  }
592

    
593
  return $static[$entity_type][$bundle];
594
}
595

    
596
/**
597
 * Implements hook_field_views_data_alter().
598
 */
599
function makemeeting_field_views_data_alter(&$result, $field, $module) {
600
  if ($module == 'makemeeting') {
601
    foreach ($result as $table => $data) {
602
      $field_name = $field['field_name'];
603

    
604
      // We will replace the filter handlers with a friendlier one.
605
      $result[$table][$field_name . '_closed']['filter']['handler'] = 'views_handler_filter_boolean_operator';
606
      $result[$table][$field_name . '_hidden']['filter']['handler'] = 'views_handler_filter_boolean_operator';
607
      $result[$table][$field_name . '_one_option']['filter']['handler'] = 'views_handler_filter_boolean_operator';
608
      $result[$table][$field_name . '_yesnomaybe']['filter']['handler'] = 'views_handler_filter_boolean_operator';
609
    }
610
  }
611
}