'image_button', '#title' => t('My button'), '#return_value' => 'my_data', '#src' => 'my/image/path.jpg', ); */ /* TODO Remove $row argument from db_result() method The $row argument of db_result() was removed from the database abstraction layer in 6.x core, as it was a database dependent option. Developers need to use other handling to replace the needs of this method. */ /* TODO Remove db_num_rows() method The db_num_rows() method was removed from the database abstraction layer in 6.x core, as it was a database dependent method. Developers need to use other handling to replace the needs of this method. */ /* TODO Change 'Submit' to 'Save' on buttons It has been agreed on that the description 'Submit' for a button is not a good choice since it does not indicate what actually happens. While for example on node editing forms, 'Preview' and 'Delete' describe exactly what will happen when the user clicks on the button, 'Submit' only gives a vague idea. When labelling your buttons, make sure that it is clear what this button does when the user clicks on it. */ /* TODO Node previews and adding form fields to the node form. There is a subtle but important difference in the way node previews (and other such operations) are carried out when adding or editing a node. With the new Forms API, the node form is handled as a multi-step form. When the node form is previewed, all the form values are submitted, and the form is rebuilt with those form values put into $form['#node']. Thus, form elements that are added to the node form will lose any user input unless they set their '#default_value' elements using this embedded node object. */ /* TODO New user_mail_tokens() method may be useful. user.module now provides a user_mail_tokens() function to return an array of the tokens available for the email notification messages it sends when accounts are created, activated, blocked, etc. Contributed modules that wish to make use of the same tokens for their own needs are encouraged to use this function. */ /* TODO There is a new hook_watchdog in core. This means that contributed modules can implement hook_watchdog to log Drupal events to custom destinations. Two core modules are included, dblog.module (formerly known as watchdog.module), and syslog.module. Other modules in contrib include an emaillog.module, included in the logging_alerts module. See syslog or emaillog for an example on how to implement hook_watchdog. function example_watchdog($log = array()) { if ($log['severity'] == WATCHDOG_ALERT) { mysms_send($log['user']->uid, $log['type'], $log['message'], $log['variables'], $log['severity'], $log['referer'], $log['ip'], format_date($log['timestamp'])); } } */ /* TODO Implement the hook_theme registry. Combine all theme registry entries into one hook_theme function in each corresponding module file. function netforum_node_theme() { return array( 'netforum_node_object_lookup' => array( 'file' => 'netforum_node.module', 'arguments' => array( 'obj_type_name' => '', ), ), ); } */ /* TODO Form buttons can define custom #submit and #validate handlers. All forms can have #validate and #submit properties containing lists of validation and submission handlers to be executed when a user submits data. Previously, if a form featured multiple submission buttons to initiate different actions (updating a record versus deleting, for example), it was necessary to check the incoming form_values['op'] for the name of the clicked button, then execute different code based on its value. Now, it is possible to define #validate and #submit properties on each individual form button if desired. */ //IDEA: multiple node templates //IDEA: Popup reference/help for lists and queries in node bodies //TODO: Test node operations /** * @file * The netFORUM Dynamic Facade Nodes module * * Uses the netFORUM xWeb module to fetch Objects from netFORUM and * turn them into Nodes for viewing. The nodes work as a cross between * netFORUM e-marketing (correspondence) templates and the List content type in eWeb. * Most e-marketing templates should look the same as nodes, but child_form_html and * child_form_text commands are ignored. Much of the functionality can be reproduced using * the list functionality. * * Many functions and behavior of this module is patterend, copied, or adapted from the Node module * */ /** * Implementation of hook_menu() */ function netforum_node_menu() { $items = array(); //'create netFORUM node', 'edit own netFORUM nodes', 'edit netFORUM nodes', 'administer netFORUM node templates', 'administer netFORUM node special fields' global $user; /*$node_access_perm = (user_access('administer site configuration') || user_access('create netFORUM node') || user_access('edit netFORUM nodes') || (user_access('edit own netFORUM nodes') && ($user->uid == $node->uid)) ); $template_access_perm = (user_access('administer site configuration') || user_access('administer netFORUM node templates')); $special_field_access_perm = (user_access('administer site configuration') || user_access('administer netFORUM node special fields')); */ /* TODO Non menu code that was placed in hook_menu under the '!$may_cache' block so that it could be run during initialization, should now be moved to hook_init. Previously we called hook_init twice, once early in the bootstrap process, second just after the bootstrap has finished. The first instance is now called boot instead of init. In Drupal 6, there are now two hooks that can be used by modules to execute code at the beginning of a page request. hook_boot() replaces hook_boot() in Drupal 5 and runs on each page request, even for cached pages. hook_boot() now only runs for non-cached pages and thus can be used for code that was previously placed in hook_menu() with $may_cache = FALSE: Dynamic menu items under a '!$may_cache' block can often be simplified to remove references to arg(n) and use of '%' to check conditions. See http://drupal.org/node/103114. The title and description arguments should not have strings wrapped in t(), because translation of these happen in a later stage in the menu system. */ $items['admin/content/types/netforum-node-templates'] = array( 'title' => 'netFORUM Node Templates', 'page callback' => 'netforum_node_overview_types', 'access arguments' => $template_access_perm, ); $items['admin/content/types/netforum-node-templates/list'] = array( 'title' => 'List', 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10, ); $items['admin/content/types/netforum-node-templates/add'] = array( 'title' => 'Add netFORUM Node Template', 'page callback' => 'drupal_get_form', 'page arguments' => array('netforum_node_type_form'), 'type' => MENU_LOCAL_TASK, ); $items['admin/settings/netforum/special-fields'] = array( 'title' => 'netFORUM node special fields', 'page callback' => 'netforum_node_overview_special_fields', 'description' => 'Add and edit special fields to use in netFORUM nodes', 'access arguments' => $special_field_access_perm, ); $items['admin/settings/netforum/special-fields/list'] = array( 'title' => 'List', 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10, ); $items['admin/settings/netforum/special-fields/add'] = array( 'title' => 'Add netFORUM Node Special Field', 'page callback' => 'drupal_get_form', 'page arguments' => array('netforum_node_special_field_form'), 'type' => MENU_LOCAL_TASK, ); $items['admin/content/netforum-object-autocomplete'] = array( 'title' => 'Netforum object name autocomplete', 'page callback' => 'netforum_objects_autocomplete', 'type' => MENU_CALLBACK, 'access arguments' => $node_access_perm, ); $items['admin/content/netforum-object-fields-autocomplete'] = array( 'title' => 'Netforum object fields autocomplete', 'page callback' => 'netforum_node_object_fields_autocomplete', 'type' => MENU_CALLBACK, 'access arguments' => $node_access_perm, ); $items['admin/content/netforum-node-object-lookup' ] = array( 'title' => 'Netforum object key lookup', 'page callback' => 'netforum_node_object_lookup', 'page arguments' => array(true), 'type' => MENU_CALLBACK, 'access arguments' => $node_access_perm, ); //NEVER CACHE // netFORUM node template configuration. if (arg(0) == 'admin' && arg(1) == 'content' && arg(2) == 'types' && arg(3) == 'netforum-node-templates') { if (arg(4) != NULL && netforum_is_valid_guid(arg(4)) ) { $nft_obj_key = arg(4); $res = db_query("SELECT obj_name as obj_type_name, default_title, default_body, a.nft_obj_key as nft_obj_key FROM {netforum_node_types} a JOIN {netforum_object_cache} b ON a.nft_obj_key = b.obj_key WHERE b.obj_key = '%s'", $nft_obj_key); $node_template = db_fetch_object($res); if (!empty($node_template)) { $node_template->obj_type_name = check_plain($node_template->obj_type_name); $items['admin/content/types/netforum-node-templates/'. $nft_obj_key] = array( 'title' => '@object_name Object Template', array('@object_name' => $node_template->obj_type_name), 'page callback' => 'drupal_get_form', 'page arguments' => array('netforum_node_type_form', $node_template), 'type' => MENU_CALLBACK, ); $items['admin/content/types/netforum-node-templates/'. $nft_obj_key .'/delete'] = array( 'title' => 'Delete', 'page callback' => 'drupal_get_form', 'page arguments' => array('netforum_node_type_delete_confirm', $node_template), 'type' => MENU_CALLBACK, ); } } if (arg(0) == 'admin' && arg(1) == 'settings' && arg(2) == 'netforum' && arg(3) == 'special-fields' && arg(4) != NULL) { $field_code = arg(4); $field_code = db_result(db_query("SELECT field_code FROM {netforum_node_special_fields} WHERE field_code = '%s'", $field_code)); if (!empty($field_code )) { $field_code = check_plain($field_code); $items['admin/settings/netforum/special-fields/'. $field_code ] = array( 'title' => $field_code, 'page callback' => 'drupal_get_form', 'page arguments' => array('netforum_node_special_field_form', $field_code), 'type' => MENU_CALLBACK, ); $items['admin/settings/netforum/special-fields/'. $field_code .'/delete'] = array( 'title' => 'Delete', 'page callback' => 'drupal_get_form', 'page arguments' => array('netforum_node_special_field_delete_confirm', $field_code), 'type' => MENU_CALLBACK, ); } } if (arg(0) == 'node' && is_numeric(arg(1))) { $is_nf_node = db_result(db_query("SELECT count(nfn_key) FROM {netforum_node} WHERE nid=%d", arg(1))); if ($is_nf_node > 0) { $items["node/". '%' ."/refreshdata"] = array( 'title' => 'Refresh netFORUM data', 'access arguments' => $node_access_perm, 'type' => MENU_LOCAL_TASK, 'weight' => 2, 'page callback' => 'netforum_node_refresh_tab', 'page arguments' => arg(1), ); } } if (arg(0) == 'node' && netforum_is_valid_guid(arg(1)) && netforum_is_valid_guid(arg(2)) ) { $items["node/". arg(1) ."/". '%'] = array( 'title' => 'Create netFORUM Node', 'access arguments' => array('access content'), 'type' => MENU_CALLBACK, 'page callback' => 'netforum_node_create_from_template', 'page arguments' => array(arg(1), arg(2)), ); } } return $items; } /** * menu local task callback to refresh a node's data * * @param $nid * integer, the node ide * @return * redirects to the node view */ function netforum_node_refresh_tab($nid) { $node = node_load($nid); netforum_node_refresh_fields($node, true); if (isset($node->nfn_data)) { //if the data is set, that means that we successfully got it from netforum netforum_node_save_fields($node); drupal_set_message("Fetched latest data from netFORUM"); } else{ drupal_set_message("netFORUM currently unavailable", 'error'); } drupal_goto("node/". $node->nid); } /** * menu callback to search for node fields * * @param $obj * A string containing either the object name or key * @param $search * A string to match against the list of object fields * @return * a JSON object with the results, exits after completion */ function netforum_node_object_fields_autocomplete($obj='', $search="") { if ($obj == '' || $search == '') { print drupal_to_js(array('' => 'Try setting the object name before looking for object fields')); exit; } $obj = netforum_is_valid_guid($obj) ? netforum_object_name($obj) : $obj; $obj_fields = array_merge(netforum_object_fields($obj), netforum_node_special_fields(true)); if ($search != "") { $obj_matches = array(); foreach ($obj_fields as $k => $v) { if (stristr($v, $search) || stristr($k, $search)) { $obj_matches["{". $k ."}"] = $v; } } } else { //this just makes a copy of the obj_fields array with added curly brackets on either side of the key values $obj_matches = array_combine(array_map(create_function('$v', 'return "{". $v ."}";'), array_keys($obj_fields)), array_values($obj_fields)); } print drupal_to_js($obj_matches); exit; } /** * Implementation of node_form() */ function netforum_node_form($node) { $type = node_get_types('type', $node); //Get a working object name and key if there isn't one set for the object if (isset($node->obj_type_name)) { $object_name = $node->obj_type_name; $node->nfn_obj_key = netforum_object_key($node->obj_type_name); } else if (isset($_POST['obj_type_name']) ) { $object_name = $_POST['obj_type_name']; $node->nfn_obj_key = netforum_object_key($object_name); } $object_name = trim($object_name) == '' ? "Object" : $object_name; $form['obj_type_name'] = array( '#type' => 'textfield', '#title' => t('Object Name'), '#size' => 50, '#autocomplete_path' => 'admin/content/netforum-object-autocomplete', '#weight' => -7, '#required' => true, '#default_value' => $node->obj_type_name, '#description' => t("This is the kind of information to pull from netFORUM, usually seen in profile forms in iWeb. Type the object name or the object prefix to search."), ); $form['nfn_key'] = array( '#type' => 'textfield', '#title' => t('@objectname Key', array('@objectname' => $object_name) ), '#required' => TRUE, '#default_value' => $node->nfn_key, '#weight' => -6, '#description' => t("This is the unique identifier for information in netFORUM. If you don't know the key, click the help find object key link below to look for one."), ); $form['object_help'] = array( '#type' => 'fieldset', '#title' => t('Help find @objectname key', array('@objectname' => $object_name)), '#collapsible' => TRUE, '#collapsed' => TRUE, '#weight' => -5, '#attributes' => array("id" => 'object_help'), '#suffix' => "" ); $form['object_help']['helpframe'] = array( '#prefix' => "
", '#value' => theme_netforum_node_object_lookup($object_name), '#suffix' => "
", ); $form['unparsed_title'] = array( '#type' => 'textfield', '#title' => check_plain($type->title_label), '#default_value' => $node->unparsed_title, '#weight' => -2, '#description' => t('Leave empty to use the default title for this @objectname. Anything in curly brackets will be replaced with the information from netFORUM.', array('@objectname' => $object_name)), ); $form['obj_field_help'] = array( '#type' => 'textfield', '#title' => t('Find object fields'), '#size' => 50, '#autocomplete_path' => 'admin/content/netforum-object-fields-autocomplete/', '#weight' => -1, '#default_value' => '', '#description' => t("Type the first few letters of what you're looking for to find the code. Use the code in the title or body to get the information from netFORUM"), ); if ($node->nfn_obj_key) { $form['obj_field_help']['#autocomplete_path'] .= $node->nfn_obj_key; } $form['body_filter']['unparsed_body'] = array( '#type' => 'textarea', '#title' => check_plain($type->body_label), '#default_value' => $node->unparsed_body, '#rows' => 5, '#description' => t('Leave empty to use the default body for this @objectname. Anything in curly brackets will be replaced with the information from netFORUM.', array('@objectname' => $object_name)), ); $form['body_filter']['filter'] = filter_form($node->format); return $form; } /** * Implementation of node_validate() */ function netforum_node_validate(&$node, $form) { if ( trim($node->unparsed_title) == '' || trim($node->unparsed_body) == '') { $nft_obj_key = netforum_object_key($node->obj_type_name); $template_exists = false; if ( netforum_is_valid_guid($nft_obj_key)) { $count = db_result(db_query("SELECT count(default_title) FROM {netforum_node_types} WHERE nft_obj_key = '%s' ", $nft_obj_key)); $template_exists = ($count > 0); } } if ( trim($node->unparsed_title) == '' && $template_exists == false) { form_set_error('unparsed_title', t('A title is required, and no default title is set for %type objects, perhaps you need to !add_link?', array( '%type' => $node->obj_type_name, '!add_link' => l(t('add a netforum node template'), 'admin/content/types/netforum-node-templates/add'), ) ) ); } if ( trim($node->unparsed_body) == '' && $template_exists == false) { form_set_error('unparsed_body', t('A body is required, and no default body is set for %type objects, perhaps you need to !add_link?', array( '%type' => $node->obj_type_name, '!add_link' => l(t('add a netforum node template'), 'admin/content/types/netforum-node-templates/add'), ) ) ); } if (isset($node->obj_type_name) && isset($node->nfn_key) ) { $valid_key = true; $valid_object = true; if ( netforum_is_valid_guid($node->nfn_key) == false ) { form_set_error('nfn_key', t('The object key an invalid format')); $valid_key = false; } if ( netforum_is_valid_guid(netforum_object_key($node->obj_type_name)) == false ) { form_set_error('obj_type_name', t('Could not find any netFORUM objects with that name, perhaps you need to !link ?', array('!link' => l(t('refresh netforum object names'), 'admin/settings/netforum/objectnamesrefresh') ) )); $valid_object = false; } if ($valid_key && $valid_object) { $obj_name = $node->obj_type_name; $nfn_key = $node->nfn_key; $results = netforum_xweb_request('GetFacadeObject', array('szObjectName' => $obj_name, 'szObjectKey' => $nfn_key) ); if (is_null($results) || (isset($results->attributes()->recordReturn) && $results->attributes()->recordReturn == 0) ) { form_set_error('nfn_key', t('No %obj_name object found with that key', array('%obj_name' => $node->obj_type_name) )); } } } } /** * The help DIV included when editing a netFORUM node to help lookup an object type and key * Included here as part of the form * * @param $obj_type_name * A string containing the name of the object * @return * A string containing HTML */ function theme_netforum_node_object_lookup($obj_type_name = '') { drupal_add_css(drupal_get_path('module', 'netforum_node') .'/netforum_node.css'); drupal_add_js(drupal_get_path('module', 'netforum_node') .'/jquery.netforum_node.js'); $obj_type_name = $obj_type_name == "Object" ? "" : $obj_type_name; return netforum_node_object_lookup(false, $obj_type_name); } /** * The contents of the help DIV included when editing a netFORUM node to help lookup an object type and key * * This function is called both by the theme and as a menu callback by ajax requests, possibly searching on * fields for the current object or on a URL from netFORUM * * @param $obj_type_name * A string containing the name of the object * @param $exit_after * A boolean, if true the function will output HTML directly and exit. Default value is false * @return * A string containing html if the $exit_after parameter is empty or set to false */ function netforum_node_object_lookup($exit_after = false, $obj_type_name='') { $output = ''; $max_rows_return = 40; $form = array(); $where_args = array(); $table_header = array(''); //We want one blank column in here for the Set Key link $table_rows = array(); $search_results = ""; $search_on = array(); $form['url_lookup'] = array( '#type' => 'textfield', '#title' => t('netFORUM URL'), '#default_value' => '', '#maxlength' => 500, '#description' => 'Copy and paste a URL to search for the key and object', ); if ($obj_type_name != '') { //If any modules invoke hook_netforum_node_obj_search_on or hook_netforum_node_obj_order_by use those, if not set some defaults $search_on = module_invoke_all('netforum_node_obj_search_on', $obj_type_name); $order_by = module_invoke_all('netforum_node_obj_order_by', $obj_type_name); if (count($search_on) == 0 || count($order_by) == 0) { $default_columns = array(); $res = db_query("SELECT obj_defaultcolumns, obj_defaultorderby FROM {netforum_object_cache} WHERE obj_name = '%s'", $obj_type_name); $obj_defaults = db_fetch_array($res); $default_orderby = $obj_defaults['obj_defaultorderby']; //If the default columns are * or empty, try setting it to the default order by if ( ! isset($obj_defaults['obj_defaultcolumns']) || $obj_defaults['obj_defaultcolumns'] == "*" || trim($obj_defaults['obj_defaultcolumns']) == '' ) { $obj_defaults['obj_defaultcolumns'] = $obj_defaults['obj_defaultorderby']; } if (isset($obj_defaults['obj_defaultcolumns']) && $obj_defaults['obj_defaultcolumns'] != "*" && trim($obj_defaults['obj_defaultcolumns']) != '' ) { $columns = explode(",", $obj_defaults['obj_defaultcolumns']); foreach ($columns as $col) { $col = trim($col); $default_columns[$col] = ucwords(str_replace("_", " ", $col)); } } $search_on = count($search_on) == 0 ? $default_columns : $search_on; $order_by = count($order_by) == 0 ? $obj_defaults['obj_defaultorderby'] : $order_by; } $order_by = is_array($order_by) ? implode(",", $order_by) : $order_by; if (is_array($search_on)) { foreach ($search_on as $col => $friendly_col) { $col = trim($col); $table_header[] = $friendly_col; //If they submitted the form, prepare to get some data from netFORUM if (isset($_GET[$col]) && $_GET[$col] != '') { $where_args[] = $col ." LIKE '". $_GET[$col] ."%'"; } //build the form either way $form[$col] = array( '#type' => 'textfield', '#size' => 20, '#title' => $friendly_col, '#default_value' => $_GET[$col], ); } } } $form['key_submit'] = array( '#type' => 'button', '#value' => 'Search', ); $form['key_obj_type_name'] = array( '#type' => 'hidden', '#value' => $obj_type_name, '#disabled' => true, ); $form['search_on'] = array( '#type' => 'hidden', '#value' => drupal_to_js(array_keys($search_on)), '#disabled' => true, ); //drupal_prepare_form('key-help', $form); if ($obj_type_name != '' && count($where_args) > 0) { $arguments = array( 'szObjectName' => $obj_type_name ." @TOP ". $max_rows_return, 'szColumnList' => implode(", ", array_keys($search_on)), 'szWhereClause' => implode(" AND ", $where_args), 'szOrderBy' => $order_by, ); $response = netforum_xweb_request('GetQuery', $arguments); if ($response && $response->attributes()->recordReturn > 0) { $response_objects = $obj_type_name ."Object"; $obj_count = 0; foreach ($response->$response_objects as $obj) { $fields = get_object_vars($obj); $obj_key = (string)array_shift($fields); $table_rows[] = array_merge( array( l(t("Set Key "), '', array('attributes' => array('id' => $obj_key, 'class' => 'key-help'), 'query' => null)) ), $fields); $obj_count++; if ($obj_count > $max_rows_return) { break; } } } if (count($table_rows) == 0) { $table_rows[] = array(array('data' => t('No results found.'), 'colspan' => count($search_on), 'class' => 'message')); } } else if ($_GET['url_lookup'] && true) { parse_str($_GET['url_lookup'], $nf_url); //gets the GET arguments into the $nf_url array $obj_name = ''; $table_header = array('', 'Object Key', 'Object Name'); if (netforum_is_valid_guid($nf_url['FormKey'])) { $arguments = array( 'szObjectName' => 'md_dynamic_form', 'szColumnList' => 'obj_name', 'szWhereClause' => "dyn_key = '". $nf_url['FormKey'] ."' ", 'szOrderBy' => '', ); $response = netforum_xweb_request('GetQuery', $arguments); if ($response && $response->attributes()->recordReturn > 0) { $obj_name = $response->md_dynamic_formObject->obj_name; } } if (netforum_is_valid_guid($nf_url['key'])) { if ($obj_name == '' ) { $obj_name = "Object name not found"; } $table_rows[] = array( l("Set Object name and Key", '', array('attributes' => array('class' => 'obj-help'), 'query' => null)) , $nf_url['key'], $obj_name, ); //Not the most elegant way of passing data back and forth, but since we're outputting HTML anyway I'll take it. $output .= ""; $output .= ""; } else { $table_rows[] = array(array('data' => t('No results found.'), 'colspan' => 2, 'class' => 'message')); } } if (count($table_rows) > 0) { $search_results = theme('table', $table_header, $table_rows); } if (count($table_rows) >= $max_rows_return) { $output .= "

