Parent Directory
|
Revision Log
|
Revision Graph
rough in double-opt-in function
| 1 | <?php |
| 2 | // $Id: connect.module,v 1.6 2009/04/03 19:09:52 stevem Exp $ |
| 3 | |
| 4 | /* |
| 5 | * connect.module |
| 6 | * |
| 7 | * steve@openconcept.ca |
| 8 | * |
| 9 | * This module uses a couple of GPL-licensed icons from the |
| 10 | * Silk icon set 1.3: http://www.famfamfam.com/lab/icons/silk/ |
| 11 | * |
| 12 | */ |
| 13 | |
| 14 | |
| 15 | /** |
| 16 | * Implementation of hook_help |
| 17 | */ |
| 18 | function connect_help($section = 'admin/help#connect') { |
| 19 | $output = ''; |
| 20 | switch ($section) { |
| 21 | case 'admin/modules#description': |
| 22 | $output = t("Allows the creation of online actions, petitions, event registration ... anything where you want to associate two kinds of data in a one-to-many way."); |
| 23 | break; |
| 24 | case 'admin/help#connect': |
| 25 | $output = file_get_contents(drupal_get_path('module', 'connect') .'/connect_help.html'); |
| 26 | break; |
| 27 | } |
| 28 | return $output; |
| 29 | } |
| 30 | |
| 31 | |
| 32 | /** |
| 33 | * Implementation of hook_perm |
| 34 | */ |
| 35 | function connect_perm() { |
| 36 | return array('manage connect', 'create connect campaign', 'access connect'); |
| 37 | } |
| 38 | |
| 39 | |
| 40 | /* |
| 41 | * Implementation of hook_cron |
| 42 | */ |
| 43 | function connect_cron() { |
| 44 | // clear out expired cached data |
| 45 | require_once(drupal_get_path('module', 'connect') .'/connect_lookup.php'); |
| 46 | $cache_names = _connect_get_cache_names(); |
| 47 | foreach ($cache_names as $name => $title) { |
| 48 | $lifetime = variable_get("connect_cache_$name", '0 s'); |
| 49 | list($num, $unit) = sscanf($lifetime, '%d %s'); |
| 50 | if ($num == 0) { |
| 51 | continue; |
| 52 | } |
| 53 | switch ($unit) { |
| 54 | case 'm' : |
| 55 | $seconds = 60; |
| 56 | break; |
| 57 | case 'h' : |
| 58 | $seconds = 60*60; |
| 59 | break; |
| 60 | case 'd' : |
| 61 | $seconds = 60*60*24; |
| 62 | break; |
| 63 | default : |
| 64 | continue; |
| 65 | break; |
| 66 | } |
| 67 | $sql = "DELETE FROM {connect_cache} WHERE type='%s' AND created < %d;"; |
| 68 | $result = db_query($sql, $name, time() - $num * $seconds); |
| 69 | } |
| 70 | } |
| 71 | |
| 72 | |
| 73 | /** |
| 74 | * Implementation of hook_menu |
| 75 | */ |
| 76 | function connect_menu($may_cache) { |
| 77 | require_once('connect_actions.php'); |
| 78 | |
| 79 | $items = array(); |
| 80 | |
| 81 | if ($may_cache) { |
| 82 | // administer module settings |
| 83 | $items[] = array( |
| 84 | 'path' => 'admin/settings/connect', |
| 85 | 'title' => t('Connect'), |
| 86 | 'description' => t("Configure your parent and participant node types."), |
| 87 | 'callback' => 'connect_admin_overview_page', |
| 88 | 'access' => user_access('manage connect'), |
| 89 | ); |
| 90 | |
| 91 | // register callbacks for connect_action functions |
| 92 | $empty = array(); |
| 93 | $all_actions = connect_get_actions(); |
| 94 | foreach ($all_actions as $action) { |
| 95 | $action_menu = call_user_func($action, $empty, $empty, 'menu'); |
| 96 | if (!empty($action_menu)) { |
| 97 | $items = array_merge($items, $action_menu); |
| 98 | } |
| 99 | } |
| 100 | } |
| 101 | |
| 102 | //TODO move the non-cached menu callbacks to the action functions too |
| 103 | else { |
| 104 | if (arg(0) == 'node' && is_numeric(arg(1))) { |
| 105 | $node = node_load(arg(1)); |
| 106 | if ( !connect_is_parent_node($node) ) { |
| 107 | return; |
| 108 | } |
| 109 | |
| 110 | $can_admin = (($user->id == $node->uid && user_access('create connect campaign')) || user_access('manage connect')); |
| 111 | |
| 112 | // cache report ? |
| 113 | $child = array(); |
| 114 | $cache_required = FALSE; |
| 115 | $actions = connect_get_actions($node->nid); |
| 116 | foreach ($actions as $action) { |
| 117 | $desc = call_user_func($action, $node, $child, 'describe', 'parent'); |
| 118 | if (isset($desc['cache']) && $desc['cache'] == TRUE) { |
| 119 | $items[] = array( |
| 120 | 'path' => 'node/'. arg(1) .'/connect/status', |
| 121 | 'title' => t('Cache report'), |
| 122 | 'callback' => 'connect_node_list_page', |
| 123 | 'access' => $can_admin, |
| 124 | 'type' => MENU_LOCAL_TASK, |
| 125 | 'weight' => 10, |
| 126 | ); |
| 127 | break; |
| 128 | } |
| 129 | } |
| 130 | |
| 131 | // re-send emails |
| 132 | if (in_array('connect_action_send_email', $actions)) { |
| 133 | $items[] = array( |
| 134 | 'path' => 'node/'. arg(1) .'/connect/resend_failed_mail', |
| 135 | 'title' => t('Re-send emails'), |
| 136 | 'callback' => '_connect_action_send_email_resend_failed', |
| 137 | 'access' => $can_admin, |
| 138 | 'type' => MENU_LOCAL_TASK, |
| 139 | 'weight' => 10 |
| 140 | ); |
| 141 | } |
| 142 | |
| 143 | // select functions |
| 144 | $items[] = array( |
| 145 | 'path' => 'node/'. arg(1) .'/connect/admin/functions', |
| 146 | 'title' => t('Functions'), |
| 147 | 'callback' => 'connect_node_config_functions', |
| 148 | 'access' => $can_admin, |
| 149 | 'type' => MENU_LOCAL_TASK, |
| 150 | 'weight' => 5 |
| 151 | ); |
| 152 | |
| 153 | // configure functions |
| 154 | $items[] = array( |
| 155 | 'path' => 'node/'. arg(1) .'/connect/admin/settings', |
| 156 | 'title' => t('Settings'), |
| 157 | 'callback' => 'connect_node_config_settings', |
| 158 | 'access' => $can_admin, |
| 159 | 'type' => MENU_LOCAL_TASK, |
| 160 | 'weight' => 6 |
| 161 | ); |
| 162 | } |
| 163 | |
| 164 | //embedded function |
| 165 | $items[] = array( |
| 166 | 'path' => 'connect/embed/'. arg(2), |
| 167 | 'title' => t('Connect Embed'), |
| 168 | 'access' => TRUE, |
| 169 | 'callback' => '_connect_embed', |
| 170 | 'callback arguments' => array(arg(2)), |
| 171 | 'type' => MENU_CALLBACK, |
| 172 | ); |
| 173 | } |
| 174 | return $items; |
| 175 | } |
| 176 | |
| 177 | |
| 178 | /** |
| 179 | * Implementation of hook_block |
| 180 | */ |
| 181 | function connect_block($op = 'list', $delta = 0, $edit = array()) { |
| 182 | require_once('connect_blocks.php'); |
| 183 | switch ($op) { |
| 184 | case 'list': |
| 185 | return connect_block_list(); |
| 186 | break; |
| 187 | |
| 188 | case 'view': |
| 189 | return connect_block_view($delta); |
| 190 | break; |
| 191 | } |
| 192 | } |
| 193 | |
| 194 | |
| 195 | /** |
| 196 | * Implementation of hook_nodeapi |
| 197 | */ |
| 198 | function connect_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) { |
| 199 | $is_parent = connect_is_parent_node($node); |
| 200 | $is_child = $is_parent ? FALSE : connect_is_participant_node($node); |
| 201 | if (!$is_parent && !$is_child) { |
| 202 | return; |
| 203 | } |
| 204 | require_once('connect_actions.php'); |
| 205 | |
| 206 | // prevent PHP4 from throwing a fit |
| 207 | if (PHP_VERSION < 5) error_reporting(E_ALL ^ E_NOTICE ^ E_WARNING); |
| 208 | |
| 209 | // child nodes |
| 210 | if ($is_child) { |
| 211 | // prevent looping |
| 212 | $lock = "connect_child_lock_$op"; |
| 213 | if (!isset($_SESSION[$lock])) { |
| 214 | $_SESSION[$lock] = TRUE; |
| 215 | } |
| 216 | else { |
| 217 | return $_SESSION[$lock]; |
| 218 | } |
| 219 | |
| 220 | // call hooks |
| 221 | $parent =& _connect_parent_node(); |
| 222 | if (!$parent) { |
| 223 | $temp = node_load(connect_get_parent($node)); |
| 224 | $parent =& _connect_parent_node($temp); |
| 225 | } |
| 226 | connect_call_hooks($parent, $node, $op, 'child'); |
| 227 | |
| 228 | // release lock |
| 229 | unset($_SESSION[$lock]); |
| 230 | return; |
| 231 | } |
| 232 | |
| 233 | // don't cache me |
| 234 | global $conf; |
| 235 | $conf['cache'] = FALSE; |
| 236 | |
| 237 | // parent nodes |
| 238 | switch ($op) { |
| 239 | case 'delete' : |
| 240 | // TODO: delete all child nodes too? |
| 241 | $sql = "DELETE FROM {connect_data} WHERE pid=%d;"; |
| 242 | db_query($sql, $node->nid); |
| 243 | break; |
| 244 | |
| 245 | case 'view': |
| 246 | if ($teaser == TRUE || !user_access('access connect') || !_connect_captcha_test()) { |
| 247 | break; |
| 248 | } |
| 249 | drupal_add_css(drupal_get_path('module', 'connect') .'/connect.css'); |
| 250 | $parent =& _connect_parent_node($node); // save a reference to this node |
| 251 | $child = NULL; |
| 252 | |
| 253 | // allow actions to add display items to the node |
| 254 | $weight = 5; // arbitrary |
| 255 | $additions = connect_call_hooks($parent, $child, 'display', 'parent'); |
| 256 | foreach ($additions as $function => $add) { |
| 257 | $node->content[$function] = array( |
| 258 | '#value' => t($add['data']), |
| 259 | '#weight' => $weight++, |
| 260 | ); |
| 261 | } |
| 262 | |
| 263 | // display messages, forms, etc. depending on status |
| 264 | $show_form = TRUE; |
| 265 | $status = connect_call_hooks($parent, $child, 'status', 'parent'); |
| 266 | foreach ($status as $add) { |
| 267 | if ( isset($add['show_form']) && $add['show_form'] === FALSE ) { |
| 268 | $show_form = FALSE; |
| 269 | } |
| 270 | if ( isset($add['status']) && !empty($add['status']) ) { |
| 271 | drupal_set_message( t($add['status']) ); |
| 272 | } |
| 273 | } |
| 274 | |
| 275 | // add form |
| 276 | if ($show_form) { |
| 277 | $form = drupal_get_form('connect_form_page'); |
| 278 | $node->content['connect_form_page'] = array( |
| 279 | '#value' => $form, |
| 280 | '#weight' => 20, |
| 281 | ); |
| 282 | } |
| 283 | break; |
| 284 | } |
| 285 | } |
| 286 | |
| 287 | /* |
| 288 | * Implementation of hook_user |
| 289 | * |
| 290 | */ |
| 291 | function connect_user($op, &$edit, &$account, $category = NULL) { |
| 292 | switch ($op) { |
| 293 | //clear session info on login/out |
| 294 | case 'logout': |
| 295 | case 'login': |
| 296 | foreach ($_SESSION as $key => $value) { |
| 297 | if (strpos($key, 'connect_') !== FALSE) unset($_SESSION[$key]); |
| 298 | } |
| 299 | break; |
| 300 | } |
| 301 | } |
| 302 | |
| 303 | |
| 304 | /** |
| 305 | * Menu callback: cache report |
| 306 | */ |
| 307 | function connect_node_list_page() { |
| 308 | $nid = arg(1); |
| 309 | $parent = node_load($nid); |
| 310 | _connect_parent_node($parent); // cache the parent |
| 311 | return drupal_get_form('connect_campaign_cache_report_form'); |
| 312 | } |
| 313 | |
| 314 | |
| 315 | /** |
| 316 | * Menu callback: displays function selection page |
| 317 | */ |
| 318 | function connect_node_config_functions() { |
| 319 | require_once('connect_admin.php'); |
| 320 | drupal_set_title(t('Select functions')); |
| 321 | return drupal_get_form('connect_node_functions_form'); |
| 322 | } |
| 323 | |
| 324 | /** |
| 325 | * Menu callback: displays function configuration page |
| 326 | */ |
| 327 | function connect_node_config_settings() { |
| 328 | require_once('connect_admin.php'); |
| 329 | drupal_set_title(t('Function settings')); |
| 330 | return drupal_get_form('connect_node_settings_form'); |
| 331 | } |
| 332 | |
| 333 | /** |
| 334 | * Menu callback; displays the connect administration page. |
| 335 | */ |
| 336 | function connect_admin_overview_page() { |
| 337 | require_once('connect_admin.php'); |
| 338 | drupal_set_title(t('Connect settings')); |
| 339 | return drupal_get_form('connect_admin_form'); |
| 340 | } |
| 341 | |
| 342 | |
| 343 | /**** FORM: participate ****/ |
| 344 | |
| 345 | function connect_forms() { |
| 346 | $forms['connect_form_page'] = array( |
| 347 | 'callback' => 'connect_form', |
| 348 | 'callback arguments' => array('page'), |
| 349 | ); |
| 350 | $forms['connect_form_block'] = array( |
| 351 | 'callback' => 'connect_form', |
| 352 | 'callback arguments' => array('block'), |
| 353 | ); |
| 354 | return $forms; |
| 355 | } |
| 356 | |
| 357 | function connect_form_page_validate($form_id, &$form_values) { |
| 358 | connect_form_validate($form_id, &$form_values); |
| 359 | } |
| 360 | |
| 361 | function connect_form_page_submit($form_id, &$form_values) { |
| 362 | connect_form_submit($form_id, &$form_values); |
| 363 | } |
| 364 | |
| 365 | function connect_form_block_validate($form_id, &$form_values) { |
| 366 | connect_form_validate($form_id, &$form_values); |
| 367 | } |
| 368 | |
| 369 | function connect_form_block_submit($form_id, &$form_values) { |
| 370 | connect_form_submit($form_id, &$form_values); |
| 371 | } |
| 372 | |
| 373 | |
| 374 | /** added argument for block view **/ |
| 375 | |
| 376 | function connect_form($type = 'page') { |
| 377 | $parent =& _connect_parent_node(); |
| 378 | if (!$parent || !isset($parent->nid)) return; |
| 379 | |
| 380 | $participant_type = connect_node_options($parent->nid, 'participant_type'); |
| 381 | if (!$participant_type) return; |
| 382 | |
| 383 | // generate form |
| 384 | $participant->nid = NULL; |
| 385 | $participant->type = $participant_type; |
| 386 | $form = content_form($participant); |
| 387 | |
| 388 | // remove some fields (hidden, computed) |
| 389 | $cck_type = content_types($participant_type); |
| 390 | foreach ($cck_type["fields"] as $field) { |
| 391 | if ($field['display_settings']["teaser"]["format"] == "hidden" || $field['display_settings']["full"]["format"] == "hidden" || $field["type"] == "computed") { |
| 392 | unset($form[ $field['field_name'] ]); |
| 393 | } |
| 394 | } |
| 395 | |
| 396 | // allow action functions to alter the form |
| 397 | $temp = connect_call_hooks($parent, $form, 'form_alter', 'child'); |
| 398 | if ($temp) $form = $temp; |
| 399 | |
| 400 | // add call to action |
| 401 | $form['call_to_action'] = array( |
| 402 | '#prefix' => '<div id="connect-form-title">', |
| 403 | '#value' => t(connect_node_options($parent->nid, 'call_to_action')), |
| 404 | '#suffix' => '</div>', |
| 405 | '#weight' => -10, |
| 406 | ); |
| 407 | |
| 408 | // pass the participant type along so that a custom form can be generated in the template, if desired |
| 409 | $form['participant_type'] = array( |
| 410 | '#type' => 'hidden', |
| 411 | '#value' => $participant_type, |
| 412 | ); |
| 413 | |
| 414 | $form['submit'] = array( |
| 415 | '#type' => 'submit', |
| 416 | '#value' => t('Submit'), |
| 417 | '#weight' => 15, |
| 418 | ); |
| 419 | |
| 420 | return $form; |
| 421 | } |
| 422 | |
| 423 | |
| 424 | function connect_form_validate($form_id, &$form_values) { |
| 425 | // call connect functions validation |
| 426 | $parent =& _connect_parent_node(); |
| 427 | $results = connect_call_hooks($parent, $form_values, 'validate', 'child'); |
| 428 | if (!empty($results)) { |
| 429 | foreach ($results as $result) { |
| 430 | if ($result['value'] == FALSE) form_set_error('', $result['message']); |
| 431 | } |
| 432 | } |
| 433 | |
| 434 | // call cck fields validation |
| 435 | $return = array(); |
| 436 | $nothing = array(); |
| 437 | $cck_type = content_types($form_values['participant_type']); |
| 438 | $cck_fields = $cck_type['fields']; |
| 439 | $widget_types = _content_widget_types(); |
| 440 | foreach ($form_values as $form_field => $field_value) { |
| 441 | $module = $widget_types[$cck_fields[$form_field]['widget']['type']]['module']; |
| 442 | $function = $module .'_widget'; |
| 443 | if (function_exists($function)) { |
| 444 | $result = $function('validate', $nothing, $cck_fields[$form_field], $form_values[$form_field]); |
| 445 | } |
| 446 | } |
| 447 | } |
| 448 | |
| 449 | function connect_form_submit($form_id, &$form_values) { |
| 450 | |
| 451 | // build CCK node |
| 452 | foreach ($form_values as $key => $value) { |
| 453 | // simple form elements |
| 454 | if (isset($value[0])) { |
| 455 | $node_cck->$key = array($value[0]); |
| 456 | } |
| 457 | // single-value elements |
| 458 | elseif (isset($value['key'])) { |
| 459 | $node_cck->$key = array(array('value' => $value['key'])); |
| 460 | } |
| 461 | // multi-value elements |
| 462 | elseif (isset($value['keys'])) { |
| 463 | // select lists |
| 464 | if (is_array($value['keys'])) { |
| 465 | foreach ($value['keys'] as $item_key => $item_value) { |
| 466 | if ($item_key === $item_value) { |
| 467 | $temp_array[] = array('value' => $item_value); |
| 468 | } |
| 469 | } |
| 470 | $node_cck->$key = $temp_array; |
| 471 | } |
| 472 | // checkboxes |
| 473 | else { |
| 474 | $node_cck->$key = array(array('value' => $value['keys'])); |
| 475 | } |
| 476 | } |
| 477 | } |
| 478 | |
| 479 | $parent =& _connect_parent_node(); |
| 480 | $node_cck->nid = NULL; |
| 481 | $node_cck->type = connect_node_options($parent->nid, 'participant_type'); |
| 482 | $node_cck->title = 'participant in \''. $parent->title .'\''; |
| 483 | node_save($node_cck); |
| 484 | |
| 485 | // allow actions to change redirected page |
| 486 | $redirect = connect_call_hooks($parent, $node_cck, 'redirect', 'parent'); |
| 487 | if (!empty($redirect)) { |
| 488 | // just grab first one ... yech, but necessary |
| 489 | $keys = array_keys($redirect); |
| 490 | $target = $redirect[$keys[0]]; |
| 491 | } |
| 492 | else { |
| 493 | $target = 'node/'. $parent->nid; |
| 494 | } |
| 495 | |
| 496 | return $target; |
| 497 | } |
| 498 | |
| 499 | |
| 500 | /******************************** |
| 501 | * UTILITY FUNCTIONS |
| 502 | ********************************/ |
| 503 | |
| 504 | // is this node a participant node? |
| 505 | function connect_is_participant_node(&$node) { |
| 506 | $participant_nodes = variable_get('connect_participant_nodes', array()); |
| 507 | $return = in_array($node->type, $participant_nodes); |
| 508 | if ( $return ) { |
| 509 | $sql = 'SELECT pid FROM {connect_data} WHERE nid = %d'; |
| 510 | $parent = db_result(db_query($sql, $node->nid)); |
| 511 | if ($parent) $node->parent_id[0]['value'] = $parent; |
| 512 | } |
| 513 | return $return; |
| 514 | } |
| 515 | |
| 516 | |
| 517 | // is this node type a parent node type? |
| 518 | function connect_is_parent_node(&$node) { |
| 519 | $parent_nodes = variable_get('connect_parent_nodes', array()); |
| 520 | return (in_array($node->type, $parent_nodes)); |
| 521 | } |
| 522 | |
| 523 | |
| 524 | // test for valid canadian postal code |
| 525 | function connect_is_postalcode($code) { |
| 526 | $regexp = '/^[a-zA-Z][0-9][a-zA-Z]\s*[0-9][a-zA-Z][0-9]$/'; |
| 527 | return preg_match($regexp, $code); |
| 528 | } |
| 529 | |
| 530 | |
| 531 | // get db info for CCK field |
| 532 | function _connect_get_cck_db_info($type) { |
| 533 | $field = content_fields($type); |
| 534 | $db_info = content_database_info($field); |
| 535 | return $db_info; |
| 536 | } |
| 537 | |
| 538 | |
| 539 | // gets form value key(s) for cck field |
| 540 | function connect_get_field_keys($field) { |
| 541 | if ($field == 'title' || $field == 'body' ) { |
| 542 | return array('value'); |
| 543 | } |
| 544 | $test_db = _connect_get_cck_db_info($field); |
| 545 | if (isset($test_db['columns'])) { |
| 546 | return array_keys($test_db['columns']); |
| 547 | } |
| 548 | else { |
| 549 | return array(); |
| 550 | } |
| 551 | } |
| 552 | |
| 553 | |
| 554 | // counts the participants for a parent |
| 555 | function connect_participant_count(&$parent) { |
| 556 | $sql = "SELECT count(*) FROM {connect_data} WHERE pid = %d"; |
| 557 | $count = db_result(db_query($sql, $parent->nid)); |
| 558 | return $count; |
| 559 | } |
| 560 | |
| 561 | |
| 562 | // what type of participant does this parent use? |
| 563 | /* |
| 564 | function connect_get_participant_type($parent_nid) { |
| 565 | $type = connect_node_options( $parent_nid, 'participant_type' ); |
| 566 | return $type; |
| 567 | } |
| 568 | */ |
| 569 | |
| 570 | |
| 571 | // return an array of participant node types suitable for use in forms |
| 572 | function connect_participant_types_options() { |
| 573 | $array = array(); |
| 574 | $values = variable_get('connect_participant_nodes', array() ); |
| 575 | foreach ($values as $value) { |
| 576 | $array[$value] = $value; |
| 577 | } |
| 578 | return $array; |
| 579 | } |
| 580 | |
| 581 | |
| 582 | /** |
| 583 | * Returns an array of connect_action_* functions |
| 584 | * if an nid is passed in, returns the declared functions for that parent |
| 585 | * otherwise, it returns all the available actions |
| 586 | */ |
| 587 | function connect_get_actions( $parent_id = FALSE ) { |
| 588 | require_once('connect_actions.php'); |
| 589 | static $functions = array(); |
| 590 | $key = $parent_id ? (int) $parent_id : 'all'; |
| 591 | if (!isset($functions[$key])) { |
| 592 | if ($key == 'all') { |
| 593 | $allfns = get_defined_functions(); |
| 594 | $array = $allfns['user']; |
| 595 | foreach ($array as $fid => $fname) { |
| 596 | if (strpos($fname, 'connect_action_') !== 0) { |
| 597 | unset($array[$fid]); |
| 598 | } |
| 599 | } |
| 600 | asort($array); |
| 601 | } |
| 602 | else { |
| 603 | $array = connect_node_options($key, 'connect_actions'); |
| 604 | if (empty($array)) { |
| 605 | $array = array('connect_action_basic'); |
| 606 | } |
| 607 | } |
| 608 | $functions[$key] = $array; |
| 609 | } |
| 610 | return $functions[$key]; |
| 611 | } |
| 612 | |
| 613 | |
| 614 | /** |
| 615 | * returns an array of arrays mapping action variables onto CCK fields |
| 616 | * array( 'parent' => array( action => cck, ... ), 'child' => array( action => cck, ... ) ) |
| 617 | */ |
| 618 | function connect_get_map( $parent_id = 0 ) { |
| 619 | static $maps = array(); |
| 620 | if ( !isset($maps[$parent_id]) ) { |
| 621 | $maps[$parent_id] = connect_node_options( $parent_id, 'connect_map' ); |
| 622 | } |
| 623 | return $maps[$parent_id]; |
| 624 | } |
| 625 | |
| 626 | |
| 627 | /** |
| 628 | * determines all the required variables for a given campaign node |
| 629 | * @ return |
| 630 | * array( 'parent' => array(), 'child' => array() ) |
| 631 | */ |
| 632 | function connect_get_required_vars(&$node, &$child) { |
| 633 | if (empty($node)) return; |
| 634 | |
| 635 | $parent = array(); |
| 636 | $variables = array(); |
| 637 | $vars_done = array(); |
| 638 | |
| 639 | $function_vars = connect_call_hooks($node, $child, 'requires', 'parent'); |
| 640 | foreach ($function_vars as $fn_name => $fn_requires) { |
| 641 | foreach (array('parent', 'child') as $target ) { |
| 642 | if (isset($fn_requires[$target])) { |
| 643 | $$target = array_merge( $$target, $fn_requires[$target] ); |
| 644 | } |
| 645 | } |
| 646 | if (isset($fn_requires['variables'])) { |
| 647 | foreach ($fn_requires['variables'] as $name => $item) { |
| 648 | if (!isset($vars_done[$name])) { |
| 649 | $vars_done[$name] = TRUE; |
| 650 | $variables[$fn_name][$name] = $item; |
| 651 | } |
| 652 | } |
| 653 | } |
| 654 | } |
| 655 | return array( |
| 656 | 'parent' => $parent, |
| 657 | 'child' => $child, |
| 658 | 'variables' => $variables, |
| 659 | ); |
| 660 | } |
| 661 | |
| 662 | |
| 663 | /** |
| 664 | * determine the content fields in a node type and |
| 665 | * return as an array for use in admin forms |
| 666 | */ |
| 667 | function connect_get_node_fields( $typename ) { |
| 668 | $options = array( 0 => '', 'title' => 'Title' ); |
| 669 | // body? |
| 670 | $type = node_get_types('type', $typename); |
| 671 | if ($type->has_body) $options['body'] = 'Body'; |
| 672 | $cck_type = content_types($typename); |
| 673 | $fields = $cck_type["fields"]; |
| 674 | if (!empty($fields)) { |
| 675 | foreach ($fields as $field) { |
| 676 | $options[$field['field_name']] = $field['widget']['label']; |
| 677 | } |
| 678 | } |
| 679 | //asort($options); |
| 680 | return $options; |
| 681 | } |
| 682 | |
| 683 | |
| 684 | /** |
| 685 | * returns or sets the required value from $target = child | parent |
| 686 | if $value is set, this is a setter, otherwise, it's a getter |
| 687 | */ |
| 688 | function connect_value($variable = '', &$parent, &$child, $target = '', $value=FALSE) { |
| 689 | $return = ''; |
| 690 | if (!empty($variable) && isset($parent->nid) && ($target == 'child' || $target == 'parent')) { |
| 691 | $map = connect_get_map($parent->nid); |
| 692 | $var = $map[$variable]; |
| 693 | $path = _connect_get_field_path($$target, $var); |
| 694 | if (!$path) return FALSE; |
| 695 | |
| 696 | if (!$value) { |
| 697 | $exp = '$return = stripslashes($'. $target . $path .');'; |
| 698 | eval($exp); |
| 699 | } |
| 700 | else { |
| 701 | $exp = '$'. $target . $path .' = "'. addslashes(trim($value)) .'";'; |
| 702 | eval($exp); |
| 703 | } |
| 704 | } |
| 705 | return $return; |
| 706 | } |
| 707 | |
| 708 | |
| 709 | /** |
| 710 | * Returns the correct object or array path to retrieve a node field value |
| 711 | */ |
| 712 | function _connect_get_field_path( $target = FALSE, $var = FALSE) { |
| 713 | if (!$target || !$var) return FALSE; |
| 714 | |
| 715 | $path = ''; |
| 716 | // standard fields |
| 717 | if ($var == 'body' || $var == 'title') { |
| 718 | if (is_object($target)) { |
| 719 | $path = "->$var"; |
| 720 | } |
| 721 | else { |
| 722 | $path = "['$var'][0]['value']"; |
| 723 | } |
| 724 | |
| 725 | // cck fields |
| 726 | } |
| 727 | elseif (!empty($var)) { |
| 728 | $key = connect_get_field_keys($var); |
| 729 | if (is_object($target)) { |
| 730 | $var = "->$var"; |
| 731 | } |
| 732 | else { |
| 733 | $var = "['$var']"; |
| 734 | } |
| 735 | $path = $var .'[0][\''. $key[0] .'\']'; |
| 736 | } |
| 737 | return $path; |
| 738 | } |
| 739 | |
| 740 | |
| 741 | /** |
| 742 | * Returns the admin options for a parent node |
| 743 | * @param $nid: the node id of the parent |
| 744 | * @param $field: the name of the option to retrieve | NULL to retrieve all |
| 745 | * @param $value: if provided, sets $field to $value |
| 746 | * |
| 747 | */ |
| 748 | function connect_node_options($nid = 0, $field = NULL, $value = NULL) { |
| 749 | if (empty($nid)) return FALSE; |
| 750 | |
| 751 | // cache this info |
| 752 | static $options = array(); |
| 753 | if (!isset($options[$nid])) { |
| 754 | $temp = variable_get('connect_'. $nid .'_options', FALSE); |
| 755 | $options[$nid] = $temp ? unserialize($temp) : array(); |
| 756 | } |
| 757 | |
| 758 | // setter |
| 759 | if ($value !== NULL && $field !== NULL) { |
| 760 | $options[$nid][$field] = $value; |
| 761 | variable_set( 'connect_'. $nid .'_options', serialize($options[$nid]) ); |
| 762 | } |
| 763 | |
| 764 | // getter |
| 765 | if ($field !== NULL) { |
| 766 | $return = isset($options[$nid][$field]) ? $options[$nid][$field]: ''; |
| 767 | } |
| 768 | else { |
| 769 | $return = $options[$nid]; |
| 770 | } |
| 771 | |
| 772 | return $return; |
| 773 | } |
| 774 | |
| 775 | |
| 776 | /** |
| 777 | * Iterates through connect_action_* functions and calls them for the current operation and target |
| 778 | */ |
| 779 | function connect_call_hooks(&$parent, &$child, $op = '', $target = 'child') { |
| 780 | $debug_hooks = FALSE; // use this to turn on a display of what hooks are called in what order |
| 781 | $return = array(); |
| 782 | |
| 783 | // parent always required, child only if acting on child, operation always required |
| 784 | if (!$op || !$parent || ($target=='child' && !$child)) { |
| 785 | return $return; |
| 786 | } |
| 787 | |
| 788 | $parent = empty($parent) ? array() : $parent; |
| 789 | $child = empty($child) ? array() : $child; |
| 790 | $debug_parent = empty($parent) ? 'empty' : gettype($parent); |
| 791 | $debug_child = empty($child) ? 'empty' : gettype($child); |
| 792 | |
| 793 | if ( $op != 'describe' ) { |
| 794 | $actions = connect_get_actions($parent->nid); |
| 795 | } |
| 796 | else { |
| 797 | $actions = connect_get_actions(); |
| 798 | } |
| 799 | |
| 800 | if ($debug_hooks) drupal_set_message("$op ($target | $debug_parent | $debug_child)"); |
| 801 | |
| 802 | foreach ($actions as $action) { |
| 803 | if ($debug_hooks) drupal_set_message("$action 1"); |
| 804 | |
| 805 | // make sure we have the required vars before calling the function |
| 806 | // skip check for purely informational $op values |
| 807 | $skip_check = array('describe','requires', 'admin-validate'); |
| 808 | if (!in_array($op, $skip_check)) { |
| 809 | if (_connect_hook_check_requirements($parent, $child, $action, $target) !== TRUE){ |
| 810 | continue; |
| 811 | } |
| 812 | } |
| 813 | |
| 814 | if ($debug_hooks) drupal_set_message("$action 2"); |
| 815 | |
| 816 | // call the hook |
| 817 | $action_return = call_user_func($action, &$parent, &$child, $op, $target); |
| 818 | if ($action_return) { |
| 819 | $return[$action] = $action_return; |
| 820 | } |
| 821 | } |
| 822 | return $return; |
| 823 | } |
| 824 | |
| 825 | |
| 826 | /* |
| 827 | * tests to see that the required settings are present for a given action |
| 828 | */ |
| 829 | function _connect_hook_check_requirements(&$parent, &$child, $action, $target) { |
| 830 | static $requirements_ok = array(); |
| 831 | if (!isset($requirements_ok[$action])) { |
| 832 | $required = call_user_func($action, $parent, $child, 'requires', $target); |
| 833 | if (empty($required)) return TRUE; // no settings = no requirements, but let's not cache that |
| 834 | |
| 835 | // default to true, test for exceptions |
| 836 | $requirements_ok[$action] = ''; |
| 837 | |
| 838 | // parent + child fields, all required |
| 839 | $map = connect_get_map($parent->nid); |
| 840 | $map_keys = array_keys($map); |
| 841 | foreach (array('parent', 'child') as $type) { |
| 842 | if (isset($required[$type])) { |
| 843 | foreach ($required[$type] as $name => $desc) { |
| 844 | if (!in_array($name, $map_keys) || empty($map[$name])) { |
| 845 | $requirements_ok[$action] .= "'$desc' (required field in $type node)<br />\n"; |
| 846 | } |
| 847 | } |
| 848 | } |
| 849 | } |
| 850 | |
| 851 | // settings, might be optional |
| 852 | if (isset($required['variables'])) { |
| 853 | foreach ($required['variables'] as $name => $desc) { |
| 854 | $value = connect_node_options($parent->nid, $name); |
| 855 | if ($desc['#required'] == TRUE && empty($value)) { |
| 856 | $requirements_ok[$action] .= $desc['#title'] . "<br />\n"; |
| 857 | } |
| 858 | } |
| 859 | } |
| 860 | |
| 861 | // return TRUE or a message |
| 862 | if (empty($requirements_ok[$action])) $requirements_ok[$action] = TRUE; |
| 863 | } |
| 864 | return $requirements_ok[$action]; |
| 865 | } |
| 866 | |
| 867 | /* |
| 868 | * determine parent nid for child node |
| 869 | */ |
| 870 | function connect_get_parent(&$child) { |
| 871 | if (empty($child->nid)) return 0; |
| 872 | |
| 873 | static $parents = array(); |
| 874 | if (!isset( $parents[$child->nid])) { |
| 875 | $sql = "SELECT pid FROM {connect_data} WHERE nid = %d;"; |
| 876 | $parents[$child->nid]= db_result(db_query($sql, $child->nid)); |
| 877 | } |
| 878 | $child->connect_parent_id = $parents[$child->nid]; |
| 879 | return $parents[$child->nid]; |
| 880 | } |
| 881 | |
| 882 | |
| 883 | /* |
| 884 | * determines if connect_form captcha is required, and if so, if it's enabled |
| 885 | */ |
| 886 | function _connect_captcha_test($form_id = 'connect_form_page') { |
| 887 | $captcha_required = variable_get('connect_captcha_required', 'yes'); |
| 888 | if ($captcha_required == 'yes') { |
| 889 | $captcha = db_result(db_query("SELECT type FROM {captcha_points} WHERE form_id = '%s'", $form_id)); |
| 890 | if (!$captcha) { |
| 891 | if (!user_access('skip CAPTCHA')) { |
| 892 | watchdog('debug', 'connect form cannot be displayed: no CAPTCHA defined'); |
| 893 | return FALSE; |
| 894 | } |
| 895 | else { |
| 896 | drupal_set_message('Warning: CAPTCHA has not been configured for connect forms yet.'); |
| 897 | } |
| 898 | } |
| 899 | } |
| 900 | return TRUE; |
| 901 | } |
| 902 | |
| 903 | |
| 904 | /* |
| 905 | * use this wherever it's necessary to load the parent |
| 906 | * enforces a singleton pattern to solve reference problems in PHP4 |
| 907 | * |
| 908 | * made it an array to fix problems with displaying the form in a block |
| 909 | * - it is called twice when a block is visible and the current node is also a campaign |
| 910 | */ |
| 911 | function &_connect_parent_node($node = NULL) { |
| 912 | static $parent_node = array( 0 => NULL ); |
| 913 | if ($node == NULL && arg(0) == 'node') { |
| 914 | $nid = arg(1); |
| 915 | } |
| 916 | else { |
| 917 | $nid = isset($node->nid) ? $node->nid : 0; |
| 918 | } |
| 919 | if (!isset($parent_node[$nid])) { |
| 920 | $parent_node[$nid] = $node; |
| 921 | } |
| 922 | return $parent_node[$nid]; |
| 923 | } |
| 924 | |
| 925 | |
| 926 | /* |
| 927 | * returns cck fields in array for use in forms |
| 928 | */ |
| 929 | function _connect_get_child_fields($child_type = NULL) { |
| 930 | $cck_options = array(); |
| 931 | if ($child_type) { |
| 932 | $cck_info = _content_type_info(); |
| 933 | $cck_vars = $cck_info['content types'][$child_type]['fields']; |
| 934 | foreach ($cck_vars as $name => $field) { |
| 935 | if ($field['display_settings']['teaser']['format'] != 'hidden' && $field['display_settings']['full']['format'] != 'hidden') { |
| 936 | $cck_options[$name] = $field['widget']['label']; |
| 937 | } |
| 938 | } |
| 939 | $cck_options[''] = ''; |
| 940 | asort($cck_options); |
| 941 | } |
| 942 | return $cck_options; |
| 943 | } |
| 944 | |
| 945 | |
| 946 | /* |
| 947 | * accept a range of values that mean 'yes'/true |
| 948 | */ |
| 949 | function _connect_positive_value($in) { |
| 950 | $positive_strings = array('y', 'yes', 'true', 'ok', '1'); |
| 951 | if (in_array(strtolower($in), $positive_strings) || $in === TRUE) { |
| 952 | return TRUE; |
| 953 | } |
| 954 | else { |
| 955 | return FALSE; |
| 956 | } |
| 957 | //if (empty($in) || strtolower($in) == 'no' || strtolower($in) == 'n') { |
| 958 | // return FALSE; |
| 959 | //} |
| 960 | //else { |
| 961 | // return TRUE; |
| 962 | //} |
| 963 | } |
| 964 | |
| 965 | |
| 966 | /* |
| 967 | * menu callback: display the participants |
| 968 | */ |
| 969 | function _connect_action_display_participants($parent_nid = NULL) { |
| 970 | $output = FALSE; |
| 971 | if (is_numeric($parent_nid)) { |
| 972 | $parent = node_load($parent_nid); |
| 973 | drupal_set_title('"'. $parent->title .'" '. connect_node_options($parent->nid, 'participant_title')); |
| 974 | |
| 975 | // determine display field names |
| 976 | $header = array(); |
| 977 | $display_fields = variable_get('connect_'. $parent->nid .'_options', FALSE); |
| 978 | if (!$display_fields) return; |
| 979 | |
| 980 | $display_fields = unserialize($display_fields); |
| 981 | foreach ($display_fields as $key => $value) { |
| 982 | if (empty($value) || strpos($key, 'display_participants_fields_') === FALSE) { |
| 983 | unset($display_fields[$key]); |
| 984 | } |
| 985 | } |
| 986 | |
| 987 | // set header |
| 988 | $child_type = connect_node_options($parent->nid, 'participant_type'); |
| 989 | $cck_options = _connect_get_child_fields($child_type); |
| 990 | foreach ($display_fields as $key => $value) { |
| 991 | $header[] = $cck_options[$value]; |
| 992 | } |
| 993 | |
| 994 | // double opt-in? |
| 995 | $double_opt_in = (in_array('connect_action_double_optin', connect_get_actions($parent->nid))); |
| 996 | |
| 997 | // walk through participants |
| 998 | $pager = connect_node_options($parent->nid, 'display_participants_pager'); |
| 999 | if (!$pager) $pager = 25; |
| 1000 | $data = array(); |
| 1001 | $sql = "SELECT nid FROM {connect_data} WHERE pid = %d"; |
| 1002 | $result = pager_query($sql, $pager, 0, NULL, $parent->nid); |
| 1003 | while ($row = db_fetch_object($result)) { |
| 1004 | $this_item = array(); |
| 1005 | $child = node_load($row->nid); |
| 1006 | $display = connect_value('display_participants_displayme', $parent, $child, 'child'); |
| 1007 | |
| 1008 | // double opt-in? |
| 1009 | if ($double_opt_in) { |
| 1010 | $opt_in = connect_value('double_optin_token', $parent, $child, 'child'); |
| 1011 | if (!_connect_positive_value($opt_in)) continue; |
| 1012 | } |
| 1013 | |
| 1014 | if (_connect_positive_value($display)) { |
| 1015 | foreach ($display_fields as $key => $value) { |
| 1016 | $path = _connect_get_field_path($child, $value); |
| 1017 | $exp = '$this_item[] = $child'. $path .';'; |
| 1018 | eval($exp); |
| 1019 | } |
| 1020 | $data[] = $this_item; |
| 1021 | } |
| 1022 | } |
| 1023 | if (empty($data)) return theme_page('<p>no participants</p>'); |
| 1024 | } |
| 1025 | |
| 1026 | $link = theme('connect_action_display_participants_return', $parent->title, $parent->nid); |
| 1027 | $output = theme_table($header, $data) . $link; |
| 1028 | $output .= theme('pager', NULL, $pager); |
| 1029 | return $output; |
| 1030 | } |
| 1031 | |
| 1032 | /* themeable function */ |
| 1033 | function theme_connect_action_display_participants_return($parent_title, $parent_nid) { |
| 1034 | $link = l(t('Return to ') . $parent_title, 'node/'. $parent_nid); |
| 1035 | return "<div id='connect-returnto-link'>» ". $link .'</div>'; |
| 1036 | } |
| 1037 | |
| 1038 | |
| 1039 | |
| 1040 | /** report for admins **/ |
| 1041 | |
| 1042 | function connect_campaign_cache_report_form() { |
| 1043 | $parent =& _connect_parent_node(); |
| 1044 | $form = array(); |
| 1045 | |
| 1046 | // intro |
| 1047 | $form['report_intro'] = array( |
| 1048 | '#value' => 'This form allows you to create reports based on when participant data was cached by connect. If you keep persistent caches, this offers a rough measurement of the number of participants who are new to your system. Note that this assumes you are using a connect feature that uses caching (such as email target lookups) and that caching has been turned on.', |
| 1049 | ); |
| 1050 | |
| 1051 | // date |
| 1052 | $form['report_date'] = array( |
| 1053 | '#type' => 'date', |
| 1054 | '#title' => 'Date', |
| 1055 | ); |
| 1056 | |
| 1057 | // operator |
| 1058 | $form['report_operator'] = array( |
| 1059 | '#type' => 'radios', |
| 1060 | '#title' => 'Date range', |
| 1061 | '#options' => array('Before this date', 'On or after this date'), |
| 1062 | ); |
| 1063 | |
| 1064 | // type of report |
| 1065 | $form['report_type'] = array( |
| 1066 | '#type' => 'radios', |
| 1067 | '#title' => 'Type of report', |
| 1068 | '#options' => array('Numeric report', 'CSV'), |
| 1069 | ); |
| 1070 | |
| 1071 | // cache to test |
| 1072 | require_once(drupal_get_path('module', 'connect') .'/connect_lookup.php'); |
| 1073 | $cache_names = _connect_get_cache_names(); |
| 1074 | $form['report_cache'] = array( |
| 1075 | '#type' => 'radios', |
| 1076 | '#title' => 'Cache to query', |
| 1077 | '#description' => 'Connect maintains a number of caches, each of which can be set to expire independently. Please select the cache that will be the basis of your query.', |
| 1078 | '#options' => $cache_names, |
| 1079 | ); |
| 1080 | |
| 1081 | // relevant participant field |
| 1082 | $child_type = connect_node_options($parent->nid, 'participant_type'); |
| 1083 | $cck_options = _connect_get_child_fields($child_type); |
| 1084 | unset($cck_options['']); |
| 1085 | $form['report_field'] = array( |
| 1086 | '#type' => 'radios', |
| 1087 | '#title' => 'Cached participant field', |
| 1088 | '#description' => 'Please select the participant item that is cached as the "source" of the lookup (i.e., if the chosen cache stores the results of "Postal code to riding" lookups, select the participant field holding the postal code).', |
| 1089 | '#options' => $cck_options, |
| 1090 | ); |
| 1091 | |
| 1092 | $form['submit'] = array( |
| 1093 | '#type' => 'submit', |
| 1094 | '#value' => t('Submit'), |
| 1095 | ); |
| 1096 | |
| 1097 | return $form; |
| 1098 | } |
| 1099 | |
| 1100 | |
| 1101 | function connect_campaign_cache_report_form_submit($form_id, $form_values) { |
| 1102 | $output = ''; |
| 1103 | $parent =& _connect_parent_node(); |
| 1104 | |
| 1105 | // date |
| 1106 | $date = sprintf('%04d%02d%02d 00:00:00', $form_values['report_date']['year'], $form_values['report_date']['month'], $form_values['report_date']['day']); |
| 1107 | $date = strtotime($date); |
| 1108 | |
| 1109 | // participant field |
| 1110 | $cck_db = _connect_get_cck_db_info($form_values['report_field']); |
| 1111 | $cck_table = $cck_db['table']; |
| 1112 | $cck_column = $cck_db['columns']['value']['column']; |
| 1113 | |
| 1114 | // date operator |
| 1115 | $modal = ($form_values['report_operator'] == 0) ? '<': '>='; |
| 1116 | |
| 1117 | // CSV reports: set header |
| 1118 | if ($form_values['report_type'] > 0) { |
| 1119 | $child_type = connect_node_options($parent->nid, 'participant_type'); |
| 1120 | $child_fields = _connect_get_child_fields($child_type); |
| 1121 | unset($child_fields['']); |
| 1122 | $output = join(',', array_values($child_fields)) ."\n"; |
| 1123 | } |
| 1124 | |
| 1125 | // loop through all children |
| 1126 | $path = FALSE; |
| 1127 | $sql = "SELECT d.nid from {connect_data} d WHERE d.pid=%d;"; |
| 1128 | $result = db_query($sql, $parent->nid); |
| 1129 | while ($row = db_fetch_object($result)) { |
| 1130 | $child = node_load($row->nid); |
| 1131 | if (!$path) { |
| 1132 | $path = _connect_get_field_path($child, $form_values['report_field']); |
| 1133 | } |
| 1134 | eval('$value = strtoupper($child'. $path .');'); |
| 1135 | $value = preg_replace('/[^0-9A-Z]/', '', $value); |
| 1136 | |
| 1137 | // match value, date parameters |
| 1138 | $sql = "SELECT count(*) as total FROM {connect_cache} WHERE type='%s' AND source='%s' AND created $modal %d;"; |
| 1139 | $count = db_query($sql, $form_values['report_cache'], $value, $date); |
| 1140 | $number = db_result($count); |
| 1141 | if ($number > 0) { |
| 1142 | $cached_total++; |
| 1143 | if ($form_values['report_type'] > 0) { |
| 1144 | foreach ($child_fields as $field => $title) { |
| 1145 | $path = _connect_get_field_path($child, $field); |
| 1146 | eval('$item = $child'. $path .';'); |
| 1147 | $line[] = '"'. str_replace('"', '""', $item) .'"'; |
| 1148 | } |
| 1149 | if (!empty($line)) { |
| 1150 | $output .= join(',', $line) ."\n"; |
| 1151 | $line = ''; |
| 1152 | } |
| 1153 | } |
| 1154 | } |
| 1155 | } |
| 1156 | |
| 1157 | // display numeric report |
| 1158 | if ($form_values['report_type'] == 0) { |
| 1159 | $total = connect_participant_count($parent); |
| 1160 | drupal_set_message("total participants: $total"); |
| 1161 | drupal_set_message("cached since ". date('d M Y', $date) .": $cached_total"); |
| 1162 | return; |