/** 
  * Get display value(s) out of result $po_result for specified bundle and format it using the configured value template
  *
  * @param object $po_result A sub-class of SearchResult or BaseModel to extract data out of
  * @param int $pn_placement_id 
  * @param array Optional array of options. Supported options include:
  *		request = The current RequestHTTP object
  *		convertCodesToDisplayText = If true numeric list id's and value lists are converted to display text. Default is true. If false then all such values are returned as the original integer codes.
  *		forReport = If true then certain values are transformed for display in a report. Namely, all media output is forced to use the version specified by the app.conf 'representation_version_for_report' directive, no matter the setting in the display.
  *		purify = if true then value is run through HTMLPurifier (http://htmlpurifier.org) before being returned; this is useful when you want to make sure any HTML in the value is valid, particularly when converting HTML to a PDF as invalid markup will cause an exception. Default is false as HTMLPurify can significantly slow down things if used everywhere.
  *		delimiter = character(s) to place between repeating values
  *		showHierarchy = 
  *		showCurrentOnly = 
  * @return string The processed value ready for display
  */
 public function getDisplayValue($po_result, $pn_placement_id, $pa_options = null)
 {
     if (!is_array($pa_options)) {
         $pa_options = array();
     }
     if (!is_numeric($pn_placement_id)) {
         $vs_bundle_name = $pn_placement_id;
         $va_placement = array();
     } else {
         $va_placements = $this->getPlacements();
         $va_placement = $va_placements[$pn_placement_id];
         $vs_bundle_name = $va_placement['bundle_name'];
     }
     $va_settings = caGetOption('settings', $va_placement, array(), array('castTo' => 'array'));
     $o_request = caGetOption('request', $pa_options, null);
     if (!isset($pa_options['convertCodesToDisplayText'])) {
         $pa_options['convertCodesToDisplayText'] = true;
     }
     if (!isset($pa_options['forReport'])) {
         $pa_options['forReport'] = false;
     }
     if (!isset($pa_options['purify'])) {
         $pa_options['purify'] = false;
     }
     if (!isset($pa_options['asHTML'])) {
         $pa_options['asHTML'] = true;
     }
     if (!isset($pa_options['maximumLength'])) {
         $pa_options['maximumLength'] = $va_settings['maximum_length'] ? $va_settings['maximum_length'] : null;
     }
     if (!isset($pa_options['filter'])) {
         $pa_options['filter'] = caGetOption('filter', $va_settings, null);
     }
     $pa_options['delimiter'] = caGetOption('delimiter', $pa_options, caGetOption('delimiter', $va_settings, '; '));
     $pa_options['dateFormat'] = caGetOption('dateFormat', $pa_options, caGetOption('dateFormat', $va_settings, ''));
     $pa_options['useSingular'] = isset($va_settings['sense']) && $va_settings['sense'] == 'singular' ? true : false;
     $pa_options['returnURL'] = isset($va_settings['display_mode']) && $va_settings['display_mode'] == 'url' ? true : false;
     if (caGetOption('display_currency_conversion', $va_settings, false) && $o_request && $o_request->isLoggedIn()) {
         $pa_options['displayCurrencyConversion'] = $o_request->user->getPreference('currency');
     }
     $va_bundle_bits = explode('.', $vs_bundle_name);
     $pa_options['restrictToRelationshipTypes'] = caGetOption('restrict_to_relationship_types', $va_settings, null);
     $pa_options['restrictToTypes'] = caGetOption('restrict_to_types', $va_settings, null);
     unset($pa_options['format']);
     // don't pass format strings to get() here
     if (sizeof($va_bundle_bits) == 1 || sizeof($va_bundle_bits) == 2 && $va_bundle_bits[1] == 'related') {
         $pa_options['template'] = caGetOption('format', $va_settings, $this->getAppConfig()->get($va_bundle_bits[0] . '_relationship_display_format'));
     } else {
         $pa_options['template'] = caGetOption('format', $va_settings, null);
     }
     $vs_val = '';
     if ($vs_template = trim($pa_options['template'])) {
         unset($pa_options['template']);
         if ($t_instance = $this->getAppDatamodel()->getInstanceByTableName($va_bundle_bits[0], true)) {
             $va_bundle_bits_proc = $va_bundle_bits;
             $vb_is_related = false;
             if (sizeof($va_bundle_bits) == 1 || sizeof($va_bundle_bits) == 2 && $va_bundle_bits[1] == 'related') {
                 // pulling related
                 $vb_is_related = true;
             } elseif (sizeof($va_bundle_bits_proc) > 1 && in_array($vs_tmp = array_pop($va_bundle_bits_proc), array('related'))) {
                 // pulling related
                 $va_bundle_bits_proc[] = $vs_tmp;
                 $vb_is_related = true;
             } else {
                 // pulling current record
                 $va_bundle_bits_proc[] = $t_instance->primaryKey();
             }
             if ($vb_is_related) {
                 $vs_restrict_to_types = is_array($pa_options['restrictToTypes']) ? "restrictToTypes=\"" . join("|", $pa_options['restrictToTypes']) . "\"" : "";
                 $vs_restrict_to_relationship_types = is_array($pa_options['restrictToRelationshipTypes']) ? "restrictToRelationshipTypes=\"" . join("|", $pa_options['restrictToRelationshipTypes']) . "\"" : "";
                 // resolve template relative to relationship
                 $o_dm = $this->getAppDatamodel();
                 if (is_array($va_path = $o_dm->getPath($po_result->tableName(), $t_instance->tableName()))) {
                     $va_path = array_keys($va_path);
                     $vs_unit_tag = "<unit relativeTo=\"" . $va_path[1] . "\" delimiter=\"" . $pa_options['delimiter'] . "\" {$vs_restrict_to_types} {$vs_restrict_to_relationship_types}>";
                     switch (sizeof($va_path)) {
                         case 3:
                             // For regular relationships just evaluate the template relative to the relationship record
                             // this way the template can reference interstitial data
                             $vs_val = $po_result->getWithTemplate($vs_unit_tag . $vs_template . "</unit>", $pa_options);
                             break;
                         case 2:
                             $t_rel = $o_dm->getInstanceByTableName($va_path[1], true);
                             if (method_exists($t_rel, 'isSelfRelationship') && $t_rel->isSelfRelationship()) {
                                 // is a self-relationship
                                 $vs_val = $po_result->getWithTemplate($vs_unit_tag . $vs_template . "</unit>", array_merge($pa_options, array('primaryIDs' => array($po_result->tableName() => array($po_result->getPrimaryKey())))));
                             } else {
                                 // is a many-one relationship; evaluate the template for these relative
                                 // to the related record
                                 $vs_val = $po_result->getWithTemplate($vs_unit_tag . $vs_template . "</unit>", $pa_options);
                             }
                             break;
                         default:
                             $vs_val = _t("???");
                             break;
                     }
                 }
             } else {
                 // resolve template relative to current record
                 $vs_val = $po_result->getWithTemplate($vs_template);
             }
         }
     } else {
         // Straight get
         $vs_val = $po_result->get(join(".", $va_bundle_bits), $pa_options);
     }
     if (isset($pa_options['purify']) && $pa_options['purify']) {
         $vs_val = ca_bundle_displays::getPurifier()->purify($vs_val);
     }
     return $vs_val;
 }