". t("More than @numrows matches found, try being more specific.", array('@numrows' => $max_rows_return)) ."

"; } $output .= $search_results; $output .= drupal_render($form); if ($exit_after) { print $output; //If we got no response, it might have set an error using drupal_message. Since we could be ajaxin' it up here, //clear out those errors. drupal_get_messages(); exit; } else { return $output; } } /** * Implementation of node_insert() */ function netforum_node_insert($node) { db_query("INSERT INTO {netforum_node} (nid, vid, nfn_obj_key, nfn_key, unparsed_title, unparsed_body) VALUES(%d, %d, '%s', '%s', '%s', '%s')", $node->nid, $node->vid, netforum_object_key($node->obj_type_name), $node->nfn_key, $node->unparsed_title, $node->unparsed_body); } /** * Implementation of node_update() */ function netforum_node_update($node) { if ($node->revision) { netforum_node_insert($node); } else{ db_query("UPDATE {netforum_node} set nfn_obj_key = '%s', nfn_key = '%s', unparsed_title = '%s', unparsed_body = '%s' WHERE nid='%d' AND vid='%d' ", netforum_object_key($node->obj_type_name), $node->nfn_key, $node->unparsed_title, $node->unparsed_body, $node->nid, $node->vid); } } /** * Implementation of node_submit() */ function netforum_node_submit($form, &$form_state) { netforum_node_set_to_template($node); //This will only override our input if we pass TRUE as the second parameter, so we're safe netforum_node_refresh_fields($node); } /** * Implementation of node_load() */ function netforum_node_load($node) { $op = isset($_POST['op']) ? $_POST['op'] : ''; if ($op == 'Preview') { //this data is going to get used to generate a preview, so we ought ot set it... $node_part = new stdClass(); $node_part->nfn_key = $_POST['nfn_key']; $node_part->obj_type_name = trim($_POST['obj_type_name']); $node_part->nfn_obj_key = netforum_object_key($node_part->obj_type_name); $node_part->unparsed_body = $_POST['unparsed_body']; $node_part->unparsed_title = $_POST['unparsed_title']; netforum_node_set_to_template($node_part); } else{ $res = db_query("SELECT nfn_obj_key, a.nfn_key as nfn_key, obj_name as obj_type_name, unparsed_title, unparsed_body FROM {netforum_node} a JOIN {netforum_object_cache} b ON a.nfn_obj_key = b.obj_key WHERE nid=%d AND vid=%d", $node->nid, $node->vid); $node_part = db_fetch_object($res); } return $node_part; } /** * Implementation of node_delete() */ function netforum_node_delete($node) { db_query("DELETE FROM {netforum_node} WHERE nid=%d", $node->nid); } /** * Implementation of node_view() */ function netforum_node_view($node, $teaser = FALSE, $page = FALSE) { $op = isset($_POST['op']) ? $_POST['op'] : ''; if ($op == 'Preview') { netforum_node_set_to_template($node); } $obj_key = netforum_is_valid_guid($node->nfn_obj_key) ? $node->nfn_obj_key : netforum_object_key($node->obj_type_name); if (! isset($node->nfn_data) || empty($node->nfn_data)) { $page_title = drupal_get_title(); $old_node_title = $node->title; netforum_node_refresh_fields($node); netforum_node_save_fields($node); //Update the title of the page if it changed if ($page_title == $old_node_title && $node->title != $old_node_title) { drupal_set_title($node->title); } } if (!$teaser) { //Mostly this is a sanity check, but if they are editing or previewing a node it will be handy if ($node->body == $node->unparsed_body || empty($node->body)) { $node->body = netforum_node_process_text($node->unparsed_body, $node->nfn_data, $obj_key); } if ($node->title == $node->unparsed_title || empty($node->title) ) { $node->title = netforum_node_process_text($node->unparsed_title, $node->nfn_data); } $node = node_prepare($node, $teaser); } if ($teaser) { $node->teaser = netforum_node_process_text($node->teaser, $node->nfn_data, $obj_key); $node = node_prepare($node, $teaser); } return $node; } /** * Create a new node programatically from a template * * The node's body and title are set to the default template and inserted in the databse * * @param $nfn_obj_key * A string representing the netFORUM key of the node object type * @param $nfn_key * A string representing the unique key of the netFORUM object */ function netforum_node_create_from_template($nfn_obj_key, $nfn_key) { $node = new stdClass(); $node->type = 'netforum_node'; $node->log = t('Created on demand by a {BeginLink}{EndLink} tag'); $node->nfn_key = $nfn_key; $node->nfn_obj_key = $nfn_obj_key; $node->obj_type_name = netforum_object_name($nfn_obj_key); $node_options = variable_get('node_options_'. $node->type, array('status')); foreach (array('status', 'promote', 'sticky', 'revision') as $key) { $node->$key = in_array($key, $node_options); } if (false) { drupal_goto($_GET['destination']); } netforum_node_set_to_template($node); if (isset($node->unparsed_body) == false || isset($node->unparsed_title) == false) { drupal_not_found(); return; } netforum_node_refresh_fields($node); if (isset($node->nfn_data) == false) { drupal_not_found(); return; } node_save($node); drupal_goto('node/'. $node->nid); } /** * Set the nodes body and title to the netFORUM node template * * @param &$node * A netFORUM node object with either an object key or name set * @param $overwrite_fields * Boolean, defaults to false. If set to true the body and title will be overwritten with * the template */ function netforum_node_set_to_template(&$node, $overwrite_fields = false) { if ($overwrite_fields != true && trim($node->unparsed_body) != '' && trim($node->unparsed_title) != '') { //no overwrite needed because the fields have a value and we weren't instructed to overwrite them return; } $obj_key = netforum_is_valid_guid($node->nfn_obj_key) ? $node->nfn_obj_key : netforum_object_key($node->obj_type_name); if ( netforum_is_valid_guid($obj_key)) { $template = db_fetch_array(db_query("SELECT default_body, default_title FROM {netforum_node_types} WHERE nft_obj_key = '%s' ", $obj_key)); if ($template) { if ($overwrite_fields || trim($node->unparsed_body) == '') { $node->unparsed_body = $template['default_body']; } if ($overwrite_fields || trim($node->unparsed_title) == '') { $node->unparsed_title = $template['default_title']; } } } } /** * Fetch new data for the netFORUM node and update the body and title * * $node->nfn_skip_save is set by this function. If the data came from the cache * or doesn't change the body or title nfn_skip_save is set to true * * @param &$node * A netFORUM node object * @param $ignore_cache * Boolean, defaults to false. If set to true the latest data will be fetched from * netFORUM and not from the local DB cache */ function netforum_node_refresh_fields(&$node, $ignore_cache = false) { $save_not_needed = false; netforum_node_refresh_data($node, $ignore_cache); if (isset($node->nfn_data)) { $obj_key = netforum_is_valid_guid($node->nfn_obj_key) ? $node->nfn_obj_key : netforum_object_key($node->obj_type_name); $new_body = netforum_node_process_text($node->unparsed_body, $node->nfn_data, $obj_key); $new_title = netforum_node_process_text($node->unparsed_title, $node->nfn_data); $save_not_needed = (strcmp($new_body, $node->body) == 0 && strcmp($new_title, $node->title) == 0); $node->body = $new_body; $node->title = $new_title; $node->teaser = isset($node->body) ? node_teaser($node->body, isset($node->format) ? $node->format : NULL) : ''; } else{ $save_not_needed = true; } $node->nfn_skip_save = $save_not_needed; } /** * Get the latest data for a node from netFORUM * * @param &node * The netFORUM Dynamic Facade Node with an object key and object type key set * @return * Nothing, the node is passed by reference */ function netforum_node_refresh_data(&$node, $ignore_cache = false) { if ( (isset($node->nfn_data) && $ignore_cache == false) ) { return; } $obj_name = $node->obj_type_name; if (!isset($obj_name) && netforum_is_valid_guid($node->nfn_obj_key) ) { $obj_name = netforum_object_name($node->nfn_obj_key); } $nfn_key = $node->nfn_key; if ( isset($obj_name) == false || netforum_is_valid_guid($nfn_key == false)) { return; } if ($ignore_cache) { $cache_time = '1 second'; } else{ $cache_time = variable_get('netforum_cache_default', '1 hour'); // only look in the default cache, if we're refreshing the information we don't want really old (possibly bad) data } $results = netforum_xweb_request('GetFacadeObject', array('szObjectName' => $obj_name, 'szObjectKey' => $nfn_key), $cache_time); if (is_null($results)) { watchdog('netforum', 'No data for Dynamic Facade Node type !obj_name found for key !key', array('!obj_name' => $obj_name, '!key' => $nfn_key), WATCHDOG_ERROR, l(t('node'), 'node/'. $node->nid)); } else { $node->nfn_data_from_cache = netforum_response_from_cache(); $node->nfn_data = $results; } } /** * Implementation of hook_perm() */ function netforum_node_perm() { return array('create netFORUM node', 'edit own netFORUM nodes', 'edit netFORUM nodes', 'administer netFORUM node templates', 'administer netFORUM node special fields'); } /** * Save the node body and title to the database unless $node->nfn_skip_save is true * * This is used to make sure that the content in the database is always the most current, which is important * in the event that the netFORUM server is unreachable * * @param $node * A netFORUM node object */ function netforum_node_save_fields($node) { if ($node->nfn_skip_save == true || !isset($node->nid) || !isset($node->vid) ) { return; } db_query("UPDATE {node_revisions} SET title='%s', body='%s', teaser='%s' WHERE nid='%d' AND vid='%d'", $node->title, $node->body, $node->teaser, $node->nid, $node->vid); db_query("UPDATE {node} SET title='%s' WHERE nid='%d' AND vid='%d'", $node->title, $node->nid, $node->vid); } /** * Implementation of hook_access() */ function netforum_node_access($op, $node, $account) { if ($op == 'create') { return (user_access('create netFORUM node', $account)); } if ($op == 'update' || $op == 'delete') { return ((user_access('edit own netFORUM nodes', $account) && ($account->uid == $node->uid)) || user_access('edit netFORUM nodes', $account)); } } /** * Implementation of hook_node_operations() */ function netforum_node_node_operations() { $operations = array( 'netforum_node_refresh_data' => array( 'label' => t('Refresh data from netFORUM'), 'callback' => 'netforum_node_operations_refresh', ), 'netforum_node_template' => array( 'label' => t('Reset to netFORUM object template'), 'callback' => 'neforum_node_operations_set_template', ), ); return $operations; } /** * Node operation to reset the nodes to the default template * */ function neforum_node_operations_set_template(&$form_state, $nodes) { $templates = array(); $res = db_query("SELECT nft_obj_key, default_title, default_body FROM {netforum_node_types} "); while ($node_template = db_fetch_array($res)) { $templates[$node_template['nft_obj_key']] = $node_template; } foreach ($nodes as $nid) { $node = node_load($nid); if ($node->type == 'netforum_node') { if ( array_key_exists($node->nfn_obj_key, $templates)) { $node->revision = 1; $node->unparsed_title = $templates[$node->nfn_obj_key]['default_title']; $node->unparsed_body = $templates[$node->nfn_obj_key]['default_body']; $node->log = t("Node reset to template default during bulk operation"); netforum_node_refresh_fields($node); //work around for the weird pathauto bug that expects taxonomy to be an array and not an object, like it was coming from a form. // For the record, grr. if ( module_exists('pathauto') ){ $new_tax = array(); if ($node->taxonomy) { $vocabularies = taxonomy_get_vocabularies(); foreach ($node->taxonomy as $tid => $value) { if (is_object($value)) { if ($vocabularies[$value->vid]->tags) { if (array_key_exists('tags', $new_tax) == false) { $new_tax['tags'] = array(); $new_tax['tags'][$value->vid] = array(); } $new_tax['tags'][$value->vid][] = $value->name; } else { if (array_key_exists($value->vid, $new_tax) == false) { $new_tax[$value->vid] = array(); } $new_tax[$value->vid][$value->tid] = $value->tid; $node->taxonomy[$tid] = (array)$value; } } } if (count($new_tax) > 0) {// at the end, we should have an array that looks like it came from a form. There is a much simpler //patch for this, but I'd rather implement an ugly workaround than make people patch other modules' code if (array_key_exists('tags', $new_tax)) { foreach ($new_tax['tags'] as $vid => $tags) { $new_tax['tags'][$vid] = implode(', ', $tags); } } $node->taxonomy = $new_tax; } } } node_save($node); } } } } /** * Node operation to fetch recent data from netFORUM * */ function netforum_node_operations_refresh(&$form_state, $nodes) { foreach ($nodes as $nid) { $node = db_fetch_object(db_query("SELECT obj_name as obj_type_name, nfn_key, unparsed_body, unparsed_title, a.nid, n.vid FROM {netforum_node} a JOIN {netforum_object_cache} b ON a.nfn_obj_key = b.obj_key JOIN {node} n ON n.nid = a.nid AND n.vid = a.vid WHERE a.nid= %d", $nid)); if ($node) { netforum_node_refresh_fields($node, true); netforum_node_save_fields($node); } } } /** * Implementation of hook_node_info() */ function netforum_node_node_info() { return array( 'netforum_node' => array( 'name' => t('netFORUM Dynamic Facade Node'), 'module' => 'netforum_node', 'description' => t('Convert a netFORUM Object to a Drupal node'), 'has_title' => TRUE, 'title_label' => t('Title'), 'has_body' => TRUE, 'body_label' => t('Body'), 'locked' => TRUE, ), ); } /** * Interpret special directives inside the the text and produce a final output. * * Dispatch the text for processing by various functions depending on the directives in the text. * This should be safe to use on most text to produce an output suitable for viewing or processing * by a text filter * * @param $text * A string with the plaint text to be replaced * @param $object_data * A hashed array or object representing an object with keys set to column names and values set to the column data. * @param $obj_type_key * The object key representing the object that the data came from, if this is set then {BeginLink} and {EndLink} * will be enabled. * @return * A string with the parsed text * @see netforum_node_process_list_tags() * @see netforum_node_process_link_tags() * @see netforum_node_process_curly_tags() */ function netforum_node_process_text($text, $object_data = array(), $obj_type_key = '') { if ($text == '' || stristr($text, "{") == false ) { return $text; } if (is_array($object_data) == false) { $object_data = get_object_vars($object_data); } if (is_array($object_data) == false || count($object_data) == 0) { return $text; } $special_curlies = netforum_node_special_fields(); foreach (array_keys($object_data) as $col) { if (stristr($col, "html")) { //look for columns with HTML in the name and parse them for curly brackets just in case there are any calls to {ApplicationPath} or similar if ($ignore_html_tag) { $object_data[$col] = ''; } else{ //This way the final output has the parsed html, not the straight database values $object_data[$col] = netforum_node_process_curly_tags($object_data[$col], $object_data, $obj_type_key, TRUE); } } } //combine the arrays so we can get a proper mapping $object_data = array_merge($object_data, $special_curlies); if (stristr($text, "{BeginList}")) { $text = netforum_node_process_list_tags($text, $object_data); } $text = netforum_node_process_curly_tags($text, $object_data, $obj_type_key); return $text; } /** * Find and evaluate all {BeginList} and {EndList} tags in the text * * The {BeginList} and {EndList} tags must have enough internal tags to * specify arguments for either an xWeb GetQuery or GetDynamicQuery * call, the results of which are used in the {BeginDetail} and {EndDetail} tags. * Nested {BeginList} and {EndList} tags are NOT supported. * * @param $text * A string, the text to be processed * @param $object_data * An array or object with fields and replacement values for the fields * @return * A string of text with the replacements inserted * @see netforum_node_process_text() * @see netforum_node_process_link_tags() * @see netforum_node_process_curly_tags() */ function netforum_node_process_list_tags($text, $object_data = array() ) { // Start with the sanity checks if ($text == '' || stristr($text, "{") == false ) { return $text; } if (is_array($object_data) == false) { $object_data = get_object_vars($object_data); } if (is_array($object_data) == false) { return $text; } global $user; $show_debug = (user_access('administer site configuration') || user_access('create netFORUM node') || user_access('edit netFORUM nodes') || (user_access('edit own netFORUM nodes') && ($user->uid == $node->uid)) ); $lists_pattern = "/\{BeginList\}((?!\{BeginList).+?)\{EndList\}/is"; preg_match_all($lists_pattern, $text, $lists); $num_lists = count($lists[0]); $cache_time = variable_get('netforum_cache_default', '1 hour'); // only look in the default cache, if we're refreshing the information we don't want really old (possibly bad) data for ($index=0; $index < $num_lists; $index++ ) { $segment = $lists[1][$index]; // This is the text between the {BeginList} and {EndList} tags $replacement = ''; $szObjectName = ''; $szColumnList = ''; $szWhereClause = ''; $szOrderBy = ''; $szQueryName = ''; $detail_text = ''; // Find all the internal directives in the text, and start with the easiest stuff preg_match("/\{BeginObjectName\}(.+?)\{EndObjectName\}/is", $segment, $object_names); preg_match("/\{BeginQueryName\}(.+?)\{EndQueryName\}/is", $segment, $query_name); preg_match("/\{BeginDetail\}(.+?)\{EndDetail\}/is", $segment, $details); if (count($object_names) != 0) { $szObjectName = $object_names[1]; } else if ($show_debug) { drupal_set_message(t("Missing {BeginObjectName} and {EndObjectName} tags in list !num.", array('!num' => ($index+1) )), 'error'); } if (count($query_name) != 0) { $szQueryName = $query_name[1]; } if (count($details) != 0) { $detail_text = $details[1]; } else if ($show_debug) { drupal_set_message(t("Missing {BeginDetail} and {EndDetail} tags in list !num.", array('!num' => ($index+1) )), 'error'); } //If we have enough directives to call GetDynamicQuery do it and stop searching for other directives if ($szObjectName != '' && $szQueryName != '' && $detail_text != '') { $results = netforum_xweb_request('GetDynamicQuery', array('szObjectName' => $szObjectName, 'szQueryName' => $szQueryName), $cache_time ); if ($results) { foreach ($results->Result as $query_result) { $query_result = get_object_vars($query_result); $query_result = array_merge($object_data, $query_result); $replacement .= netforum_node_process_curly_tags($detail_text, $query_result, netforum_object_key($szObjectName) ); } } else if ($show_debug) { drupal_set_message(t("No results for the !obj_name list, make sure that GetDynamicQuery for !query_name works in !xweb_test", array('!obj_name' => $szObjectName, '!query_name' => $szQueryName, '!xweb_test' => l(t('xWeb', 'admin/settings/netforum/xwebtest')) )), 'error'); } } else { //Get all the other directives and parse them out preg_match("/\{BeginColumns\}(.+?)\{EndColumns\}/is", $segment, $columns); preg_match("/\{BeginWhere\}(.+?)\{EndWhere\}/is", $segment, $where_clauses); preg_match("/\{BeginOrderBy\}(.+?)\{EndOrderBy\}/is", $segment, $order_by); if (count($columns) != 0) { $szColumnList = $columns[1]; } else if ($show_debug) { drupal_set_message(t("Missing {BeginColumns} and {EndColumns} tags in list !num.", array('!num' => ($index+1) )), 'error'); } if (count($where_clauses) != 0) { $szWhereClause = $where_clauses[1]; } else if ($show_debug) { drupal_set_message(t("Missing {BeginWhere} and {EndWhere} tags in list !num.", array('!num' => ($index+1) )), 'error'); } if (count($order_by) != 0) { $szOrderBy = $order_by[1]; } if ($szObjectName != '' && $szWhereClause != '' && $szColumnList != '' && $detail_text != '') { $arguments = array( 'szObjectName' => $szObjectName, 'szWhereClause' => netforum_node_process_curly_tags($szWhereClause, $object_data), 'szColumnList' => $szColumnList, 'szOrderBy' => $szOrderBy, ); $results = netforum_xweb_request('GetQuery', $arguments, $cache_time); if ($results) { $obj_name = netforum_clean_object_name($szObjectName) ."Object"; foreach ($results->{$obj_name} as $query_result) { $query_result = get_object_vars($query_result); $query_result = array_merge($object_data, $query_result); $replacement .= netforum_node_process_curly_tags($detail_text, $query_result, netforum_object_key($szObjectName) ); } } if ($show_debug && $replacement == '') { drupal_set_message(t("No results for the !obj_name list, make sure that GetQuery works like it should in !xweb_test", array('!obj_name' => $szObjectName, '!xweb_test' => l(t('xWeb'), 'admin/settings/netforum/xwebtest') )), 'error'); } } else if ($show_debug && $szWhereClause == '' && $szColumnList == '') { drupal_set_message(t("No output for list number !num, perhaps you meant to add {BeginQueryName} and {EndQueryName}?", array('!num' => ($index+1) )), 'error'); } } $text = str_ireplace($lists[0][$index], $replacement, $text); } return $text; } /** * Replace all {BeginLink} and {EndLink} tags with a link to a node * * For use within lists any text between the {BeginLink} and {EndLink} tags will * be linked to a node with the current object key if it exists, or link to a new * node with that object key if a template for that object exists. * * @param $text * A string with the text to be searched and replaced * @param $object_data * An array or object with the fields and values to be inserted * @param $obj_type_key * A string with the guid of the object type to link to * @return * A string with links inserted * @see netforum_node_process_text() * @see netforum_node_process_list_tags() * @see netforum_node_process_curly_tags() */ function netforum_node_process_link_tags($text, $object_data, $obj_type_key) { //start with some basic sanity checks $obj_type_key = (string)$obj_type_key; if ($text == '' || stristr($text, "{") == false ) { return $text; } if (is_array($object_data) == false) { $object_data = get_object_vars($object_data); } if (is_array($object_data) == false) { return $text; } // some things aren't likely to change in during the request, so hang onto them and avoid database calls static $prefix_cache = array(); static $template_cache = array(); if (array_key_exists($obj_type_key, $prefix_cache)) { $obj_prefix = $prefix_cache[$obj_type_key]; } else{ $obj_prefix = db_result(db_query("SELECT obj_prefix FROM {netforum_object_cache} WHERE obj_key = '%s'", $obj_type_key)); $prefix_cache[$obj_type_key] = $obj_prefix; } $links_pattern = "/\{BeginLink\}(.+?)\{EndLink\}/is"; preg_match_all($links_pattern, $text, $links); $num_links = count($links[0]); for ($index=0; $index < $num_links; $index++ ) { $segment = $links[1][$index]; //This is the text in between the {BeginLink} and {EndLink} tags $replacement = ''; //If there are any conditionals or curlies in the link text, figure it out $link_text = netforum_node_process_curly_tags($segment, $object_data); if ($obj_prefix) { // Find the objects primary key, first checking for prefix_key, and if that fails prefix_cst_key $nfn_key = $object_data[$obj_prefix ."_key"] ? $object_data[$obj_prefix ."_key"] : $object_data[$obj_prefix ."_cst_key"] ; $nid = db_result(db_query("SELECT nid FROM {netforum_node} WHERE nfn_key = '%s' LIMIT 1", $nfn_key)); if ($nid) { //If a node with that object key and type exists, link to it. If there's more than one node with the same key and object, well, good luck. $replacement .= l($link_text, 'node/'. $nid); } else { //If no node exists, find out if we have a template if (array_key_exists($obj_type_key, $template_cache)) { $template_count = $template_cache[$obj_type_key]; } else { $template_count = db_result(db_query("SELECT count(nft_obj_key) FROM {netforum_node_types} WHERE nft_obj_key = '%s'", $obj_type_key)); $template_cache[$obj_type_key] = $template_count; } if ($template_count > 0) { //If there is a template for it, link it. If a request for a node comes in with two valid guids it will create the node with netforum_node_create_from_template $replacement .= l($link_text, 'node/'. $obj_type_key ."/". $nfn_key ); } else { $replacement .= $link_text; //if there's no template there's no link. } } } else{ //If we have no object or no object prefix, then we have nothing to link to $replacement .= $link_text; } $text = str_ireplace($links[0][$index], $replacement, $text); } return $text; } /** * Replace object columns inside curly brackets and conditionals with the object values * * Treat text like the correspondence template or HTML content type on eWeb forms by replacing * curly brackets with appropriate data depending on the object passed. If no object is presented * then any object columns will be replaced with empty values. * * @param $text * A string with the plaint text to be replaced * @param $object_data * A hashed array representing an object with keys set to column names and values set to the column data. * @param $obj_type_key * A string, if set the text will call netforum_node_process_list_tags to process {BeginLink} and {EndLink} tags * @param $ignore_html_tag * Boolean, if set to true any tag with html in the name will be replaced with an empty string. * @return * A string with the parsed text * @see netforum_node_process_text() * @see netforum_node_process_list_tags() * @see netforum_node_process_link_tags() */ function netforum_node_process_curly_tags($text, $object_data = array(), $obj_type_key = '', $ignore_html_tag = false ) { if ($text == '' || stristr($text, "{") == false ) { return $text; } if (is_array($object_data) == false) { $object_data = get_object_vars($object_data); } if (is_array($object_data) == false) { return $text; } $simple_curly_pattern = '/\{[^\?]([^\{ \}]*) \}/x'; $conditional_curly_pattern = '/\{\?([^\{\}]*)\}/x'; if (stristr($text, "{BeginLink}") && netforum_is_valid_guid($obj_type_key) ) { $text = netforum_node_process_link_tags($text, $object_data, $obj_type_key); } //Do a case insensitive search and replace with two arrays, one is the object keys with { } added on each side and the other is the object values. $text = str_ireplace( array_map(create_function('$v', 'return "{". $v ."}";'), array_keys($object_data)), array_values($object_data), $text); //Replace any simple curly brackets with an empty string $text = preg_replace($simple_curly_pattern, '', $text); // now we get to dealing with conditional statements. Since they can be nested, we parse them out in a // while loop, but step one is priming the loop. We deal with them inside out, evaluating the simplest statements // before evaluating the complex nested ones. So in a conditional that looks like this: // {?cst_type=='Individual'?{?cst_name=='fred'?Hi fred?where's fred}?you're a company, even if you are named fred} // we evaluate {?cst_name=='fred'?Hi fred?where's fred} on the first loop, leaving us // {?cst_type=='Individual'?Hi fred?you're a company, even if you are named fred} // to evaluate on the second iteration preg_match_all($conditional_curly_pattern, $text, $conditional_matches); while (count($conditional_matches[0]) > 0) { //as long as we keep getting hits on the logic (eg, conditional matches), keep replacing them. $searches = array(); $replacements = array(); foreach ($conditional_matches[0] as $conditional) { $searches[] = $conditional; // this is the complete conditional statement from { to }, which we will replace once we figure out what to replace it with. $parts = explode('?', $conditional); //the eval/true/false parts //replace any known values in the evaluation part with the data, eg converting from cst_type to 'Customer' in the example above. Include double quotes around it and add slashes too. $condition = str_ireplace(array_keys($object_data), array_values($object_data), $parts[1]); $condition_result = _netforum_node_parse_conditional($condition); if ($condition_result == true && isset($parts[2]) ) { $replacements[] = $parts[2]; //these are the parts of the complete statement from { to }, so we're replacing the true part or the false part as appropriate } elseif (isset($parts[3])) { $replacements[] = trim($parts[3], "{}"); } else { $replacements[] = ''; } } $text = str_replace($searches, $replacements, $text); //search out any more matches and repeat if needed preg_match_all($conditional_curly_pattern, $text, $conditional_matches); } return $text; } /** * Evaluate the truth of a conditional statement as found in the netFORUM template syntax * * Designed to work with the netFORUM HTML template syntax this evaluates a statement without * using the insecure eval() function. It does strip beginning and ending spaces and quotes from the string, so * a single space is logically the same as empty. It is a little more tolerant in that you can have spaces between * the comparison operator * * @param * String with the test to be evaluated * @return * Boolean. True if the string can be evaluated and evaluates to true, false in all other cases * @see netforum_node_process_text() */ function _netforum_node_parse_conditional($cond_str) { $conditionals = array('==', '!=', '<=', '>=', '<>', '>', '<', '===', '!=='); foreach ($conditionals as $c) { $pos = strpos($cond_str, $c); if ($pos !== false) { list($lcomp, $rcomp) = explode($c, $cond_str); $cond = $c; $lcomp = trim($lcomp, " \"'"); $rcomp = trim($rcomp, " \"'"); break; //break the loop because we are only looking for one conditional to match, no compounds } } if (isset($cond) == false) { return false; //If we were called without a conditional for some reason return false } switch ($cond) { //you may see a pattern here... case '==': return ($lcomp == $rcomp); case '!=': return ($lcomp != $rcomp); case '<=': return ($lcomp <= $rcomp); case '>=': return ($lcomp >= $rcomp); case '<>': return ($lcomp <> $rcomp); case '>': return ($lcomp > $rcomp); case '<': return ($lcomp < $rcomp); case '===': return ($lcomp === $rcomp); case '!==': return ($lcomp !== $rcomp); default: return false; } } /* ******************************************************************************* ******************************************************************************* Special Field Functions ******************************************************************************* ******************************************************************************* */ /** * Get a list of special fields for use in netforum_node_process_text() * * The special fields are editable by the user at admin/settings/netforum/special-fields, * but a few are set up by default. These will be included on every call to process text * * @param $descriptions * Boolean defaults to false. If it is set true it will return the field descriptions instead of the values * @return * A hashed array with the field names and either values or descriptions depending on the $descriptions variable */ function netforum_node_special_fields($descriptions = false) { static $description_fields = array(); static $value_fields = array(); $fields = array(); if ($descriptions) { if (count($description_fields) > 0) { return $description_fields; } else{ $res = db_query("SELECT field_code, field_description FROM {netforum_node_special_fields}"); while ($fld = db_fetch_array($res)) { $fields[$fld['field_code']] = "SpecialField::"; $fields[$fld['field_code']] .= trim($fld['field_description']) == '' ? $fld['field_code'] : $fld['field_description']; } $description_fields = $fields; } } else { if (count($value_fields) > 0) { return $value_fields; } else { $res = db_query("SELECT field_code, field_value FROM {netforum_node_special_fields}"); while ($fld = db_fetch_array($res)) { $fields[$fld['field_code']] = netforum_node_special_field_value($fld['field_code'], $fld['field_value']); } $value_fields = $fields; } } return $fields; } /** * Get the value of the given special field * * Special fields can optionally include PHP code, this function looks for that and if found * executes the PHP. * * @param $field_code * A string with the field code * @param $field_value * A string, if not set the value from the database is used * @return * A string with the field value */ function netforum_node_special_field_value($field_code, $field_value = null) { if (is_null($field_value)) { $fld = db_fetch_array(db_query("SELECT field_value FROM {netforum_node_special_fields} WHERE field_code = '%s'", $field_code)); $field_value = $fld['field_value']; } if ($field_code && $field_value) { $field_value = trim($field_value); if (strtolower(substr($field_value, 0, 5)) == "field_code] = $fld; } $header = array(t('Code'), t('Description'), t('Value'), array('data' => t('Operations'), 'colspan' => '2')); $rows = array(); foreach ($fields as $fld ) { $code = check_plain($fld->field_code); $description = check_plain($fld->field_description); $value = netforum_node_special_field_value($code, $fld->field_value); // Populate the operations field. $operations = array(); // Set the edit column. $operations[] = array('data' => l(t('edit'), 'admin/settings/netforum/special-fields/'. $code)); // Set the delete column. $operations[] = array('data' => l(t('delete'), 'admin/settings/netforum/special-fields/'. $code .'/delete')); $row = array(l($code, 'admin/settings/netforum/special-fields/'. $code), check_plain($description), check_plain($value)); foreach ($operations as $operation) { $row[] = $operation; } $rows[] = $row; } if (empty($rows)) { $rows[] = array(array('data' => t('No special fields available.'), 'colspan' => '5', 'class' => 'message')); } return theme('table', $header, $rows); } /** * The form for adding or editing a special field */ function netforum_node_special_field_form($field_code = NULL) { $field_description = ''; $field_value = ''; if ($field_code != null) { $field_details = db_fetch_array(db_query("SELECT field_description, field_value from {netforum_node_special_fields} WHERE field_code = '%s'", $field_code)); $field_description = $field_details['field_description']; $field_value = $field_details['field_value']; } $form['field_code'] = array( '#type' => 'textfield', '#title' => t('Special field name'), '#size' => 50, '#required' => TRUE, '#weight' => -7, '#default_value' => $field_code, '#description' => t('The one-word code that will be used in netFORUM nodes'), ); $form['field_description'] = array( '#type' => 'textfield', '#title' => t('Special field description'), '#size' => 60, '#maxlength' => 50, '#weight' => -3, '#default_value' => $field_description, '#description' => t('A short, informative sentence about the special field'), ); $form['field_value'] = array( '#type' => 'textfield', '#title' => t('Special field value') , '#required' => TRUE, '#default_value' => $field_value, '#weight' => -5, '#description' => t('Any PHP code entered will be evaluated, just be sure to start with <?php and print some output.'), ); $form['submit'] = array( '#type' => 'submit', '#value' => t('Save special field'), ); if (!empty($field_code)) { $form['delete'] = array( '#type' => 'submit', '#value' => t('Delete special field'), ); $form['old_field_code'] = array( '#type' => 'hidden', '#value' => $field_code, ); } return $form; } /** * hook_validate for the special field form */ function netforum_node_special_field_form_validate($form, &$form_state) { /* TODO The 'op' element in the form values is deprecated. Each button can have #validate and #submit functions associated with it. Thus, there should be one button that submits the form and which invokes the normal form_id_validate and form_id_submit handlers. Any additional buttons which need to invoke different validate or submit functionality should have button-specific functions. */ $op = isset($form_state['values']['op']) ? $form_state['values']['op'] : ''; if ( $form_state['values']['field_code'] && stristr(trim($form_state['values']['field_code']), " ") != false ) { form_set_error('field_code', t('Special field codes cannot contain spaces')); } if ( $form_state['values']['field_code'] && (stristr($form_state['values']['field_code'], "}") != false || stristr($form_state['values']['field_code'], "{") != false)) { form_set_error('field_code', t('Special field codes cannot contain curly brackets like { or }')); } if ( empty($form_state['values']['old_field_code'])) { $is_existing = db_result(db_query("SELECT * FROM {netforum_node_special_fields} WHERE field_code = '%s'", $form_state['values']['field_code'])); if ($is_existing) { form_set_error('field_code', t('The %code special field code already exists.', array("%code" => check_plain($form_state['values']['field_code'])))); } } } /** * Implementation of hook_form_submit(). */ function netforum_node_special_field_form_submit($form, &$form_state) { /* TODO The 'op' element in the form values is deprecated. Each button can have #validate and #submit functions associated with it. Thus, there should be one button that submits the form and which invokes the normal form_id_validate and form_id_submit handlers. Any additional buttons which need to invoke different validate or submit functionality should have button-specific functions. */ $op = isset($form_state['values']['op']) ? $form_state['values']['op'] : ''; $fld = new stdClass(); $fld->field_code = trim($form_state['values']['field_code']); $fld->field_description = trim($form_state['values']['field_description']); $fld->field_value = trim($form_state['values']['field_value']); if ($op == t('Delete special field')) { return 'admin/settings/netforum/special-fields/'. $fld->field_code .'/delete'; } $status = netforum_node_special_field_save($fld); if ($status == SAVED_UPDATED) { drupal_set_message(t('The %code special field has been updated.', array('%code' => $fld->field_code))); } elseif ($status == SAVED_NEW) { drupal_set_message(t('The %code special field has been added.', array('%code' => $fld->field_code))); watchdog('netforum', 'Added %code special field.', array('%code' => $fld->field_code), WATCHDOG_NOTICE, l(t('view'), 'admin/settings/netforum/special-fields')); } $form_state['redirect'] = 'admin/settings/netforum/special-fields'; } /** * Save or update the special field * * @param $info * An object representing the special field * @return * SAVED_NEW or SAVED_UPDATED */ function netforum_node_special_field_save($info) { $is_existing = FALSE; $existing_field = !empty($info->field_code) ? $info->field_code : NULL; if ($existing_field) { $is_existing = db_result(db_query("SELECT * FROM {netforum_node_special_fields} WHERE field_code = '%s'", $existing_field)); } if ($is_existing) { db_query("UPDATE {netforum_node_special_fields} SET field_value = '%s', field_description = '%s' WHERE field_code = '%s'", $info->field_value, $info->field_description, $existing_field ); return SAVED_UPDATED; } else { $res = db_query("INSERT INTO {netforum_node_special_fields} (field_code, field_value, field_description) VALUES('%s', '%s', '%s') ", $existing_field, $info->field_value, $info->field_description); if (db_affected_rows($res) > 0) { return SAVED_NEW; } } } /** * hook_delete_confirm() for special fields */ function netforum_node_special_field_delete_confirm( $field_code) { $form['field_code'] = array('#type' => 'value', '#value' => $field_code); $message = t('Are you sure you want to delete the %code special field code?', array('%code' => $field_code)); $caption = '

'. t('This action cannot be undone.') .'

'; return confirm_form($form, $message, 'admin/settings/netforum/special-fields', $caption, t('Delete')); } /** * hook_delete_confirm_submit() for special fields, removes the field from the database */ function netforum_node_special_field_delete_confirm_submit($form, &$form_state) { db_query("DELETE FROM {netforum_node_special_fields} WHERE field_code = '%s'", $form_state['values']['field_code']); $t_args = array('%code' => $form_state['values']['field_code']); drupal_set_message(t('The %code special field has been deleted.', $t_args)); watchdog('netforum', 'Deleted %code special field.', $t_args, WATCHDOG_NOTICE); $form_state['redirect'] ='admin/settings/netforum/special-fields'; } /* ******************************************************************************* ******************************************************************************* Node template functions, used for templates ******************************************************************************* ******************************************************************************* */ /** * Displays the netforum node type admin overview page. * based off of the node_overview_types page */ function netforum_node_overview_types() { $types = array(); $res = db_query("SELECT a.nft_obj_key as nft_obj_key, default_title, obj_name FROM {netforum_node_types} a JOIN {netforum_object_cache} b ON a.nft_obj_key = b.obj_key"); while ($obj = db_fetch_object($res)) { $types[$obj->nft_obj_key] = $obj; } $header = array(t('Object'), t('Title'), array('data' => t('Operations'), 'colspan' => '2')); $rows = array(); foreach ($types as $object ) { $title = check_plain($object->default_title); $key = check_plain($object->nft_obj_key); // Populate the operations field. $operations = array(); // Set the edit column. $operations[] = array('data' => l(t('edit'), 'admin/content/types/netforum-node-templates/'. $key)); // Set the delete column. $operations[] = array('data' => l(t('delete'), 'admin/content/types/netforum-node-templates/'. $key .'/delete')); $row = array(array('data' => l($object->obj_name, 'admin/content/types/netforum-node-templates/'. $key), 'class' => $class), array('data' => check_plain($title), 'class' => $class) ); foreach ($operations as $operation) { $operation['class'] = $class; $row[] = $operation; } $rows[] = $row; } if (empty($rows)) { $rows[] = array(array('data' => t('No netforum node templates available.'), 'colspan' => '5', 'class' => 'message')); } return theme('table', $header, $rows); } /** * netFORUM Node type form, for netFORUM node templates * * @param $node_template * netFORUM node template, as an object * @return * netFORUM node template form */ function netforum_node_type_form($node_template = NULL) { if (!isset($node_template->nft_obj_key)) { $node_template = new stdClass(); $node_template->nft_obj_key = $node_template->obj_type_name = $node_template->title = $node_template->body = ''; drupal_add_js(drupal_get_path('module', 'netforum_node') .'/jquery.netforum_node.js'); } $type = node_get_types('type', 'netforum_node'); $form['obj_type_name'] = array( '#type' => 'textfield', '#title' => t('Object Name'), '#size' => 50, '#required' => TRUE, '#autocomplete_path' => 'admin/content/netforum-object-autocomplete', '#weight' => -7, '#default_value' => $node_template->obj_type_name, '#description' => t("This is the kind of information to pull from netFORUM, usually seen in profile forms in iWeb. Type the object name or the object prefix to search."), ); $form['key_obj_type_name'] = array( '#type' => 'hidden', '#value' => $node_template->obj_type_name, '#disabled' => true, ); $form['obj_field_help'] = array( '#type' => 'textfield', '#title' => t('Find object fields'), '#size' => 60, '#autocomplete_path' => 'admin/content/netforum-object-fields-autocomplete/'. $node_template->nft_obj_key, '#weight' => -3, '#default_value' => '', '#description' => t("Type the first few letters of what you're looking for to find the code. Use the code in the title or body to get the information from netFORUM"), ); $form['default_title'] = array( '#type' => 'textfield', '#title' => t('Default @title', array('@title' => check_plain($type->title_label))) , '#required' => TRUE, '#default_value' => $node_template->default_title, '#weight' => -5, '#description' => t("Any netFORUM nodes created with a blank title will use this title instead. Anything in curly brackets will be replaced with the information from netFORUM.'"), ); $form['default_body'] = array( '#type' => 'textarea', '#title' => t('Default @body', array('@body' => check_plain($type->body_label))), '#required' => TRUE, '#default_value' => $node_template->default_body, '#rows' => 7, '#description' => t("Any netFORUM nodes created with a blank body will use this title instead. Anything in curly brackets will be replaced with the information from netFORUM.'"), ); $form['nft_obj_key'] = array( '#type' => 'value', '#value' => $node_template->nft_obj_key, ); $form['submit'] = array( '#type' => 'submit', '#value' => t('Save node template'), ); if (!empty($node_template->nft_obj_key)) { $form['obj_type_name']['#autocomplete_path'] = ''; $form['obj_type_name']['#disabled'] = TRUE; $form['delete'] = array( '#type' => 'submit', '#value' => t('Delete node template'), ); } return $form; } /** * Validates the netforum node type form * */ function netforum_node_type_form_validate($form, &$form_state) { /* TODO The 'op' element in the form values is deprecated. Each button can have #validate and #submit functions associated with it. Thus, there should be one button that submits the form and which invokes the normal form_id_validate and form_id_submit handlers. Any additional buttons which need to invoke different validate or submit functionality should have button-specific functions. */ $op = isset($form_state['values']['op']) ? $form_state['values']['op'] : ''; if ( $form_state['values']['obj_type_name'] && (array_key_exists('nft_obj_key', $form_state['values']) == false || $form_state['values']['nft_obj_key'] == '') ) { $nft_obj_key = netforum_object_key($form_state['values']['obj_type_name']); $template_exists = false; if ( netforum_is_valid_guid($nft_obj_key)) { $count = db_result(db_query("SELECT count(default_title) FROM {netforum_node_types} WHERE nft_obj_key = '%s' ", $nft_obj_key)); $template_exists = ($count > 0); } if ($template_exists) { form_set_error('obj_type_name', t('A %obj_name netFORUM node type already exists, click !here to edit it. ', array( '%obj_name' => $form_state['values']['obj_type_name'], '!here' => l(t('here'), 'admin/content/types/netforum-node-templates/'. $nft_obj_key), ) ) ); } } } /** * Implementation of hook_form_submit(). */ function netforum_node_type_form_submit($form, &$form_state) { /* TODO The 'op' element in the form values is deprecated. Each button can have #validate and #submit functions associated with it. Thus, there should be one button that submits the form and which invokes the normal form_id_validate and form_id_submit handlers. Any additional buttons which need to invoke different validate or submit functionality should have button-specific functions. */ $op = isset($form_state['values']['op']) ? $form_state['values']['op'] : ''; if ($op == t('Delete node template')) { return 'admin/content/types/netforum-node-templates/'. $template->nft_obj_key .'/delete'; } $template = new stdClass(); $template->obj_type_name = trim($form_state['values']['obj_type_name']); $template->default_title = trim($form_state['values']['default_title']); $template->default_body = trim($form_state['values']['default_body']); $template->nft_obj_key = trim($form_state['values']['nft_obj_key']); $status = netforum_node_type_save($template); if ($status == SAVED_UPDATED) { drupal_set_message(t('The %name object template has been updated.', array('%name' => $template->obj_type_name))); } elseif ($status == SAVED_NEW) { drupal_set_message(t('The %name object template has been added.', array('%name' => $template->obj_type_name))); watchdog('netforum', 'Added %name object template.', array('%name' => $template->obj_type_name), WATCHDOG_NOTICE, l(t('view'), 'admin/content/types/netforum-node-templates')); } $form_state['redirect'] = 'admin/content/types/netforum-node-templates'; } /** * Saves a netforum object template to the database. * * @param $info * The template to save, as an object. * @return * Status flag indicating outcome of the operation. */ function netforum_node_type_save($info) { $is_existing = FALSE; $existing_type = !empty($info->nft_obj_key) ? $info->nft_obj_key : NULL; if ($existing_type) { $is_existing = db_result(db_query("SELECT * FROM {netforum_node_types} WHERE nft_obj_key = '%s'", $existing_type)); } if ($is_existing) { db_query("UPDATE {netforum_node_types} SET default_body = '%s', default_title = '%s' WHERE nft_obj_key = '%s'", $info->default_body, $info->default_title, $existing_type ); return SAVED_UPDATED; } else { $res = db_query("INSERT INTO {netforum_node_types} (nft_obj_key, default_title, default_body) SELECT obj_key, '%s', '%s' FROM {netforum_object_cache} WHERE obj_name = '%s' ", $info->default_title, $info->default_body, $info->obj_type_name); if (db_affected_rows($res) > 0) { return SAVED_NEW; } } } /** * Menu callback; delete a single node template. * * @param $node_template * The template to delete, as an object. * @return * confirmation form */ function netforum_node_type_delete_confirm( $node_template) { $form['nft_obj_key'] = array('#type' => 'value', '#value' => $node_template->nft_obj_key); $form['obj_type_name'] = array('#type' => 'value', '#value' => $node_template->obj_type_name); $message = t('Are you sure you want to delete the %object_name object template?', array('%object_name' => $node_template->obj_type_name)); $caption = '

'. t('This action cannot be undone.') .'

'; return confirm_form($form, $message, 'admin/content/types/netforum-node-templates', $caption, t('Delete')); } /** * Process object template delete confirm submissions. * * @param $form_id * @param $form_values * @return * path to the node types overview */ function netforum_node_type_delete_confirm_submit($form, &$form_state) { db_query("DELETE FROM {netforum_node_types} WHERE nft_obj_key = '%s'", $form_state['values']['nft_obj_key']); $t_args = array('%obj_name' => $form_state['values']['obj_type_name']); drupal_set_message(t('The %obj_name object template has been deleted.', $t_args)); watchdog('netforum', 'Deleted %obj_name object template.', $t_args, WATCHDOG_NOTICE); $form_state['redirect'] = 'admin/content/types/netforum-node-templates'; } /** * Implementation of hook_help() */ function netforum_node_help($path, $arg) { switch ($path) { case 'admin/help#netforum_node': $output = t('

This module adds a new node type that can fetch information from netFORUM and format it. The node title and body work much like e-Marketing (correspondence) templates in netFORUM, using curly brackets to insert data.

Most object fields are available, but newly added fields may not show up until you !refresh_obj_fields. The {child_form_text} and {child_form_html} statements are ignored. Conditional statements are fully supported, here are some examples:

Nodes can also use syntax similar to the List content type in eWeb. Here is an example to pull all email addresses for an Individual:

{BeginList}
  {BeginObjectName}IndividualEmail{EndObjectName}
  {BeginWhere}eml_cst_key = "{ind_cst_key}" {EndWhere}
  {BeginColumns}*{EndColumns}
  {BeginOrderBy}eml_type, eml_address {EndOrderBy}
  <ul>
  {BeginDetail}<li>{eml_address}, {BeginLink}{eml_type}{EndLink} <li>{EndDetail}
</ul>
{EndList}
The object name, columns, where statement, and order by are passed to xWeb, so if no results appear try your query in the !xweb_test page, being careful which columns you select. Any text between the {BeginLink} and {EndLink} tags is checked for curly bracket codes, and the resulting text is linked to a node on the website if one of the following is true:
  1. A netFORUM Node with the same object (IndividualEmail in this case) and object key (eml_key in this case) already exist on the site
  2. A netFORUM Node Type for that object (IndividualEmail in this case) exists, in which case a new node like the template will be created the first time a user clicks on the link
The List types can also get results from a query defined in iWeb too. Here is an example.
{BeginList}
  {BeginObjectName}Individual{EndObjectName}
  {BeginQueryName}Newest members{BeginQueryName}
  {BeginDetail}<li>{ind_first_name} joined on {ind_add_date}</li>{EndDetail}
{EndList}
The list will only return valid results if the iWeb query has no ask at runtime parameters.



netFORUM Dynamic Facade Nodes configuration pages


', array('!refresh_obj_fields' => l(t('refresh netFORUM object fields'), 'admin/settings/netforum/objectinfo'), '!xweb_test' => l(t('xWeb testing'), 'admin/settings/netforum/xwebtest'), '!curlies' => l(t('netFORUM node special fields'), 'admin/settings/netforum/special-fields'), '!templates' => l(t('netFORUM Node Templates'), 'admin/content/types/netforum-node-templates'), )); return $output; } }