/** * Create a data tree of the selectable forms. Deisgned to be fed into tree select * @param array $fields an ordered array E.g array('village+county','county','district,'region+country','country'). * it is an "bottom up" array of string where strings are of the form "$form" or "$form+$link_field". In the case of * the former type, then $link_field is assumed to be the next form. So for example, "county" has link field "district". * If a "$form(+$link_field)" is surrounded by brackets [ ] , it is not displayed. * @param array $forms An unorderd array of form names whose values we allow to be selected * @param array $limits An array with keys form names and value limit data * @param array $orders An array with keys form names and values array of field orders for that form. * If the form name has no orders, we use default ordering for that form based on its displayed firelds * @param int $show_hidden 0=non-hidden, 1=All, 2=hidden only. Defaults to 0 * @param array $report A report name to use for the query instead of building it from form cache or directly. * @return array */ public static function buildDataTree($fields, $forms, $limits, $orders = array(), $show_hidden = 0, $report = null, $style = 'default') { //$forms = array(village, county) -- order does not matter. //$fields -- order matters. //Example0: //$fields == array('village','county','[district+cssc_region],'cssc_region+cssc_country','country') //Example1: //$fields == array('village+county','county','district,'region+country','country') -- you could just as easily have used 'region' //Example2: //$fields == array('village','county'); //build the tree top down. top = country in Example 0,1 and top = county in Example 2 if (!is_array($forms) || !is_array($fields) || count($fields) == 0) { return array(); } $data = array(); $displayed = array(); $last_form = false; $fields = array_reverse($fields); foreach ($fields as &$field) { //start0: $form = country $link_field = false $last_form = false //next0: $form = cssc_region $link_field = country $last_form = country //start1: $form = country $link_field = false $last_form = false //next0: $form = region $link_field = country $last_form = country //start2: $form = county $link_field = false $last_form = false //next2: $form = village $link_field = county $last_form = county if (!is_string($field)) { return array(); } $len = strlen($field); if ($len >= 2 && $field[0] == '[' && $field[$len - 1] == ']') { $field = substr($field, 1, $len - 2); $display = false; } else { $display = true; } if (($pos = strpos($field, '+')) !== false) { list($form, $link_field) = explode('+', $field, 2); if ($last_form == false) { //throw away junk linked field data on the top level form $link_field = false; } } else { $form = $field; $link_field = false; } if (!$form) { return array(); } if (!$link_field) { $link_field = $last_form; } $field = array($form, $link_field); $displayed[$form] = $display; $last_form = $form; } unset($field); $styles = array(); $ff = I2CE_FormFactory::instance(); if ($last_form) { $avail_styles = array($last_form => $style); $curr_style = $style; foreach (array_reverse($fields) as $formfield) { list($form, $link_field) = $formfield; if (!$form || !($formObj = $ff->createContainer($form)) instanceof I2CE_Form) { break; } if (array_key_exists($form, $avail_styles) && is_string($avail_styles[$form])) { $curr_style = $avail_styles[$form]; } else { $curr_style = 'default'; } $styles["{$form}+{$link_field}"] = $curr_style; if (!$link_field || !($fieldObj = $formObj->getField($link_field)) instanceof I2CE_FormField) { break; } $avail_styles = I2CE_List::getDisplayFieldStyles($form, $style); } } if (is_array($report)) { $results = self::buildReportTree($fields, $forms, $displayed, $limits, $orders, $show_hidden, $report); if (count($results) > 0) { return $results; } else { I2CE::raiseError("buildReportTree returned no results so defaulting to regular display. If there is data then something went wrong so it should be fixed."); } } $use_cache = true; if (I2CE_ModuleFactory::instance()->isEnabled("CachedForms")) { $fs = I2CE_FormStorage::getMechanismByStorage("cached"); if ($fs instanceof I2CE_FormStorage_cached) { try { return $fs->buildDataTree($fields, $forms, $displayed, $limits, $orders, $show_hidden, $style); } catch (Exception $e) { $use_cache = false; I2CE::raiseError("Could not cache {$form}"); } } } $phonebook = array(); //indexed by "$form|$id" with values (by reference) the arrays at which contains the 'children' sub-array for $form|$id node $parent_links = array(); //indexed by "$form|$id" with values "$pform|$pid" which is the form/id that "$form|$id" is linked against $display_string = array(); foreach ($fields as $formfield) { list($form, $link_field) = $formfield; if (array_key_exists("{$form}+{$link_field}", $limits)) { $limit = $limits["{$form}+{$link_field}"]; } elseif (array_key_exists($form, $limits)) { $limit = $limits[$form]; } else { $limit = array(); } if (!($formObj = $ff->createContainer($form)) instanceof I2CE_Form) { continue; } $style = 'default'; if (array_key_exists("{$form}+{$link_field}", $styles)) { $style = $styles["{$form}+{$link_field}"]; } //if we dont show the hidden list memmber we need to include the limit where i2ce_disabled is false $limit = self::showHiddenLimit($limit, $show_hidden); $disp_fields = I2CE_List::getDisplayFields($form, $style); $disp_str = I2CE_List::getDisplayString($form, $style); //start0: $form = country, $fields = (name) //next0: $form = cssc_region, $fields = (name, cssc_country) //next0: $form = district $fields = (name, cssc_region) //next0: $form = county $field = (name,distrcit) //end0: $form = villate $field = (name,county) //start1: $form = country, $fields = (name) //next1: $form = region, $fields = (name, country) //next1: $form = district $fields = (name, region) //etc. if (array_key_exists($form, $orders)) { $order = $orders[$form]; } else { $order = I2CE_List::getSortFields($form, $style); } ksort($order); if ($link_field) { $field_datas = I2CE_FormStorage::listFields($form, $link_field, false, $limit, $order, false, -1, $use_cache); } else { $field_datas = I2CE_FormStorage::listFields($form, 'id', false, $limit, $order, false, -1, $use_cache); } $display_datas = I2CE_FormStorage::listFields($form, $disp_fields, false, $limit, $order, false, -1, $use_cache); $link_id = false; $last_link = false; $selectable = in_array($form, $forms); foreach ($field_datas as $id => $field_data) { $formid = $form . '|' . $id; if (!$link_field) { //this should only be the case for the top form $parent =& $data; } else { //we are not at the top. $link = $field_data[$link_field]; unset($field_data[$link_field]); if ($last_link != $link) { if (!array_key_exists($link, $phonebook)) { //don't know where to put this as a child of the previous one so skip it continue; } $last_link = $link; if (!array_key_exists('children', $phonebook[$link])) { $phonebook[$link]['children'] = array(); } $parent =& $phonebook[$link]['children']; //example: $diplayed == array(country=>true, region=>false, district=>true, county=>true village => true) //we have $form = district, $formid = district|30, $parent_link= region|40 end($displayed); $disp_form = key($displayed); while ($disp_form !== false && $disp_form !== $form) { prev($displayed); $disp_form = key($displayed); } //we end here either before the beginning of the array or where $disp_form == $form. prev($displayed); //we are now at the one before the $form. if the current form was district, we are now at region $parent_link = $link; } else { if (!array_key_exists($link, $phonebook) || !$phonebook[$link]) { //don't know where to put this as a child of the previous one so skip it continue; } } $parent_links[$formid] = $link; } if (!array_key_exists($id, $display_datas)) { continue; } $disp_array = array(); foreach ($disp_fields as $field) { if (array_key_exists($field, $display_datas[$id]) && ($fieldObj = $formObj->getField($field)) instanceof I2CE_FormField) { $fieldObj->setFromDB($display_datas[$id][$field]); $disp_array[] = $fieldObj->getDisplayValue(false, $style); } else { $disp_array[] = ''; } } $display = vsprintf($disp_str, $disp_array); $child_data = array('display' => $display, 'show' => $displayed[$form]); if ($selectable) { $child_data['value'] = $formid; } $parent[] = $child_data; end($parent); $phonebook[$formid] =& $parent[key($parent)]; } } self::removeNotShownNodes($data); return $data; }
/** * Build the data tree for the given list of fields * and limits. This is called by I2CE_List::buildDataTree. * See that for more details * @see I2CE_List::buildDataTree * @param array $fields The fields to build the tree * @param array $forms The selectable forms * @param array $displayed The displayed forms for the tree * @param array $limits The list of limits for each form. * @param array $orders The order fields for each given form * @param int $show_hidden 0=non-hidden, 1=All, 2=hidden only. Defaults to 0 * @return array The ordered list of all entries in the tree. */ public function buildDataTree($fields, $forms, $displayed, $limits, $orders, $show_hidden = 0, $style = 'default') { $prev_form = false; $form_aliases = array(); $selects = array(); $displays = array(); $all_orders = array(); $formObjs = array(); $skip_links = array(); $skip_link_fields = array(); $this->preserve_ids = true; $last_form = false; $styles = array(); end($fields); $formfield = current($fields); if (is_array($formfield)) { list($last_form, $link_field) = $formfield; } if ($last_form) { $ff = I2CE_FormFactory::instance(); $avail_styles = array($last_form => $style); $curr_style = $style; foreach (array_reverse($fields) as $formfield) { list($form, $link_field) = $formfield; if (!$form || !($formObj = $ff->createContainer($form)) instanceof I2CE_Form) { break; } if (array_key_exists($form, $avail_styles) && is_string($avail_styles[$form])) { $curr_style = $avail_styles[$form]; } else { $curr_style = 'default'; } $styles["{$form}+{$link_field}"] = $curr_style; if (!$link_field || !($fieldObj = $formObj->getField($link_field)) instanceof I2CE_FormField) { break; } $avail_styles = I2CE_List::getDisplayFieldStyles($form, $style); } } foreach ($fields as $formfield) { list($form, $link_field) = $formfield; $cachedForm = new I2CE_CachedForm($form); $cachedForm->generateCachedTable(); unset($cachedForm); $alias_form = $form . ($link_field ? "+{$link_field}" : ""); if (array_key_exists($alias_form, $limits)) { $limit = $limits[$alias_form]; } elseif (array_key_exists($form, $limits)) { $limit = $limits[$form]; } else { $limit = array(); } self::addFormIdToLimit($form, $limit); $limit = I2CE_List::showHiddenLimit($limit, $show_hidden); $style = 'default'; if (array_key_exists($form . '+' . $link_field, $styles)) { $style = $styles[$form . '+' . $link_field]; } $disp_fields = I2CE_List::getDisplayFields($form, $style); $field_list = $disp_fields; $field_list[] = 'id'; if ($link_field && !in_array($link_field, $field_list)) { $field_list[] = $link_field; } if (array_key_exists($form, $orders)) { $order = $orders[$form]; } else { $order = I2CE_List::getSortFields($form, $style); } $sort_list = array(); if (array_key_exists($form, $displayed) && $displayed[$form]) { $displays[$alias_form]['form'] = $form; $displays[$alias_form]['link_field'] = $link_field; foreach ($disp_fields as $disp) { $displays[$alias_form]['fields'][$disp] = "{$alias_form}+{$disp}"; } foreach ($order as $i => $ord) { if (!is_string($ord)) { unset($order[$i]); continue; } if ($ord[0] == '-') { $field = substr($ord, 1); $all_orders[] = "`{$alias_form}`.`{$field}` DESC"; } else { $field = $ord; $all_orders[] = "`{$alias_form}`.`{$field}` ASC"; } if (!in_array($field, $field_list)) { $sort_list[] = $field; } } } else { $skip_link_fields[$alias_form] = $link_field; } if (!array_key_exists($form, $formObjs)) { $formObjs[$form] = $this->getContainer($form); if (!$formObjs[$form] instanceof I2CE_Form) { I2CE::raiseError("Could not instantiate {$form}"); return array(); } } $where = false; if (is_array($limit) && count($limit) > 0) { $where = $formObjs[$form]->generateWhereClause($limit, array("I2CE_FormStorage_cached", "getSQLField")); } $query = $this->getRequiredFieldsQuery($form, array_merge($field_list, $sort_list), null, false, array("I2CE_FormStorage_cached", "getSQLField")); if (is_array($prev_form) && array_key_exists('form', $prev_form)) { if ($prev_form['form'] == $form) { $froms[$alias_form] = " LEFT JOIN "; } else { $froms[$alias_form] = " LEFT JOIN "; } } else { $froms[$alias_form] = ""; } $froms[$alias_form] .= "({$query}" . ($where ? " WHERE " . $where : "") . ") AS `{$alias_form}`"; if ($link_field) { $join_ons = array(); $ff = $formObjs[$form]->getField($link_field); if ($ff instanceof I2CE_FormField_MAPPED) { foreach ($ff->getSelectableForms() as $link_form) { if (array_key_exists($link_form, $form_aliases)) { foreach ($form_aliases[$link_form] as $link_alias) { $join_ons[] = "`{$alias_form}`.`{$link_field}` = `{$link_alias}`.`id`"; if (!array_key_exists($link_alias, $displays) && array_key_exists($link_alias, $skip_link_fields)) { $skip_links[$alias_form . '+' . $link_field] = $link_alias . '+' . $skip_link_fields[$link_alias]; } } } } } else { I2CE::raiseMessage("Not sure what to link for {$form} {$link_field} so guessing."); $join_ons[] = "`{$alias_form}`.`{$link_field}` = `" . $prev_form['alias'] . "`.`id`"; } if (count($join_ons) > 0) { $froms[$alias_form] .= " ON (" . implode(' OR ', $join_ons) . ") "; } else { I2CE::raiseError("Don't know how to join on {$form} {$link_field}"); return array(); } } foreach ($field_list as $field) { $selects[] = "`{$alias_form}`.`{$field}` AS `{$alias_form}+{$field}`"; } $prev_form = array('form' => $form, 'alias' => $alias_form); $form_aliases[$form][] = $alias_form; $last_form = $form; } $or_wheres = array(); foreach ($forms as $selectable) { if (array_key_exists($selectable, $form_aliases)) { foreach ($form_aliases[$selectable] as $required_form) { $or_wheres[] = "`{$required_form}`.`id` IS NOT NULL"; } } } $join_query = "SELECT " . implode(',', $selects) . " FROM " . implode('', $froms) . (count($or_wheres) > 0 ? " WHERE " . implode(' OR ', $or_wheres) : "") . (count($all_orders) > 0 ? " ORDER BY " . implode(',', $all_orders) : ""); //I2CE::raiseMessage( $join_query ); $res = $this->db->query($join_query); if (I2CE::pearError($res, "Bad query -- {$join_query}")) { return array(); } $prev_ids = array(); $results = array(); $phonebook = array(); $display_string = array(); foreach ($displays as $fix_link_alias => &$fix_link_disp_data) { if (array_key_exists('link_field', $fix_link_disp_data) && $fix_link_disp_data['link_field'] != '') { $full_link_field = strtolower($fix_link_alias . "+" . $fix_link_disp_data['link_field']); while (array_key_exists($full_link_field, $skip_links)) { $full_link_field = $skip_links[$full_link_field]; } $fix_link_disp_data['link_field'] = $full_link_field; } } $display_copy = $displays; while ($data = $res->fetchRow()) { unset($prev_disp); foreach ($displays as $alias => $disp_data) { $id_field = strtolower($alias . "+id"); if ($data->{$id_field} == null || array_key_exists($data->{$id_field}, $phonebook)) { continue; } $curr = array(); $add_this = false; if (in_array($disp_data['form'], $forms)) { $curr['value'] = $data->{$id_field}; $add_this = true; } if (!$add_this) { $check_ok = false; foreach ($display_copy as $alias_copy => $disp_data_copy) { if ($alias_copy == $alias) { $check_ok = true; continue; } if (!$check_ok) { continue; } if (array_key_exists('link_field', $disp_data_copy) && $disp_data_copy['link_field'] != '') { $link_field_copy = $disp_data_copy['link_field']; if ($data->{$id_field} == $data->{$link_field_copy}) { $add_this = true; break; } } } } if (!$add_this) { continue; } $style = 'default'; if (array_key_exists($disp_data['form'] . '+' . $disp_data['link_field'], $styles)) { $style = $styles[$disp_data['form'] . '+' . $disp_data['link_field']]; } if (!array_key_exists($disp_data['form'], $display_string)) { $display_string[$disp_data['form']] = I2CE_List::getDisplayString($disp_data['form'], $style); } $disp_array = array(); $disp_str = $display_string[$disp_data['form']]; $disp_str_arr = explode("%s", $disp_str); $fo = $formObjs[$disp_data['form']]; $disp_count = 0; foreach ($disp_data['fields'] as $field => $dbfield) { $disp_count++; if ($field == $disp_data['link_field']) { // Don't include the data from the link field since it will already // be in the tree above it. if ($disp_count == 1) { unset($disp_str_arr[$disp_count]); } else { unset($disp_str_arr[$disp_count - 1]); } continue; } $dbfield = strtolower($dbfield); $fieldObj = $fo->getField($field); if (!$fieldObj instanceof I2CE_FormField) { I2CE::raiseError("Could not get field {$field}"); continue; } if (isset($data->{$dbfield})) { $fieldObj->setFromDB($data->{$dbfield}); $disp_array[$field] = $fieldObj->getDisplayValue(false, $style); } else { $disp_array[$field] = null; } } $disp_str = implode('%s', $disp_str_arr); $display = vsprintf($disp_str, $disp_array); //$display = $data->$disp_data['fields']['name']; $curr['display'] = $display; $phonebook[$data->{$id_field}] =& $curr; if ($disp_data['link_field'] != '') { $link_field = $disp_data['link_field']; if (array_key_exists($data->{$link_field}, $phonebook)) { $add_to =& $phonebook[$data->{$link_field}]; if (!array_key_exists('children', $phonebook[$data->{$link_field}])) { $phonebook[$data->{$link_field}]['children'] = array(); } $phonebook[$data->{$link_field}]['children'][] =& $curr; } else { //I2CE::raiseMessage("Couldn't find $link_field " . $data->$link_field . " in phonebook"); } } else { $results[] =& $curr; } unset($curr); } } $this->preserve_ids = false; return $results; }