Example #1
0
 protected function functionHandler($match)
 {
     $parser = new ExpressionParser($match[1], $this->_context, false);
     # Last Parameter = DEBUG
     $parser->run();
     return $newValue = $parser->getReturn();
 }
Example #2
0
 function executeSelectQuery(&$sqlQuery)
 {
     global $g_sqlGroupingFuncs;
     global $g_sqlSingleRecFuncs;
     $resultSets = array();
     // create a copy
     $aliases = $sqlQuery->colAliases;
     $funcs = $sqlQuery->colFuncs;
     // Read all Tables
     for ($i = 0; $i < count($sqlQuery->tables); ++$i) {
         debug_printb("<br>[executeSelectQuery] Reading table " . $sqlQuery->tables[$i] . "<br>");
         if (!($resultSets[$i] = $this->readTable($sqlQuery->tables[$i]))) {
             print_error_msg("Reading Table " . $sqlQuery->tables[$i] . " failed");
             return false;
         }
         $resultSets[$i]->setColumnTableForAll($sqlQuery->tables[$i]);
         $resultSets[$i]->setColumnTableAliasForAll($sqlQuery->tableAliases[$i]);
         // set all functions to the ResultSet of the current table
         // if table and column name matches
         debug_printb("[executeSelectQuery] Setting functions for the current table:<br>");
         for ($j = 0; $j < count($funcs); ++$j) {
             if (!$funcs[$j] || !$sqlQuery->colNames[$j]) {
                 continue;
             }
             if ($sqlQuery->colTables[$j] == $sqlQuery->tables[$i] || $sqlQuery->colTables[$j] == $sqlQuery->tableAliases[$i]) {
                 $colNr = $resultSets[$i]->findColNrByAttrs($sqlQuery->colNames[$j], $sqlQuery->colTables[$j], "");
                 if ($colNr == NOT_FOUND) {
                     continue;
                 }
                 // create a new column for each function
                 $resultSets[$i]->addColumn($sqlQuery->colNames[$j], $sqlQuery->colAliases[$j], $sqlQuery->colTables[$j], "", "str", "", $funcs[$j], "", true);
                 $funcs[$j] = "";
             }
         }
         // set all aliases where table, column name and function matches
         debug_printb("[executeSelectQuery] Setting aliases for the current table:<br>");
         for ($j = 0; $j < count($aliases); ++$j) {
             if (!$aliases[$j]) {
                 continue;
             }
             if ($sqlQuery->colTables[$j] == $sqlQuery->tables[$i] || $sqlQuery->colTables[$j] == $sqlQuery->tableAliases[$i]) {
                 if (($colNr = $resultSets[$i]->findColNrByAttrs($sqlQuery->colNames[$j], $sqlQuery->colTables[$j], $sqlQuery->colFuncs[$j])) != NOT_FOUND) {
                     $resultSets[$i]->setColumnAlias($colNr, $aliases[$j]);
                     $aliases[$j] = "";
                 }
             }
         }
         if (TXTDBAPI_DEBUG) {
             debug_printb("<br>[executeSelectQuery] Dump of Table {$i} (" . $sqlQuery->tables[$i] . "):<br>");
             $resultSets[$i]->dump();
         }
     }
     // set remaining functions to the ResultSet where column name matches
     debug_printb("[executeSelectQuery] Setting remaining functions where column name matches:<br>");
     for ($i = 0; $i < count($resultSets); ++$i) {
         for ($j = 0; $j < count($funcs); ++$j) {
             if (!$funcs[$j] || !$sqlQuery->colNames[$j]) {
                 continue;
             }
             $colNr = $resultSets[$i]->findColNrByAttrs($sqlQuery->colNames[$j], "", "");
             if ($colNr == NOT_FOUND) {
                 // 'text' or 123 ? => add column
                 if (!(is_numeric($sqlQuery->colNames[$j]) || has_quotes($sqlQuery->colNames[$j]))) {
                     continue;
                 }
                 debug_print("Adding function with quoted string or number paremeter!<br>");
             }
             // create a new column for each function
             $resultSets[$i]->addColumn($sqlQuery->colNames[$j], $sqlQuery->colAliases[$j], $sqlQuery->colTables[$j], "", "str", "", $funcs[$j], "", true);
             $funcs[$j] = "";
         }
     }
     // set remaining aliases where column name and function matches
     debug_printb("[executeSelectQuery] Setting remaining aliases where column name and function matches:<br>");
     for ($i = 0; $i < count($resultSets); ++$i) {
         for ($j = 0; $j < count($aliases); ++$j) {
             if (!$aliases[$j]) {
                 continue;
             }
             if (($colNr = $resultSets[$i]->findColNrByAttrs($sqlQuery->colNames[$j], "", $sqlQuery->colFuncs[$j])) != NOT_FOUND) {
                 $resultSets[$i]->setColumnAlias($colNr, $aliases[$j]);
                 $aliases[$j] = "";
             }
         }
     }
     debug_printb("[executeSelectQuery] Executing single-rec functions (on the separate ResultSet's):<br>");
     // execute single-rec functions (on the separate ResultSet's)
     for ($i = 0; $i < count($resultSets); ++$i) {
         $resultSets[$i]->executeSingleRecFuncs();
     }
     // A query without tables ? => make a dummy ResultSet
     $dummyResultSet = false;
     if (count($sqlQuery->tables) == 0) {
         $dummyResultSet = true;
         $rsMaster = new ResultSet();
         $rsMaster->addColumn("(dummy)", "(dummy)", "(dummy)", "(dummy)", "str", "(dummy)", "", "", true);
         $rsMaster->append();
         // else: real ResultSet
     } else {
         $dummyResultSet = false;
         // join the ResultSet's
         $rsMaster = $resultSets[0];
         for ($i = 1; $i < count($resultSets); ++$i) {
             $rsMaster = $rsMaster->joinWithResultSet($resultSets[$i]);
         }
     }
     // from here we only work with $rsMaster and can free the other ResultSet's
     unset($resultSets);
     $resultSets = "";
     // generate additional columns for the remaining functions (functions without params)
     for ($i = 0; $i < count($funcs); ++$i) {
         if ($funcs[$i]) {
             $rsMaster->addColumn($sqlQuery->colNames[$i], $sqlQuery->colAliases[$i], "", "", "str", "", $funcs[$i], execFunc($funcs[$i], ""));
         }
     }
     // generate additional columns from the WHERE-expression
     $rsMaster->generateAdditionalColumnsFromWhereExpr($sqlQuery->where_expr);
     // generate additional columns from ORDER BY
     $rsMaster->generateAdditionalColumnsFromArray($sqlQuery->orderColumns);
     // generate additional columns from GROUP BY
     $rsMaster->generateAdditionalColumnsFromArray($sqlQuery->groupColumns);
     // execute the new single-rec functions (on the Master ResultSet)
     $rsMaster->executeSingleRecFuncs();
     // set row id's
     $rsMaster->reset();
     $rId = -1;
     while (++$rsMaster->pos < count($rsMaster->rows)) {
         $rsMaster->rows[$rsMaster->pos]->id = ++$rId;
     }
     --$rsMaster->pos;
     debug_printb("<br>[executeSelectQuery] Master ResultSet:</b><br>");
     if (TXTDBAPI_DEBUG) {
         $rsMaster->dump();
     }
     // apply WHERE expression
     if ($sqlQuery->where_expr) {
         $ep = new ExpressionParser();
         $rsMaster = $ep->getFilteredResultSet($rsMaster, $sqlQuery);
     }
     // free $ep
     unset($ep);
     $ep = "";
     // stop if the WHERE expression failed
     if (txtdbapi_error_occurred()) {
         return false;
     }
     // check if we can use some optimization
     // (use the limit in group by, but only if there are no grouping function
     // in the groupRows. To be able to do this we must order before grouping)
     $optimizedPath = true;
     if (!$sqlQuery->limit || !$sqlQuery->orderColumns) {
         $optimizedPath = false;
     } else {
         for ($i = 0; $i < count($sqlQuery->colFuncs); ++$i) {
             if (in_array($sqlQuery->colFuncs[$i], $g_sqlGroupingFuncs)) {
                 $optimizedPath = false;
                 break;
             }
         }
     }
     if ($optimizedPath) {
         debug_printb("[executeSelectQuery] Using optimized path!<br>");
     } else {
         debug_printb("[executeSelectQuery] Using normal path!<br>");
     }
     // Order ResultSet (if optimizedPath)
     if ($optimizedPath) {
         debug_printb("[executeSelectQuery] Calling orderRows() (optimized path)..<br>");
         if (count($sqlQuery->orderColumns) > 0) {
             $rsMaster->orderRows($sqlQuery->orderColumns, $sqlQuery->orderTypes);
         }
     }
     // Group ResultSet (process GROUP BY)
     $numGroupingFuncs = 0;
     for ($i = 0; $i < count($sqlQuery->colFuncs); ++$i) {
         if ($sqlQuery->colFuncs[$i] && in_array($sqlQuery->colFuncs[$i], $g_sqlGroupingFuncs)) {
             ++$numGroupingFuncs;
             break;
         }
     }
     if ($numGroupingFuncs > 0 || count($sqlQuery->groupColumns) > 0) {
         debug_printb("[executeSelectQuery] Calling groupRows()..<br>");
         $rsMaster = $rsMaster->groupRows($sqlQuery, $optimizedPath);
     }
     // Order ResultSet (if NOT optimizedPath)
     if (!$optimizedPath) {
         debug_printb("[executeSelectQuery] Calling orderRows() (normal path)..<br>");
         if (count($sqlQuery->orderColumns) > 0) {
             $rsMaster->orderRows($sqlQuery->orderColumns, $sqlQuery->orderTypes);
         }
     }
     // add direct value columns
     debug_printb("[executeSelectQuery] Adding direct value columns..<br>");
     for ($i = 0; $i < count($sqlQuery->colNames); ++$i) {
         if ($sqlQuery->colNames[$i] && (is_numeric($sqlQuery->colNames[$i]) || has_quotes($sqlQuery->colNames[$i])) && !$sqlQuery->colTables[$i] && !$sqlQuery->colFuncs[$i] && $rsMaster->findColNrByAttrs($sqlQuery->colNames[$i], "", "") == NOT_FOUND) {
             $value = $sqlQuery->colNames[$i];
             if (has_quotes($value)) {
                 remove_quotes($value);
             }
             $rsMaster->addColumn($sqlQuery->colNames[$i], $sqlQuery->colAliases[$i], "", "", "str", "", "", $value, true);
         }
     }
     // return only the requested columns
     debug_printb("[executeSelectQuery] Removing unwanted columns...<br>");
     $rsMaster->filterByColumnNamesInSqlQuery($sqlQuery);
     // order columns (not their data)
     debug_printb("[executeSelectQuery] Ordering columns (amog themself)...<br>");
     if (!$rsMaster->orderColumnsBySqlQuery($sqlQuery)) {
         print_error_msg("Ordering the Columns (themself) failed");
         return false;
     }
     // process DISTINCT
     if ($sqlQuery->distinct == 1) {
         $rsMaster = $rsMaster->makeDistinct($sqlQuery->limit);
     }
     // Apply Limit
     $rsMaster->reset();
     $rsMaster = $rsMaster->limitResultSet($sqlQuery->limit);
     verbose_debug_print("<br>Limited ResultSet:<br>");
     if (TXTDBAPI_VERBOSE_DEBUG) {
         $rsMaster->dump();
     }
     $rsMaster->reset();
     return $rsMaster;
 }
 /**
  * If a request (data-pipelining tag) doesn't include any dynamic tags, it's returned as is. If
  * however it does contain said tag, this function will attempt to resolve it using the $result
  * array, returning the parsed request on success, or FALSE on failure to resolve.
  *
  * @param array $request
  */
 private static function resolveRequest($request, $result)
 {
     $dataContext = self::makeContextData($result);
     foreach ($request as $key => $val) {
         $expressions = array();
         preg_match_all('/\\$\\{(.*)\\}/imxsU', $val, $expressions);
         $expressionCount = count($expressions[0]);
         if ($expressionCount) {
             for ($i = 0; $i < $expressionCount; $i++) {
                 $toReplace = $expressions[0][$i];
                 $expression = $expressions[1][$i];
                 try {
                     $expressionResult = ExpressionParser::evaluate($expression, $dataContext);
                     $request[$key] = str_replace($toReplace, $expressionResult, $request[$key]);
                 } catch (Exception $e) {
                     // ignore, maybe on the next pass we can resolve this
                     return false;
                 }
             }
         }
     }
     return $request;
 }
Example #4
0
/**
 * Replace "^" tags (eg. ^forename) in a template with values from an array
 *
 * @param string $ps_template String with embedded tags. Tags are just alphanumeric strings prefixed with a caret ("^")
 * @param string $pm_tablename_or_num Table name or number of table from which values are being formatted
 * @param string $pa_row_ids An array of primary key values in the specified table to be pulled into the template
 * @param array $pa_options Supported options are:
 *		returnAsArray = if true an array of processed template values is returned, otherwise the template values are returned as a string joined together with a delimiter. Default is false.
 *		delimiter = value to string together template values with when returnAsArray is false. Default is ';' (semicolon)
 *		relatedValues = array of field values to return in template when directly referenced. Array should be indexed numerically in parallel with $pa_row_ids
 *		relationshipValues = array of field values to return in template for relationship when directly referenced. Should be indexed by row_id and then by relation_id
 *		placeholderPrefix = attribute container to implicitly place primary record fields into. Ex. if the table is "ca_entities" and the placeholder is "address" then tags like ^city will resolve to ca_entities.address.city
 *		requireLinkTags = if set then links are only added when explicitly defined with <l> tags. Default is to make the entire text a link in the absence of <l> tags.
 *		resolveLinksUsing = 
 *		primaryIDs = row_ids for primary rows in related table, keyed by table name; when resolving ambiguous relationships the row_ids will be excluded from consideration. This option is rarely used and exists primarily to take care of a single
 *						edge case: you are processing a template relative to a self-relationship such as ca_entities_x_entities that includes references to the subject table (ca_entities, in the case of ca_entities_x_entities). There are
 *						two possible paths to take in this situations; primaryIDs lets you specify which ones you *don't* want to take by row_id. For interstitial editors, the ids will be set to a single id: that of the subject (Eg. ca_entities) row
 *						from which the interstitial was launched.
 * @return mixed Output of processed templates
 */
function caProcessTemplateForIDs($ps_template, $pm_tablename_or_num, $pa_row_ids, $pa_options = null)
{
    unset($pa_options['request']);
    unset($pa_options['template']);
    // we pass through options to get() and don't want templates
    if (!isset($pa_options['convertCodesToDisplayText'])) {
        $pa_options['convertCodesToDisplayText'] = true;
    }
    $pb_return_as_array = (bool) caGetOption('returnAsArray', $pa_options, false);
    if (!is_array($pa_row_ids) || !sizeof($pa_row_ids) || !$ps_template) {
        return $pb_return_as_array ? array() : "";
    }
    unset($pa_options['returnAsArray']);
    if (!isset($pa_options['requireLinkTags'])) {
        $pa_options['requireLinkTags'] = true;
    }
    $va_primary_ids = caGetOption("primaryIDs", $pa_options, null);
    $o_dm = Datamodel::load();
    $ps_tablename = is_numeric($pm_tablename_or_num) ? $o_dm->getTableName($pm_tablename_or_num) : $pm_tablename_or_num;
    $ps_resolve_links_using = caGetOption('resolveLinksUsing', $pa_options, $ps_tablename);
    $t_instance = $o_dm->getInstanceByTableName($ps_tablename, true);
    if ($ps_resolve_links_using != $ps_tablename) {
        $t_resolve_links_instance = $o_dm->getInstanceByTableName($ps_resolve_links_using, true);
        $vs_resolve_links_using_pk = $t_resolve_links_instance->primaryKey();
    }
    $vs_pk = $t_instance->primaryKey();
    $vs_delimiter = isset($pa_options['delimiter']) ? $pa_options['delimiter'] : '; ';
    $ps_template = str_replace("^_parent", "^{$ps_resolve_links_using}.parent.preferred_labels", $ps_template);
    $ps_template = str_replace("^_hierarchy", "^{$ps_resolve_links_using}._hierarchyName", $ps_template);
    $va_related_values = isset($pa_options['relatedValues']) && is_array($pa_options['relatedValues']) ? $pa_options['relatedValues'] : array();
    $va_relationship_values = isset($pa_options['relationshipValues']) && is_array($pa_options['relationshipValues']) ? $pa_options['relationshipValues'] : array();
    // Set up DomDocument XML parser
    $o_dom = new DOMDocument('1.0', 'utf-8');
    $o_dom->preserveWhiteSpace = true;
    libxml_use_internal_errors(true);
    // don't reported mangled HTML errors
    $o_dom->loadHTML('<?xml encoding="utf-8">' . $ps_template);
    libxml_clear_errors();
    // Parse units from template
    $o_xpath = new DOMXPath($o_dom);
    $o_units = $o_xpath->query('//unit[not(ancestor::unit)]');
    // only find units not nested within other units (allows for units with units...)
    $va_units = array();
    $ps_template = preg_replace("![\r\n\t]+!", "", html_entity_decode($ps_template));
    //DomDocument kills newlines and tabs so we do the same to the template
    $ps_template = preg_replace("!relativeTo[ ]*\\=!i", "relativeto=", $ps_template);
    //DomDocument forces attribute names to all lower case so we need to adjust the template to match
    $ps_template = preg_replace("!restrictToTypes[ ]*\\=!i", "restricttotypes=", $ps_template);
    $ps_template = preg_replace("!restrictToRelationshipTypes[ ]*\\=!i", "restricttorelationshiptypes=", $ps_template);
    $ps_template = preg_replace("!([A-Za-z0-9]+)='([^']*)'!", "\$1=\"\$2\"", $ps_template);
    //DomDocument converts quotes around attributes from single to double quotes, so we need to normalize the template to match
    $ps_template = preg_replace("!\\>[ ]+\\<!", "><", $ps_template);
    $vn_unit_id = 1;
    foreach ($o_units as $o_unit) {
        if (!$o_unit) {
            continue;
        }
        $vs_html = (string) $o_dom->saveXML($o_unit);
        $vs_content = preg_replace("!^<[^\\>]+>!", "", $vs_html);
        $vs_content = preg_replace("!<[^\\>]+>\$!", "", $vs_content);
        $vs_content = preg_replace("!>[ ]+<\$!", "><", $vs_content);
        // DomDocument messes with white space and encodes entities so we normalize the directive here so the str_ireplace() replacement below doesn't fail
        $va_units[] = $va_unit = array('tag' => $vs_unit_tag = "[[#{$vn_unit_id}]]", 'directive' => preg_replace("![\r\n\t\"]+!", "", html_entity_decode($vs_html)), 'content' => $vs_content, 'relativeTo' => (string) $o_unit->getAttribute("relativeto"), 'delimiter' => (string) $o_unit->getAttribute("delimiter"), 'restrictToTypes' => (string) $o_unit->getAttribute("restricttotypes"), 'restrictToRelationshipTypes' => (string) $o_unit->getAttribute("restricttorelationshiptypes"));
        $ps_template = str_ireplace($va_unit['directive'], $vs_unit_tag, $ps_template);
        $vn_unit_id++;
    }
    $va_tags = array();
    if (preg_match_all("!\\^([A-Za-z0-9_\\.]+[%]{1}[^ \\^\t\r\n\"\\'<>\\(\\)\\{\\}\\/\\[\\]]*|[A-Za-z0-9_\\.]+)!", $ps_template, $va_matches)) {
        $va_tags = $va_matches[1];
    }
    $qr_res = $t_instance->makeSearchResult($ps_tablename, $pa_row_ids);
    if (!$qr_res) {
        return '';
    }
    $va_proc_templates = array();
    $vn_i = 0;
    // Parse template
    $o_dom->loadHTML('<?xml encoding="utf-8">' . $ps_template);
    libxml_clear_errors();
    $o_if = $o_dom->getElementsByTagName("if");
    // if
    $o_ifdefs = $o_dom->getElementsByTagName("ifdef");
    // if defined
    $o_ifnotdefs = $o_dom->getElementsByTagName("ifnotdef");
    // if not defined
    $o_mores = $o_dom->getElementsByTagName("more");
    // more tags – content suppressed if there are no defined values following the tag pair
    $o_betweens = $o_dom->getElementsByTagName("between");
    // between tags – content suppressed if there are not defined values on both sides of the tag pair
    $o_options = $o_dom->getElementsByTagName("options");
    $va_if = array();
    foreach ($o_if as $o_if) {
        if (!$o_if) {
            continue;
        }
        $vs_html = $o_dom->saveXML($o_if);
        $vs_content = preg_replace("!^<[^\\>]+>!", "", $vs_html);
        $vs_content = preg_replace("!<[^\\>]+>\$!", "", $vs_content);
        //
        // Hack to get around DomDocument trimming leading spaces off of parsed HTML
        // We try here to detect the trimming and shunt those spaces back where they belong. Seems to work :-)
        //
        if (preg_match("!([ ]+){$vs_content}!", $ps_template, $va_match_spaces)) {
            $vs_html = preg_replace("!{$vs_content}!", $va_match_spaces[1] . $vs_content, $vs_html);
            $vs_content = $va_match_spaces[1] . $vs_content;
        }
        $va_if[] = array('directive' => $vs_html, 'content' => $vs_content, 'rule' => $vs_rule = (string) $o_if->getAttribute('rule'));
        //$vs_code = preg_replace("!%(.*)$!", '', $vs_code);
        //if (!in_array($vs_code, $va_tags)) { $va_tags[] = $vs_code; }
    }
    //print_r($va_if);
    $va_ifdefs = array();
    foreach ($o_ifdefs as $o_ifdef) {
        if (!$o_ifdef) {
            continue;
        }
        $vs_html = $o_dom->saveXML($o_ifdef);
        $vs_content = preg_replace("!^<[^\\>]+>!", "", $vs_html);
        $vs_content = preg_replace("!<[^\\>]+>\$!", "", $vs_content);
        //
        // Hack to get around DomDocument trimming leading spaces off of parsed HTML
        // We try here to detect the trimming and shunt those spaces back where they belong. Seems to work :-)
        //
        if (preg_match("!([ ]+){$vs_content}!", $ps_template, $va_match_spaces)) {
            $vs_html = preg_replace("!{$vs_content}!", $va_match_spaces[1] . $vs_content, $vs_html);
            $vs_content = $va_match_spaces[1] . $vs_content;
        }
        $va_ifdefs[$vs_code = (string) $o_ifdef->getAttribute('code')][] = array('directive' => $vs_html, 'content' => $vs_content);
        $vs_code = preg_replace("!%(.*)\$!", '', $vs_code);
        if (!in_array($vs_code, $va_tags)) {
            $va_tags[] = $vs_code;
        }
    }
    $va_ifnotdefs = array();
    foreach ($o_ifnotdefs as $o_ifnotdef) {
        if (!$o_ifnotdef) {
            continue;
        }
        $vs_html = $o_dom->saveXML($o_ifnotdef);
        $vs_content = preg_replace("!^<[^\\>]+>!", "", $vs_html);
        $vs_content = preg_replace("!<[^\\>]+>\$!", "", $vs_content);
        //
        // Hack to get around DomDocument trimming leading spaces off of parsed HTML
        // We try here to detect the trimming and shunt those spaces back where they belong. Seems to work :-)
        //
        if (preg_match("!([ ]+){$vs_content}!", $ps_template, $va_match_spaces)) {
            $vs_html = preg_replace("!{$vs_content}!", $va_match_spaces[1] . $vs_content, $vs_html);
            $vs_content = $va_match_spaces[1] . $vs_content;
        }
        $va_ifnotdefs[$vs_code = (string) $o_ifnotdef->getAttribute('code')][] = array('directive' => $vs_html, 'content' => $vs_content);
        $vs_code = preg_replace("!%(.*)\$!", '', $vs_code);
        if (!in_array($vs_code, $va_tags)) {
            $va_tags[] = $vs_code;
        }
    }
    $va_mores = array();
    foreach ($o_mores as $o_more) {
        if (!$o_more) {
            continue;
        }
        $vs_html = $o_dom->saveXML($o_more);
        $vs_content = preg_replace("!^<[^\\>]+>!", "", $vs_html);
        $vs_content = preg_replace("!<[^\\>]+>\$!", "", $vs_content);
        $va_mores[] = array('directive' => $vs_html, 'content' => $vs_content);
    }
    $va_betweens = array();
    foreach ($o_betweens as $o_between) {
        if (!$o_between) {
            continue;
        }
        $vs_html = $o_dom->saveXML($o_between);
        $vs_content = preg_replace("!^<[^\\>]+>!", "", $vs_html);
        $vs_content = preg_replace("!<[^\\>]+>\$!", "", $vs_content);
        $va_betweens[] = array('directive' => $vs_html, 'content' => $vs_content);
    }
    $va_resolve_links_using_row_ids = array();
    $va_tag_val_list = $va_defined_tag_list = array();
    while ($qr_res->nextHit()) {
        $vs_pk_val = $qr_res->get($vs_pk);
        $va_proc_templates[$vn_i] = preg_replace("![\r\n\t]+!", "", html_entity_decode($ps_template));
        // DomDocument messes with white space and encodes entities so we normalize things here so the str_ireplace() replacement below doesn't fail
        foreach ($va_units as $va_unit) {
            if (!$va_unit['content']) {
                continue;
            }
            $va_relative_to_tmp = $va_unit['relativeTo'] ? explode(".", $va_unit['relativeTo']) : array($ps_tablename);
            if (!($t_instance = $o_dm->getInstanceByTableName($va_relative_to_tmp[0], true))) {
                continue;
            }
            $vs_unit_delimiter = caGetOption('delimiter', $va_unit, '; ');
            // additional get options for pulling related records
            $va_get_options = array('returnAsArray' => true);
            if ($va_unit['restrictToTypes'] && strlen($va_unit['restrictToTypes']) > 0) {
                $va_get_options['restrictToTypes'] = explode('|', $va_unit['restrictToTypes']);
            }
            if ($va_unit['restrictToRelationshipTypes'] && strlen($va_unit['restrictToRelationshipTypes']) > 0) {
                $va_get_options['restrictToRelationshipTypes'] = explode('|', $va_unit['restrictToRelationshipTypes']);
            }
            if (sizeof($va_relative_to_tmp) == 1 && $va_relative_to_tmp[0] == $ps_tablename || sizeof($va_relative_to_tmp) >= 1 && $va_relative_to_tmp[0] == $ps_tablename && $va_relative_to_tmp[1] != 'related') {
                switch (strtolower($va_relative_to_tmp[1])) {
                    case 'hierarchy':
                        $va_relative_ids = $qr_res->get($t_instance->tableName() . ".hierarchy." . $t_instance->primaryKey(), $va_get_options);
                        $va_relative_ids = array_values($va_relative_ids);
                        break;
                    case 'parent':
                        $va_relative_ids = $qr_res->get($t_instance->tableName() . ".parent." . $t_instance->primaryKey(), $va_get_options);
                        $va_relative_ids = array_values($va_relative_ids);
                        break;
                    case 'children':
                        $va_relative_ids = $qr_res->get($t_instance->tableName() . ".children." . $t_instance->primaryKey(), $va_get_options);
                        $va_relative_ids = array_values($va_relative_ids);
                        break;
                    default:
                        $va_relative_ids = $pa_row_ids;
                        break;
                }
            } else {
                switch (strtolower($va_relative_to_tmp[1])) {
                    case 'hierarchy':
                        $va_relative_ids = $qr_res->get($t_instance->tableName() . ".hierarchy." . $t_instance->primaryKey(), $va_get_options);
                        $va_relative_ids = array_values($va_relative_ids);
                        break;
                    case 'parent':
                        $va_relative_ids = $qr_res->get($t_instance->tableName() . ".parent." . $t_instance->primaryKey(), $va_get_options);
                        $va_relative_ids = array_values($va_relative_ids);
                        break;
                    case 'children':
                        $va_relative_ids = $qr_res->get($t_instance->tableName() . ".children." . $t_instance->primaryKey(), $va_get_options);
                        $va_relative_ids = array_values($va_relative_ids);
                        break;
                    case 'related':
                        $va_relative_ids = $qr_res->get($t_instance->tableName() . ".related." . $t_instance->primaryKey(), $va_get_options);
                        $va_relative_ids = array_values($va_relative_ids);
                        break;
                    default:
                        $va_relative_ids = $qr_res->get($t_instance->tableName() . "." . $t_instance->primaryKey(), $va_get_options);
                        break;
                }
            }
            $vs_tmpl_val = caProcessTemplateForIDs($va_unit['content'], $va_relative_to_tmp[0], $va_relative_ids, array_merge($pa_options, array('restrictToTypes' => caGetOption('restrictToTypes', $va_get_options, null), 'restrictToRelationshipTypes' => caGetOption('restrictToRelationshipTypes', $va_get_options, null)), array('delimiter' => $vs_unit_delimiter, 'resolveLinksUsing' => null)));
            $va_proc_templates[$vn_i] = str_ireplace($va_unit['tag'], $vs_tmpl_val, $va_proc_templates[$vn_i]);
        }
        if (!strlen(trim($va_proc_templates[$vn_i]))) {
            $va_proc_templates[$vn_i] = null;
        }
        if (!sizeof($va_tags)) {
            continue;
        }
        // if there are no tags in the template then we don't need to process further
        if ($ps_resolve_links_using != $ps_tablename) {
            $va_resolve_links_using_row_ids[] = $qr_res->get("{$ps_resolve_links_using}.{$vs_resolve_links_using_pk}");
        }
        $va_tag_val_list[$vn_i] = array();
        $va_defined_tag_list[$vn_i] = array();
        $va_tag_opts = array();
        foreach ($va_tags as $vs_tag) {
            $va_tmp = explode('.', $vs_tag);
            $vs_last_element = $va_tmp[sizeof($va_tmp) - 1];
            $va_tag_opt_tmp = explode("%", $vs_last_element);
            if (sizeof($va_tag_opt_tmp) > 1) {
                $vs_tag_bit = array_shift($va_tag_opt_tmp);
                // get rid of getspec
                foreach ($va_tag_opt_tmp as $vs_tag_opt_raw) {
                    $va_tag_tmp = explode("=", $vs_tag_opt_raw);
                    $va_tag_tmp[0] = trim($va_tag_tmp[0]);
                    $va_tag_tmp[1] = trim($va_tag_tmp[1]);
                    if (in_array($va_tag_tmp[0], array('delimiter', 'hierarchicalDelimiter'))) {
                        $va_tag_tmp[1] = str_replace("_", " ", $va_tag_tmp[1]);
                    }
                    if (sizeof($va_tag_line_tmp = explode("|", $va_tag_tmp[1])) > 1) {
                        $va_tag_opts[trim($va_tag_tmp[0])] = $va_tag_line_tmp;
                    } else {
                        $va_tag_opts[trim($va_tag_tmp[0])] = $va_tag_tmp[1];
                    }
                }
                $va_tmp[sizeof($va_tmp) - 1] = $vs_tag_bit;
                // remove option from tag-part array
                $vs_tag_proc = join(".", $va_tmp);
                $va_proc_templates[$vn_i] = str_replace($vs_tag, $vs_tag_proc, $va_proc_templates[$vn_i]);
                $vs_tag = $vs_tag_proc;
            }
            $pa_options = array_merge($pa_options, $va_tag_opts);
            // Default label tag to hierarchies
            if (isset($pa_options['showHierarchicalLabels']) && $pa_options['showHierarchicalLabels'] && $vs_tag == 'label') {
                unset($va_related_values[$vs_pk_val][$vs_tag]);
                unset($va_relationship_values[$vs_pk_val][$vs_tag]);
                $va_tmp = array($ps_tablename, 'hierarchy', 'preferred_labels');
            }
            if (!isset($va_relationship_values[$vs_pk_val])) {
                $va_relationship_values[$vs_pk_val] = array(0 => null);
            }
            foreach ($va_relationship_values[$vs_pk_val] as $vn_relation_id => $va_relationship_value_array) {
                $vb_is_related = false;
                $va_val = null;
                if (isset($va_relationship_value_array[$vs_tag]) && !(isset($pa_options['showHierarchicalLabels']) && $pa_options['showHierarchicalLabels'] && $vs_tag == 'label')) {
                    $va_val = array($vs_val = $va_relationship_value_array[$vs_tag]);
                } else {
                    if (isset($va_related_values[$vs_pk_val][$vs_tag])) {
                        $va_val = array($vs_val = $va_related_values[$vs_pk_val][$vs_tag]);
                    } else {
                        //
                        // see if this is a reference to a related table
                        //
                        if ($ps_tablename != $va_tmp[0] && ($t_tmp = $o_dm->getInstanceByTableName($va_tmp[0], true))) {
                            // if the part of the tag before a "." (or the tag itself if there are no periods) is a related table then try to fetch it as related to the current record
                            if (isset($pa_options['placeholderPrefix']) && $pa_options['placeholderPrefix'] && $va_tmp[0] != $pa_options['placeholderPrefix'] && sizeof($va_tmp) == 1) {
                                $vs_get_spec = array_shift($va_tmp) . "." . $pa_options['placeholderPrefix'];
                                if (sizeof($va_tmp) > 0) {
                                    $vs_get_spec .= "." . join(".", $va_tmp);
                                }
                            } else {
                                $vs_get_spec = $vs_tag;
                            }
                            $va_spec_bits = explode(".", $vs_get_spec);
                            if (sizeof($va_spec_bits) == 1 && $o_dm->getTableNum($va_spec_bits[0])) {
                                $vs_get_spec .= ".preferred_labels";
                            }
                            $va_additional_options = array('returnAsArray' => true);
                            $vs_hierarchy_name = null;
                            if (in_array($va_spec_bits[1], array('hierarchy', '_hierarchyName'))) {
                                $t_rel = $o_dm->getInstanceByTableName($va_spec_bits[0], true);
                                switch ($t_rel->getProperty('HIERARCHY_TYPE')) {
                                    case __CA_HIER_TYPE_SIMPLE_MONO__:
                                        $va_additional_options['removeFirstItems'] = 1;
                                        break;
                                    case __CA_HIER_TYPE_MULTI_MONO__:
                                        $vs_hierarchy_name = $t_rel->getHierarchyName($qr_res->get($t_rel->tableName() . "." . $t_rel->primaryKey()));
                                        $va_additional_options['removeFirstItems'] = 1;
                                        break;
                                }
                            }
                            if ($va_spec_bits[1] != '_hierarchyName') {
                                $va_val = $qr_res->get($vs_get_spec, array_merge($pa_options, $va_additional_options, array("returnAsArray" => true, "returnAllLocales" => true)));
                            } else {
                                $va_val = array();
                            }
                            if (is_array($va_primary_ids) && isset($va_primary_ids[$va_spec_bits[0]]) && is_array($va_primary_ids[$va_spec_bits[0]])) {
                                foreach ($va_primary_ids[$va_spec_bits[0]] as $vn_primary_id) {
                                    unset($va_val[$vn_primary_id]);
                                }
                            }
                            $va_val = caExtractValuesByUserLocale($va_val);
                            $va_val_tmp = array();
                            foreach ($va_val as $vn_d => $va_vals) {
                                $va_val_tmp = array_merge($va_val_tmp, $va_vals);
                            }
                            $va_val = $va_val_tmp;
                            $va_val_proc = array();
                            switch ($va_spec_bits[1]) {
                                case '_hierarchyName':
                                    if ($vs_hierarchy_name) {
                                        $va_val_proc[] = $vs_hierarchy_name;
                                    }
                                    break;
                                case 'hierarchy':
                                    if (is_array($va_val) && sizeof($va_val) > 0) {
                                        if ($vs_hierarchy_name) {
                                            array_unshift($va_val, $vs_hierarchy_name);
                                        }
                                        foreach ($va_val as $va_hier) {
                                            if (!is_array($va_hier)) {
                                                $va_hier = array($va_hier);
                                            }
                                            $va_val_proc[] = join(caGetOption("delimiter", $va_tag_opts, "; "), $va_hier);
                                        }
                                    }
                                    break;
                                case 'parent':
                                    if (is_array($va_val)) {
                                        foreach ($va_val as $va_label) {
                                            $va_val_proc[] = $va_label['name'];
                                        }
                                    }
                                    break;
                                default:
                                    $vs_terminal = end($va_spec_bits);
                                    foreach ($va_val as $va_val_container) {
                                        if (!is_array($va_val_container)) {
                                            if ($va_val_container) {
                                                $va_val_proc[] = $va_val_container;
                                            }
                                            continue;
                                        }
                                        $va_val_proc[] = $va_val_container[$vs_terminal];
                                    }
                                    break;
                            }
                            $va_val = $va_val_proc;
                            $vb_is_related = true;
                        } else {
                            //
                            // Handle non-related gets
                            //
                            // Default specifiers that end with a modifier to preferred labels
                            if (sizeof($va_tmp) == 2 && in_array($va_tmp[1], array('hierarchy', 'children', 'parent', 'related'))) {
                                array_push($va_tmp, 'preferred_labels');
                            }
                            $vs_hierarchy_name = null;
                            if (in_array($va_tmp[1], array('hierarchy', '_hierarchyName'))) {
                                switch ($t_instance->getProperty('HIERARCHY_TYPE')) {
                                    case __CA_HIER_TYPE_SIMPLE_MONO__:
                                        $va_additional_options['removeFirstItems'] = 1;
                                        break;
                                    case __CA_HIER_TYPE_MULTI_MONO__:
                                        $vs_hierarchy_name = $t_instance->getHierarchyName($qr_res->get($t_instance->tableName() . "." . $t_instance->primaryKey()));
                                        $va_additional_options['removeFirstItems'] = 1;
                                        break;
                                }
                            }
                            if ($va_tmp[0] == $ps_tablename) {
                                array_shift($va_tmp);
                            }
                            // get rid of primary table if it's in the field spec
                            if (!sizeof($va_tmp) && $t_instance->getProperty('LABEL_TABLE_NAME')) {
                                $va_tmp[] = "preferred_labels";
                            }
                            if (isset($pa_options['showHierarchicalLabels']) && $pa_options['showHierarchicalLabels']) {
                                if (!in_array($va_tmp[0], array('hierarchy', 'children', 'parent', 'related')) && $va_tmp[1] == 'preferred_labels') {
                                    array_unshift($va_tmp, 'hierarchy');
                                }
                            }
                            if (isset($pa_options['placeholderPrefix']) && $pa_options['placeholderPrefix'] && $va_tmp[0] != $pa_options['placeholderPrefix']) {
                                array_splice($va_tmp, -1, 0, $pa_options['placeholderPrefix']);
                            }
                            $vs_get_spec = "{$ps_tablename}." . join(".", $va_tmp);
                            if (in_array($va_tmp[0], array('parent'))) {
                                $va_val[] = $qr_res->get($vs_get_spec, array_merge($pa_options, $va_tag_opts, array('returnAsArray' => false)));
                            } else {
                                $va_val_tmp = $qr_res->get($vs_get_spec, array_merge($pa_options, $va_tag_opts, array('returnAsArray' => true)));
                                $va_val = array();
                                if (is_array($va_val_tmp)) {
                                    foreach ($va_val_tmp as $vn_attr_id => $vm_attr_val) {
                                        if (is_array($vm_attr_val)) {
                                            if ($va_tmp[0] == 'hierarchy') {
                                                if ($vs_hierarchy_name) {
                                                    array_shift($vm_attr_val);
                                                    // remove root
                                                    array_unshift($vm_attr_val, $vs_hierarchy_name);
                                                    // replace with hierarchy name
                                                }
                                                if ($vs_delimiter_tmp = caGetOption('hierarchicalDelimiter', $va_tag_opts)) {
                                                    $vs_tag_val_delimiter = $vs_delimiter_tmp;
                                                } elseif ($vs_delimiter_tmp = caGetOption('hierarchicalDelimiter', $pa_options)) {
                                                    $vs_tag_val_delimiter = $vs_delimiter_tmp;
                                                } else {
                                                    $vs_tag_val_delimiter = caGetOption('delimiter', $va_tag_opts, $vs_delimiter);
                                                }
                                            } else {
                                                $vs_tag_val_delimiter = caGetOption('delimiter', $va_tag_opts, $vs_delimiter);
                                            }
                                            $va_val[] = join($vs_tag_val_delimiter, $vm_attr_val);
                                        } else {
                                            $va_val[] = $vm_attr_val;
                                        }
                                    }
                                }
                                if (sizeof($va_val) > 1 && $va_tmp[0] == 'hierarchy') {
                                    $vs_tag_val_delimiter = caGetOption('delimiter', $va_tag_opts, $vs_delimiter);
                                    $va_val = array(join($vs_tag_val_delimiter, $va_val));
                                }
                            }
                        }
                    }
                }
                if (is_array($va_val)) {
                    if (sizeof($va_val) > 0) {
                        foreach ($va_val as $vn_j => $vs_val) {
                            if (!is_array($va_tag_val_list[$vn_i][$vn_j][$vs_tag]) || !in_array($vs_val, $va_tag_val_list[$vn_i][$vn_j][$vs_tag])) {
                                $va_tag_val_list[$vn_i][$vn_j][$vs_tag][] = $vs_val;
                                if (is_array($vs_val) && sizeof($vs_val) || strlen($vs_val) > 0) {
                                    $va_defined_tag_list[$vn_i][$vn_j][$vs_tag] = true;
                                }
                            }
                        }
                    } else {
                        $va_tag_val_list[$vn_i][0][$vs_tag] = null;
                        $va_defined_tag_list[$vn_i][0][$vs_tag] = false;
                    }
                }
            }
        }
        $vn_i++;
    }
    foreach ($va_tag_val_list as $vn_i => $va_tags_list) {
        $va_acc = array();
        foreach ($va_tags_list as $vn_j => $va_tags) {
            $va_tag_list = array();
            $va_pt_vals = array();
            $vs_template = $va_proc_templates[$vn_i];
            // Process <if>
            foreach ($va_if as $va_def_con) {
                if (ExpressionParser::evaluate($va_def_con['rule'], $va_tags)) {
                    $vs_template = str_replace($va_def_con['directive'], $va_def_con['content'], $vs_template);
                } else {
                    $vs_template = str_replace($va_def_con['directive'], '', $vs_template);
                }
            }
            // Process <ifdef> (IF DEFined)
            foreach ($va_ifdefs as $vs_code => $va_def_con) {
                if (strpos($vs_code, "|") !== false) {
                    $vs_bool = 'OR';
                    $va_tag_list = explode("|", $vs_code);
                    $vb_output = false;
                } else {
                    $vs_bool = 'AND';
                    $va_tag_list = explode(",", $vs_code);
                    $vb_output = true;
                }
                foreach ($va_tag_list as $vs_tag_to_test) {
                    $vb_value_is_set = (bool) (isset($va_tags[$vs_tag_to_test]) && sizeof($va_tags[$vs_tag_to_test]) > 1 || sizeof($va_tags[$vs_tag_to_test]) == 1 && strlen($va_tags[$vs_tag_to_test][0]) > 0);
                    switch ($vs_bool) {
                        case 'OR':
                            if ($vb_value_is_set) {
                                $vb_output = true;
                                break 2;
                            }
                            // any must be defined; if any is defined output
                            break;
                        case 'AND':
                        default:
                            if (!$vb_value_is_set) {
                                $vb_output = false;
                                break 2;
                            }
                            // all must be defined; if any is not defined don't output
                            break;
                    }
                }
                foreach ($va_def_con as $va_ifdef) {
                    if ($vb_output) {
                        $vs_template = str_replace($va_ifdef['directive'], $va_ifdef['content'], $vs_template);
                    } else {
                        $vs_template = str_replace($va_ifdef['directive'], '', $vs_template);
                    }
                }
            }
            // Process <ifnotdef> (IF NOT DEFined)
            foreach ($va_ifnotdefs as $vs_code => $va_notdef_con) {
                if (strpos($vs_code, "|") !== false) {
                    $vs_bool = 'OR';
                    $va_tag_list = explode("|", $vs_code);
                    $vb_output = false;
                } else {
                    $vs_bool = 'AND';
                    $va_tag_list = explode(",", $vs_code);
                    $vb_output = true;
                }
                $vb_output = true;
                foreach ($va_tag_list as $vs_tag_to_test) {
                    $vb_value_is_set = (bool) (isset($va_tags[$vs_tag_to_test]) && sizeof($va_tags[$vs_tag_to_test]) > 1 || sizeof($va_tags[$vs_tag_to_test]) == 1 && strlen($va_tags[$vs_tag_to_test][0]) > 0);
                    switch ($vs_bool) {
                        case 'OR':
                            if (!$vb_value_is_set) {
                                $vb_output = true;
                                break 2;
                            }
                            // any must be not defined; if anything is not set output
                            break;
                        case 'AND':
                        default:
                            if ($vb_value_is_set) {
                                $vb_output = false;
                                break 2;
                            }
                            // all must be not defined; if anything is set don't output
                            break;
                    }
                }
                foreach ($va_notdef_con as $va_ifnotdef) {
                    if ($vb_output) {
                        $vs_template = str_replace($va_ifnotdef['directive'], $va_ifnotdef['content'], $vs_template);
                    } else {
                        $vs_template = str_replace($va_ifnotdef['directive'], '', $vs_template);
                    }
                }
            }
            // Process <more> tags
            foreach ($va_mores as $vn_more_index => $va_more) {
                if (($vn_pos = strpos($vs_template, $va_more['directive'])) !== false) {
                    if (isset($va_mores[$vn_more_index + 1]) && ($vn_next_more_pos = strpos(substr($vs_template, $vn_pos + strlen($va_more['directive'])), $va_mores[$vn_more_index + 1]['directive'])) !== false) {
                        $vn_next_more_pos += $vn_pos;
                        $vs_partial_template = substr($vs_template, $vn_pos + strlen($va_more['directive']), $vn_next_more_pos - $vn_pos);
                    } else {
                        $vs_partial_template = substr($vs_template, $vn_pos + strlen($va_more['directive']));
                    }
                    $vb_output = false;
                    foreach (array_keys($va_defined_tag_list[$vn_i][$vn_j]) as $vs_defined_tag) {
                        if (strpos($vs_partial_template, $vs_defined_tag) !== false) {
                            // content is defined
                            $vb_output = true;
                            break;
                        }
                    }
                    if ($vb_output) {
                        $vs_template = preg_replace('!' . $va_more['directive'] . '!', $va_more['content'], $vs_template, 1);
                    } else {
                        $vs_template = preg_replace('!' . $va_more['directive'] . '!', '', $vs_template, 1);
                    }
                }
            }
            // Process <between> tags - text to be output if it is between two defined values
            $va_between_positions = array();
            foreach ($va_betweens as $vn_between_index => $va_between) {
                $vb_output_before = $vb_output_after = false;
                if (($vn_cur_pos = strpos($vs_template, $va_between['directive'])) !== false) {
                    $va_between_positions[$vn_between_index] = $vn_cur_pos;
                    // Get parts of template before tag and after tag
                    $vs_partial_template_before = substr($vs_template, 0, $vn_cur_pos);
                    $vs_partial_template_after = substr($vs_template, $vn_cur_pos + strlen($va_between['directive']));
                    // Only get the template between our current position and the next <between> tag
                    if (isset($va_betweens[$vn_between_index + 1]) && ($vn_after_pos_relative = strpos($vs_partial_template_after, $va_betweens[$vn_between_index + 1]['directive'])) !== false) {
                        $vs_partial_template_after = substr($vs_partial_template_after, 0, $vn_after_pos_relative);
                    }
                    // Check for defined value before and after tag
                    foreach (array_keys($va_defined_tag_list[$vn_i][$vn_j]) as $vs_defined_tag) {
                        if (strpos($vs_partial_template_before, $vs_defined_tag) !== false) {
                            // content is defined
                            $vb_output_after = true;
                        }
                        if (strpos($vs_partial_template_after, $vs_defined_tag) !== false) {
                            // content is defined
                            $vb_output_before = true;
                            break;
                        }
                        if ($vb_output_before && $vb_output_after) {
                            break;
                        }
                    }
                }
                if ($vb_output_before && $vb_output_after) {
                    $vs_template = preg_replace('!' . $va_between['directive'] . '!', $va_between['content'], $vs_template, 1);
                } else {
                    $vs_template = preg_replace('!' . $va_between['directive'] . '!', '', $vs_template, 1);
                }
            }
            //
            // Need to sort tags by length descending (longest first)
            // so that when we go to substitute and you have a tag followed by itself with a suffix
            // (ex. ^measurements and ^measurements2) we don't substitute both for the stub (ex. ^measurements)
            //
            $va_tags_tmp = array_keys($va_tags);
            usort($va_tags_tmp, function ($a, $b) {
                return strlen($b) - strlen($a);
            });
            $vs_pt = $vs_template;
            foreach ($va_tags_tmp as $vs_tag) {
                $vs_pt = str_replace('^' . $vs_tag, is_array($va_tags[$vs_tag]) ? join(" | ", $va_tags[$vs_tag]) : $va_tags[$vs_tag], $vs_pt);
            }
            $va_pt_vals[] = $vs_pt;
            $va_acc[] = join(isset($pa_options['delimiter']) ? $pa_options['delimiter'] : $vs_delimiter, $va_pt_vals);
        }
        $va_proc_templates[$vn_i] = join($vs_delimiter, $va_acc);
    }
    foreach ($va_proc_templates as $vn_i => $vs_template) {
        if (!strlen(trim($vs_template))) {
            unset($va_proc_templates[$vn_i]);
        }
    }
    // Transform links
    $va_proc_templates = caCreateLinksFromText($va_proc_templates, $ps_resolve_links_using, $ps_resolve_links_using != $ps_tablename ? $va_resolve_links_using_row_ids : $pa_row_ids, null, null, $pa_options);
    // Kill any lingering tags (just in case)
    foreach ($va_proc_templates as $vn_i => $vs_proc_template) {
        $va_proc_templates[$vn_i] = preg_replace("!\\^([A-Za-z0-9_\\.]+[%]{1}[^ \\^\t\r\n\"\\'<>\\(\\)\\{\\}\\/\\[\\]]*|[A-Za-z0-9_\\.]+)!", "", $vs_proc_template);
    }
    if ($pb_return_as_array) {
        return $va_proc_templates;
    }
    return join($vs_delimiter, $va_proc_templates);
}
Example #5
0
 /**
  * Processes single exporter item for a given record
  *
  * @param int $pn_item_id Primary of exporter item
  * @param int $pn_table_num Table num of item to export
  * @param int $pn_record_id Primary key value of item to export
  * @param array $pa_options
  *		ignoreContext = don't switch context even though context may be set for current item
  *		relationship_type_id, relationship_type_code, relationship_typename =
  *			if this export is a sub-export (context-switch), we have no way of knowing the relationship
  *			to the 'parent' element in the export, so there has to be a means to pass it down to make it accessable
  *		logger = KLogger instance to use for logging. This option is mandatory!
  * @return array Item info
  */
 public function processExporterItem($pn_item_id, $pn_table_num, $pn_record_id, $pa_options = array())
 {
     $o_log = caGetOption('logger', $pa_options);
     // always set by exportRecord()
     $vb_ignore_context = caGetOption('ignoreContext', $pa_options);
     $o_log->logInfo(_t("Export mapping processor called with parameters [exporter_item_id:%1 table_num:%2 record_id:%3]", $pn_item_id, $pn_table_num, $pn_record_id));
     $t_exporter_item = ca_data_exporters::loadExporterItemByID($pn_item_id);
     $t_instance = ca_data_exporters::loadInstanceByID($pn_record_id, $pn_table_num);
     // switch context to a different set of records if necessary and repeat current exporter item for all those selected records
     // (e.g. hierarchy children or related items in another table, restricted by types or relationship types)
     if (!$vb_ignore_context && ($vs_context = $t_exporter_item->getSetting('context'))) {
         $va_restrict_to_types = $t_exporter_item->getSetting('restrictToTypes');
         $va_restrict_to_rel_types = $t_exporter_item->getSetting('restrictToRelationshipTypes');
         $va_check_access = $t_exporter_item->getSetting('checkAccess');
         $vn_new_table_num = $this->getAppDatamodel()->getTableNum($vs_context);
         $vb_context_is_related_table = false;
         if ($vn_new_table_num) {
             // switch to new table
             $vs_key = $this->getAppDatamodel()->getTablePrimaryKeyName($vs_context);
         } else {
             // this table, i.e. hierarchy context switch
             $vs_key = $t_instance->primaryKey();
             $vn_new_table_num = $pn_table_num;
         }
         $o_log->logInfo(_t("Initiating context switch to '%1' for mapping ID %2 and record ID %3. The processor now tries to find matching records for the switch and calls himself for each of those items.", $vs_context, $pn_item_id, $pn_record_id));
         switch ($vs_context) {
             case 'children':
                 $va_related = $t_instance->getHierarchyChildren();
                 break;
             case 'parent':
                 $va_related = array();
                 if ($vs_parent_id_fld = $t_instance->getProperty("HIERARCHY_PARENT_ID_FLD")) {
                     $va_related[] = array($vs_key => $t_instance->get($vs_parent_id_fld));
                 }
                 break;
             case 'ancestors':
                 $va_parents = $t_instance->getHierarchyAncestors(null, array('idsOnly' => true));
                 $va_related = array();
                 foreach (array_unique($va_parents) as $vn_pk) {
                     $va_related[] = array($vs_key => intval($vn_pk));
                 }
                 break;
             case 'ca_sets':
                 $t_set = new ca_sets();
                 $va_set_options = array();
                 if (isset($va_restrict_to_types[0])) {
                     // the utility used below doesn't support passing multiple types so we just pass the first.
                     // this should be enough for 99.99% of the actual use cases anyway
                     $va_set_options['setType'] = $va_restrict_to_types[0];
                 }
                 $va_set_options['checkAccess'] = $va_check_access;
                 $va_set_options['setIDsOnly'] = true;
                 $va_set_ids = $t_set->getSetsForItem($pn_table_num, $t_instance->getPrimaryKey(), $va_set_options);
                 $va_related = array();
                 foreach (array_unique($va_set_ids) as $vn_pk) {
                     $va_related[] = array($vs_key => intval($vn_pk));
                 }
                 break;
             default:
                 // plain old related table
                 if ($vn_new_table_num) {
                     $va_related = $t_instance->getRelatedItems($vs_context, array('restrictToTypes' => $va_restrict_to_types, 'restrictToRelationshipTypes' => $va_restrict_to_rel_types, 'checkAccess' => $va_check_access));
                     $vb_context_is_related_table = true;
                 } else {
                     return array();
                 }
                 break;
         }
         $va_info = array();
         if (is_array($va_related)) {
             $o_log->logDebug(_t("The current mapping will now be repreated for these items: %1", print_r($va_related, true)));
             foreach ($va_related as $va_rel) {
                 // if we're dealing with a related table, pass on some info the relationship type to the context-switched invocation of processExporterItem(),
                 // because we can't access that information from the related item simply because we don't exactly know where the call originated
                 if ($vb_context_is_related_table) {
                     $pa_options['relationship_typename'] = $va_rel['relationship_typename'];
                     $pa_options['relationship_type_code'] = $va_rel['relationship_type_code'];
                     $pa_options['relationship_type_id'] = $va_rel['relationship_type_id'];
                 }
                 $va_rel_export = $this->processExporterItem($pn_item_id, $vn_new_table_num, $va_rel[$vs_key], array_merge(array('ignoreContext' => true), $pa_options));
                 $va_info = array_merge($va_info, $va_rel_export);
             }
         } else {
             $o_log->logDebug(_t("No matching related items found for last context switch"));
         }
         return $va_info;
     }
     // end switch context
     // Don't prevent context switches for children of context-switched exporter items. This way you can
     // build cascades for jobs like exporting objects related to the creator of the record in question.
     unset($pa_options['ignoreContext']);
     $va_item_info = array();
     $vs_source = $t_exporter_item->get('source');
     $vs_element = $t_exporter_item->get('element');
     $vb_repeat = $t_exporter_item->getSetting('repeat_element_for_multiple_values');
     // if omitIfEmpty is set and get() returns nothing, we ignore this exporter item and all children
     if ($vs_omit_if_empty = $t_exporter_item->getSetting('omitIfEmpty')) {
         if (!(strlen($t_instance->get($vs_omit_if_empty)) > 0)) {
             return array();
         }
     }
     // always return URL for export, not an HTML tag
     $va_get_options = array('returnURL' => true);
     if ($t_exporter_item->getSetting('convertCodesToDisplayText')) {
         $va_get_options['convertCodesToDisplayText'] = true;
     }
     if ($t_exporter_item->getSetting('returnIdno')) {
         $va_get_options['returnIdno'] = true;
     }
     if ($vs_delimiter = $t_exporter_item->getSetting("delimiter")) {
         $va_get_options['delimiter'] = $vs_delimiter;
     }
     if ($vs_template = $t_exporter_item->getSetting('template')) {
         $va_get_options['template'] = $vs_template;
     }
     if ($vs_locale = $t_exporter_item->getSetting('locale')) {
         // the global UI locale for some reason has a higher priority
         // than the locale setting in BaseModelWithAttributes::get
         // which is why we unset it here and restore it later
         global $g_ui_locale;
         $vs_old_ui_locale = $g_ui_locale;
         $g_ui_locale = null;
         $va_get_options['locale'] = $vs_locale;
     }
     if ($vs_source) {
         $o_log->logDebug(_t("Source for current mapping is %1", $vs_source));
         $va_matches = array();
         // CONSTANT value
         if (preg_match("/^_CONSTANT_:(.*)\$/", $vs_source, $va_matches)) {
             $o_log->logDebug(_t("This is a constant. Value for this mapping is '%1'", trim($va_matches[1])));
             $va_item_info[] = array('text' => trim($va_matches[1]), 'element' => $vs_element);
         } else {
             if (in_array($vs_source, array("relationship_type_id", "relationship_type_code", "relationship_typename"))) {
                 if (isset($pa_options[$vs_source]) && strlen($pa_options[$vs_source]) > 0) {
                     $o_log->logDebug(_t("Source refers to releationship type information. Value for this mapping is '%1'", $pa_options[$vs_source]));
                     $va_item_info[] = array('text' => $pa_options[$vs_source], 'element' => $vs_element);
                 }
             } else {
                 if (!$vb_repeat) {
                     $vs_get = $t_instance->get($vs_source, $va_get_options);
                     $o_log->logDebug(_t("Source is a simple get() for some bundle. Value for this mapping is '%1'", $vs_get));
                     $o_log->logDebug(_t("get() options are: %1", print_r($va_get_options, true)));
                     $va_item_info[] = array('text' => $vs_get, 'element' => $vs_element);
                 } else {
                     // if user wants current element repeated in case of multiple returned values, go ahead and do that
                     $va_get_options['delimiter'] = ';#;';
                     $vs_values = $t_instance->get($vs_source, $va_get_options);
                     $o_log->logDebug(_t("Source is a get() that should be repeated for multiple values. Value for this mapping is '%1'. It includes the custom delimiter ';#;' that is later used to split the value into multiple values.", $vs_values));
                     $o_log->logDebug(_t("get() options are: %1", print_r($va_get_options, true)));
                     $va_tmp = explode(";#;", $vs_values);
                     foreach ($va_tmp as $vs_text) {
                         $va_item_info[] = array('element' => $vs_element, 'text' => $vs_text);
                     }
                 }
             }
         }
     } else {
         if ($vs_template) {
             // templates without source are probably just static text, but you never know
             // -> run them through processor anyways
             $vs_proc_template = caProcessTemplateForIDs($vs_template, $pn_table_num, array($pn_record_id), array());
             $o_log->logDebug(_t("Current mapping has no source but a template '%1'. Value from extracted via template processor is '%2'", $vs_template, $vs_proc_template));
             $va_item_info[] = array('element' => $vs_element, 'text' => $vs_proc_template);
         } else {
             // no source, no template -> probably wrapper
             $o_log->logDebug(_t("Current mapping has no source and no template and is probably an XML/MARC wrapper element"));
             $va_item_info[] = array('element' => $vs_element);
         }
     }
     // reset UI locale if we unset it
     if ($vs_locale) {
         $g_ui_locale = $vs_old_ui_locale;
     }
     $o_log->logDebug(_t("We're now processing other settings like default, prefix, suffix, skipIfExpression, filterByRegExp, maxLength, plugins and replacements for this mapping"));
     $o_log->logDebug(_t("Local data before processing is: %1", print_r($va_item_info, true)));
     // handle other settings and plugin hooks
     $vs_default = $t_exporter_item->getSetting('default');
     $vs_prefix = $t_exporter_item->getSetting('prefix');
     $vs_suffix = $t_exporter_item->getSetting('suffix');
     $vs_regexp = $t_exporter_item->getSetting('filterByRegExp');
     $vn_max_length = $t_exporter_item->getSetting('maxLength');
     $vs_skip_if_expr = $t_exporter_item->getSetting('skipIfExpression');
     $vs_original_values = $t_exporter_item->getSetting('original_values');
     $vs_replacement_values = $t_exporter_item->getSetting('replacement_values');
     $va_replacements = ca_data_exporter_items::getReplacementArray($vs_original_values, $vs_replacement_values);
     foreach ($va_item_info as $vn_key => &$va_item) {
         // if returned value from plugin is null then we skip the item
         if (is_null($va_plugin_item = $this->opo_app_plugin_manager->hookExportItemBeforeSettings(array('instance' => $t_instance, 'exporter_item_instance' => $t_exporter_item, 'export_item' => $va_item)))) {
             continue;
         } else {
             $va_item = $va_plugin_item['export_item'];
         }
         // handle skipIfExpression setting
         if ($vs_skip_if_expr) {
             // Add current value as variable "value", accessible in expressions as ^value
             if (ExpressionParser::evaluate($vs_skip_if_expr, array_merge(array('value' => $va_item['text']), ca_data_exporters::$s_variables))) {
                 unset($va_item_info[$vn_key]);
                 continue;
             }
         }
         // filter by regex (deprecated since you can do the same thing and more with skipIfExpression)
         if (strlen($va_item['text']) > 0 && $vs_regexp) {
             if (!preg_match("!" . $vs_regexp . "!", $va_item['text'])) {
                 unset($va_item_info[$vn_key]);
                 continue;
             }
         }
         // do replacements
         $va_item['text'] = ca_data_exporter_items::replaceText($va_item['text'], $va_replacements);
         // if text is empty, fill in default
         // if text isn't empty, respect prefix and suffix
         if (strlen($va_item['text']) == 0) {
             if ($vs_default) {
                 $va_item['text'] = $vs_default;
             }
         } else {
             if (strlen($vs_prefix) > 0 || strlen($vs_suffix) > 0) {
                 $va_item['text'] = $vs_prefix . $va_item['text'] . $vs_suffix;
             }
         }
         if ($vn_max_length && strlen($va_item['text']) > $vn_max_length) {
             $va_item['text'] = substr($va_item['text'], 0, $vn_max_length) . " ...";
         }
         // if this is a variable, set the value and delete it from the export tree
         $va_matches = array();
         if (preg_match("/^_VARIABLE_:(.*)\$/", $va_item['element'], $va_matches)) {
             ca_data_exporters::$s_variables[$va_matches[1]] = $va_item['text'];
             unset($va_item_info[$vn_key]);
             continue;
         }
         // if returned value is null then we skip the item
         if (is_null($va_plugin_item = $this->opo_app_plugin_manager->hookExportItem(array('instance' => $t_instance, 'exporter_item_instance' => $t_exporter_item, 'export_item' => $va_item)))) {
             continue;
         } else {
             $va_item = $va_plugin_item['export_item'];
         }
     }
     $o_log->logInfo(_t("Extracted data for this mapping is as follows:"));
     foreach ($va_item_info as $va_tmp) {
         $o_log->logInfo(sprintf("    element:%-20s value: %-10s", $va_tmp['element'], $va_tmp['text']));
     }
     $va_children = $t_exporter_item->getHierarchyChildren();
     if (is_array($va_children) && sizeof($va_children) > 0) {
         $o_log->logInfo(_t("Now proceeding to process %1 direct children in the mapping hierarchy", sizeof($va_children)));
         foreach ($va_children as $va_child) {
             foreach ($va_item_info as &$va_info) {
                 $va_child_export = $this->processExporterItem($va_child['item_id'], $pn_table_num, $pn_record_id, $pa_options);
                 $va_info['children'] = array_merge((array) $va_info['children'], $va_child_export);
             }
         }
     }
     return $va_item_info;
 }
/**
 * 
 *
 * @param string $ps_refinery_name
 * @param string $ps_table
 * @param array $pa_parents 
 * @param array $pa_source_data
 * @param array $pa_item
 * @param int $pn_c
 * @param KLogger $o_log
 * 
 * @return int
 */
function caProcessRefineryParents($ps_refinery_name, $ps_table, $pa_parents, $pa_source_data, $pa_item, $pn_c, $pa_options = null)
{
    global $g_ui_locale_id;
    if (!is_array($pa_options)) {
        $pa_options = array();
    }
    $o_log = caGetOption('log', $pa_options, null);
    $o_reader = caGetOption('reader', $pa_options, null);
    $o_trans = caGetOption('transaction', $pa_options, null);
    $vn_list_id = caGetOption('list_id', $pa_options, null);
    $vb_hierarchy_mode = caGetOption('hierarchyMode', $pa_options, false);
    if (!is_array($pa_parents)) {
        $pa_parents = array($pa_parents);
    }
    $vn_id = null;
    $pa_parents = array_reverse($pa_parents);
    foreach ($pa_parents as $vn_i => $va_parent) {
        if (!is_array($va_parent)) {
            $o_log->logWarn(_t('[%2] Parents options invalid. Did you forget to pass a list? Parents list passed was: %1', print_r($pa_parents, true), $ps_refinery_name));
            break;
        }
        $vs_name = BaseRefinery::parsePlaceholder($va_parent['name'], $pa_source_data, $pa_item, $pn_c, array('reader' => $o_reader, 'returnAsString' => true, 'delimiter' => ' '));
        $vs_idno = BaseRefinery::parsePlaceholder($va_parent['idno'], $pa_source_data, $pa_item, $pn_c, array('reader' => $o_reader, 'returnAsString' => true, 'delimiter' => ' '));
        $vs_type = BaseRefinery::parsePlaceholder($va_parent['type'], $pa_source_data, $pa_item, $pn_c, array('reader' => $o_reader, 'returnAsString' => true, 'delimiter' => ' '));
        if (!$vs_name && !$vs_idno) {
            continue;
        }
        if (!$vs_name) {
            continue;
        }
        //$vs_name = $vs_idno; }
        $va_attributes = isset($va_parent['attributes']) && is_array($va_parent['attributes']) ? $va_parent['attributes'] : array();
        foreach ($va_attributes as $vs_element_code => $va_attrs) {
            if (is_array($va_attrs)) {
                foreach ($va_attrs as $vs_k => $vs_v) {
                    // BaseRefinery::parsePlaceholder may return an array if the input format supports repeated values (as XML does)
                    // DataMigrationUtils::getCollectionID(), which ca_data_importers::importDataFromSource() uses to create related collections
                    // only supports non-repeating attribute values, so we join any values here and call it a day.
                    $va_attributes[$vs_element_code][$vs_k] = BaseRefinery::parsePlaceholder($vs_v, $pa_source_data, $pa_item, $pn_c, array('reader' => $o_reader, 'returnAsString' => true, 'delimiter' => ' '));
                }
            } else {
                $va_attributes[$vs_element_code] = array($vs_element_code => BaseRefinery::parsePlaceholder($va_attrs, $pa_source_data, $pa_item, $pn_c, array('reader' => $o_reader, 'returnAsString' => true, 'delimiter' => ' ')));
            }
        }
        $va_attributes['idno'] = $vs_idno;
        $va_attributes['parent_id'] = $vn_id;
        if (isset($va_parent['rules']) && is_array($va_parent['rules'])) {
            foreach ($va_parent['rules'] as $va_rule) {
                $vm_ret = ExpressionParser::evaluate($va_rule['trigger'], $pa_source_data);
                if (!ExpressionParser::hadError() && (bool) $vm_ret) {
                    foreach ($va_rule['actions'] as $va_action) {
                        if (!is_array($va_action) && strtolower($va_action) == 'skip') {
                            $va_action = array('action' => 'skip');
                        }
                        switch ($vs_action_code = strtolower($va_action['action'])) {
                            case 'set':
                                switch ($va_action['target']) {
                                    case 'name':
                                        $vs_name = BaseRefinery::parsePlaceholder($va_action['value'], $pa_source_data, $pa_item, $pn_c, array('reader' => $o_reader, 'returnAsString' => true, 'delimiter' => ' '));
                                        break;
                                    case 'type':
                                        $vs_type = BaseRefinery::parsePlaceholder($va_action['value'], $pa_source_data, $pa_item, $pn_c, array('reader' => $o_reader, 'returnAsString' => true, 'delimiter' => ' '));
                                        break;
                                    default:
                                        $va_attributes[$va_action['target']] = BaseRefinery::parsePlaceholder($va_action['value'], $pa_source_data, $pa_item, $pn_c, array('reader' => $o_reader, 'returnAsString' => true, 'delimiter' => ' '));
                                        break;
                                }
                                break;
                            case 'skip':
                            default:
                                if ($o_log) {
                                    if ($vs_action_code != 'skip') {
                                        $o_log->logInfo(_t('[%3] Parent was skipped using rule "%1" with default action because an invalid action ("%2") was specified', $va_rule['trigger'], $vs_action_code, $ps_refinery_name));
                                    } else {
                                        $o_log->logDebug(_t('[%3] Parent was skipped using rule "%1" with action "%2"', $va_rule['trigger'], $vs_action_code, $ps_refinery_name));
                                    }
                                }
                                continue 4;
                                break;
                        }
                    }
                } elseif (ExpressionParser::hadError() && $o_log) {
                    $o_log->logError(_t('[%3] Error processing rule "%1" as an error occurred. Error number was "%2"', $va_rule['trigger'], ExpressionParser::$s_last_error, $ps_refinery_name));
                }
            }
        }
        $va_match_on = caGetOption("{$ps_refinery_name}_dontMatchOnLabel", $pa_item['settings'], false) ? array('idno') : array('idno', 'label');
        $pa_options = array_merge(array('matchOn' => $va_match_on), $pa_options);
        switch ($ps_table) {
            case 'ca_objects':
                $vn_id = DataMigrationUtils::getObjectID($vs_name, $vn_id, $vs_type, $g_ui_locale_id, $va_attributes, $pa_options);
                $va_attributes['preferred_labels']['name'] = $va_attributes['_preferred_labels'] = $vs_name;
                break;
            case 'ca_entities':
                $vn_id = DataMigrationUtils::getEntityID($va_entity_label = DataMigrationUtils::splitEntityName($vs_name, $pa_options), $vs_type, $g_ui_locale_id, $va_attributes, $pa_options);
                $va_attributes['preferred_labels'] = $va_entity_label;
                $va_attributes['_preferred_labels'] = $vs_name;
                break;
            case 'ca_places':
                if (!$vn_id) {
                    // get place hierarchy root
                    require_once __CA_MODELS_DIR__ . "/ca_places.php";
                    $t_place = new ca_places();
                    if ($o_trans) {
                        $t_place->setTransaction($o_trans);
                    }
                    $vn_id = $pa_options['defaultParentID'];
                    if (!$vn_id) {
                        $vn_id = $t_place->getHierarchyRootID($pa_options['hierarchyID']);
                    }
                    $va_attributes['parent_id'] = $vn_id;
                }
                $vn_id = DataMigrationUtils::getPlaceID($vs_name, $vn_id, $vs_type, $g_ui_locale_id, $va_attributes, $pa_options);
                $va_attributes['preferred_labels']['name'] = $va_attributes['_preferred_labels'] = $vs_name;
                break;
            case 'ca_occurrences':
                $vn_id = DataMigrationUtils::getOccurrenceID($vs_name, $vn_id, $vs_type, $g_ui_locale_id, $va_attributes, $pa_options);
                $va_attributes['preferred_labels']['name'] = $va_attributes['_preferred_labels'] = $vs_name;
                break;
            case 'ca_collections':
                $vn_id = DataMigrationUtils::getCollectionID($vs_name, $vs_type, $g_ui_locale_id, $va_attributes, $pa_options);
                $va_attributes['preferred_labels']['name'] = $va_attributes['_preferred_labels'] = $vs_name;
                break;
            case 'ca_loans':
                $vn_id = DataMigrationUtils::getLoanID($vs_name, $vs_type, $g_ui_locale_id, $va_attributes, $pa_options);
                $va_attributes['preferred_labels']['name'] = $va_attributes['_preferred_labels'] = $vs_name;
                break;
            case 'ca_movements':
                $vn_id = DataMigrationUtils::getMovementID($vs_name, $vs_type, $g_ui_locale_id, $va_attributes, $pa_options);
                $va_attributes['preferred_labels']['name'] = $va_attributes['_preferred_labels'] = $vs_name;
                break;
            case 'ca_list_items':
                if (!$vn_list_id) {
                    if ($o_log) {
                        $o_log->logDebug(_t('[importHelpers:caProcessRefineryParents] List was not specified'));
                    }
                    return null;
                }
                if (!$vn_id) {
                    // get place hierarchy root
                    require_once __CA_MODELS_DIR__ . "/ca_lists.php";
                    $t_list = new ca_lists();
                    if ($o_trans) {
                        $t_list->setTransaction($o_trans);
                    }
                    $vn_id = $t_list->getRootItemIDForList($vn_list_id);
                    $va_attributes['parent_id'] = $vn_id;
                }
                $vn_id = DataMigrationUtils::getListItemID($vn_list_id, $vs_name, $vs_type, $g_ui_locale_id, $va_attributes, $pa_options);
                $va_attributes['preferred_labels']['name_singular'] = $va_attributes['preferred_labels']['name_plural'] = $vs_name;
                break;
            case 'ca_storage_locations':
                if (!$vn_id) {
                    // get storage location hierarchy root
                    require_once __CA_MODELS_DIR__ . "/ca_storage_locations.php";
                    $t_loc = new ca_storage_locations();
                    if ($o_trans) {
                        $t_loc->setTransaction($o_trans);
                    }
                    $vn_id = $t_loc->getHierarchyRootID();
                    $va_attributes['parent_id'] = $vn_id;
                }
                $vn_id = DataMigrationUtils::getStorageLocationID($vs_name, $vn_id, $vs_type, $g_ui_locale_id, $va_attributes, $pa_options);
                $va_attributes['preferred_labels']['name'] = $va_attributes['_preferred_labels'] = $vs_name;
                break;
            default:
                if ($o_log) {
                    $o_log->logDebug(_t('[importHelpers:caProcessRefineryParents] Invalid table %1', $ps_table));
                }
                return null;
                break;
        }
        $va_attributes['locale_id'] = $g_ui_locale_id;
        if ($o_log) {
            $o_log->logDebug(_t('[%6] Got parent %1 (%2) with id %3 and type %4 for %5', $vs_name, $vs_idno, $vn_id, $vs_type, $vs_name, $ps_refinery_name));
        }
    }
    if ($vb_hierarchy_mode) {
        return $va_attributes;
    }
    return $vn_id;
}
Example #7
0
 /**
  * Tests ExpressionParser::evaluate
  */
 public function testEvaluate()
 {
     $actualOutput = ExpressionParser::evaluate($this->input, $this->dataContext);
     // Expected result
     $this->assertEquals('no_prefix_id.Ee > 0', $actualOutput);
 }
 /**
  * 
  */
 public function validateUsingMetadataDictionaryRules($pa_options = null)
 {
     if (!$this->getPrimaryKey()) {
         return null;
     }
     $o_db = $this->getDb();
     $o_dm = Datamodel::load();
     $t_violation = new ca_metadata_dictionary_rule_violations();
     $va_rules = ca_metadata_dictionary_rules::getRules(array('bundles' => caGetOption('bundles', $pa_options, null)));
     $vn_violation_count = 0;
     $va_violations = array();
     foreach ($va_rules as $va_rule) {
         $va_expression_tags = caGetTemplateTags($va_rule['expression']);
         $t_violation->clear();
         $vb_skip = !$this->hasBundle($va_rule['bundle_name'], $this->getTypeID());
         if (!$vb_skip) {
             // create array of values present in rule
             $va_row = array($va_rule['bundle_name'] => $vs_val = $this->get($va_rule['bundle_name']));
             foreach ($va_expression_tags as $vs_tag) {
                 $va_row[$vs_tag] = $this->get($vs_tag);
             }
         }
         // is there a violation recorded for this rule and row?
         if ($t_found = ca_metadata_dictionary_rule_violations::find(array('rule_id' => $va_rule['rule_id'], 'row_id' => $this->getPrimaryKey(), 'table_num' => $this->tableNum()), array('returnAs' => 'firstModelInstance'))) {
             $t_violation = $t_found;
         }
         if (!$vb_skip && ExpressionParser::evaluate($va_rule['expression'], $va_row)) {
             // violation
             if ($t_violation->getPrimaryKey()) {
                 $t_violation->setMode(ACCESS_WRITE);
                 $t_violation->update();
             } else {
                 $t_violation->setMode(ACCESS_WRITE);
                 $t_violation->set('rule_id', $va_rule['rule_id']);
                 $t_violation->set('table_num', $this->tableNum());
                 $t_violation->set('row_id', $this->getPrimaryKey());
                 $t_violation->insert();
             }
             $va_violations[$va_rule['rule_level']][$va_rule['bundle_name']][] = $va_rule;
             $vn_violation_count++;
         } else {
             if ($t_violation->getPrimaryKey()) {
                 $t_violation->setMode(ACCESS_WRITE);
                 $t_violation->delete(true);
                 // remove violation
             }
         }
     }
     return $va_violations;
 }
Example #9
0
 /**
  *
  */
 public static function validate_using_metadata_dictionary_rules($po_opts = null)
 {
     require_once __CA_MODELS_DIR__ . '/ca_metadata_dictionary_rules.php';
     require_once __CA_MODELS_DIR__ . '/ca_metadata_dictionary_rule_violations.php';
     $o_dm = Datamodel::load();
     $t_violation = new ca_metadata_dictionary_rule_violations();
     $va_rules = ca_metadata_dictionary_rules::getRules();
     print CLIProgressBar::start(sizeof($va_rules), _t('Evaluating'));
     $vn_total_rows = $vn_rule_num = 0;
     $vn_num_rules = sizeof($va_rules);
     foreach ($va_rules as $va_rule) {
         $vn_rule_num++;
         $va_expression_tags = caGetTemplateTags($va_rule['expression']);
         $va_tmp = explode(".", $va_rule['bundle_name']);
         if (!($t_instance = $o_dm->getInstanceByTableName($va_tmp[0]))) {
             CLIUtils::addError(_t("Table for bundle %1 is not valid", $va_tmp[0]));
             continue;
         }
         $vs_bundle_name_proc = str_replace("{$vs_table_name}.", "", $va_rule['bundle_name']);
         $vn_table_num = $t_instance->tableNum();
         $qr_records = call_user_func_array(($vs_table_name = $t_instance->tableName()) . "::find", array(array('deleted' => 0), array('returnAs' => 'searchResult')));
         if (!$qr_records) {
             continue;
         }
         $vn_total_rows += $qr_records->numHits();
         CLIProgressBar::setTotal($vn_total_rows);
         $vn_count = 0;
         while ($qr_records->nextHit()) {
             $vn_count++;
             print CLIProgressBar::next(1, _t("Rule %1 [%2/%3]: record %4", $va_rule['rule_settings']['label'], $vn_rule_num, $vn_num_rules, $vn_count));
             $t_violation->clear();
             $vn_id = $qr_records->getPrimaryKey();
             $vb_skip = !$t_instance->hasBundle($va_rule['bundle_name'], $qr_records->get('type_id'));
             if (!$vb_skip) {
                 // create array of values present in rule
                 $va_row = array($va_rule['bundle_name'] => $vs_val = $qr_records->get($va_rule['bundle_name']));
                 foreach ($va_expression_tags as $vs_tag) {
                     $va_row[$vs_tag] = $qr_records->get($vs_tag);
                 }
             }
             // is there a violation recorded for this rule and row?
             if ($t_found = ca_metadata_dictionary_rule_violations::find(array('rule_id' => $va_rule['rule_id'], 'row_id' => $vn_id, 'table_num' => $vn_table_num), array('returnAs' => 'firstModelInstance'))) {
                 $t_violation = $t_found;
             }
             if (!$vb_skip && ExpressionParser::evaluate($va_rule['expression'], $va_row)) {
                 // violation
                 if ($t_violation->getPrimaryKey()) {
                     $t_violation->setMode(ACCESS_WRITE);
                     $t_violation->update();
                 } else {
                     $t_violation->setMode(ACCESS_WRITE);
                     $t_violation->set('rule_id', $va_rule['rule_id']);
                     $t_violation->set('table_num', $t_instance->tableNum());
                     $t_violation->set('row_id', $qr_records->getPrimaryKey());
                     $t_violation->insert();
                 }
             } else {
                 if ($t_violation->getPrimaryKey()) {
                     $t_violation->delete(true);
                     // remove violation
                 }
             }
         }
     }
     print CLIProgressBar::finish();
 }
 /**
  * Returns list of variables defined in the expression
  *
  * @param string $ps_expression
  * @return array
  */
 public static function getVariableList($ps_expression)
 {
     $o_exp = new ExpressionParser();
     if ($o_exp->tokenize($ps_expression)) {
     }
     $va_vars = array();
     while ($va_token = $o_exp->getToken()) {
         if ($va_token['type'] == EEP_TOKEN_VARIABLE) {
             $va_vars[] = $va_token['varname'];
         }
     }
     return $va_vars;
 }
Example #11
0
/**
 * Generates standard-format inspector panels for editors
 *
 * @param View $po_view Inspector view object
 * @param array $pa_options Optional array of options. Supported options are:
 *		backText = a string to use as the "back" button text; default is "Results"
 *
 * @return string HTML implementing the inspector
 */
function caEditorInspector($po_view, $pa_options = null)
{
    require_once __CA_MODELS_DIR__ . '/ca_sets.php';
    require_once __CA_MODELS_DIR__ . '/ca_data_exporters.php';
    $t_item = $po_view->getVar('t_item');
    $vs_table_name = $t_item->tableName();
    if (($vs_priv_table_name = $vs_table_name) == 'ca_list_items') {
        $vs_priv_table_name = 'ca_lists';
    }
    $vn_item_id = $t_item->getPrimaryKey();
    $o_result_context = $po_view->getVar('result_context');
    $t_ui = $po_view->getVar('t_ui');
    $t_type = method_exists($t_item, "getTypeInstance") ? $t_item->getTypeInstance() : null;
    $vs_type_name = method_exists($t_item, "getTypeName") ? $t_item->getTypeName() : '';
    if (!$vs_type_name) {
        $vs_type_name = $t_item->getProperty('NAME_SINGULAR');
    }
    $va_reps = $po_view->getVar('representations');
    $o_dm = Datamodel::load();
    if ($t_item->isHierarchical()) {
        $va_ancestors = $po_view->getVar('ancestors');
        $vn_parent_id = $t_item->get($t_item->getProperty('HIERARCHY_PARENT_ID_FLD'));
    } else {
        $va_ancestors = array();
        $vn_parent_id = null;
    }
    // action extra to preserve currently open screen across next/previous links
    $vs_screen_extra = $po_view->getVar('screen') ? '/' . $po_view->getVar('screen') : '';
    if ($vs_type_name == "list item") {
        $vs_style = "style='height:auto;'";
    }
    if ($vn_item_id | $po_view->request->getAction() === 'Delete') {
        $vs_buf = '<h3 class="nextPrevious" ' . $vs_style . '>' . caEditorFindResultNavigation($po_view->request, $t_item, $o_result_context, $pa_options) . "</h3>\n";
    }
    $vs_color = null;
    if ($t_type) {
        $vs_color = trim($t_type->get('color'));
    }
    if (!$vs_color && $t_ui) {
        $vs_color = trim($t_ui->get('color'));
    }
    if (!$vs_color) {
        $vs_color = "FFFFFF";
    }
    $vs_buf .= "<h4><div id='caColorbox' style='border: 6px solid #{$vs_color};'>\n";
    $vs_icon = null;
    if ($t_type) {
        $vs_icon = $t_type->getMediaTag('icon', 'icon');
    }
    if (!$vs_icon && $t_ui) {
        $vs_icon = $t_ui->getMediaTag('icon', 'icon');
    }
    if ($vs_icon) {
        $vs_buf .= "<div id='inspectoricon' style='border-right: 6px solid #{$vs_color}; border-bottom: 6px solid #{$vs_color}; -moz-border-radius-bottomright: 8px; -webkit-border-bottom-right-radius: 8px;'>\n{$vs_icon}</div>\n";
    }
    if ($po_view->request->getAction() === 'Delete' && $po_view->request->getParameter('confirm', pInteger)) {
        $vs_buf .= "<strong>" . _t("Deleted %1", $vs_type_name) . "</strong>\n";
        $vs_buf .= "<br style='clear: both;'/></div></h4>\n";
    } else {
        if ($vn_item_id) {
            if (!$po_view->request->config->get("{$vs_priv_table_name}_inspector_disable_headline")) {
                if ($po_view->request->user->canDoAction("can_edit_" . $vs_priv_table_name) && sizeof($t_item->getTypeList()) > 1) {
                    $vs_buf .= "<strong>" . _t("Editing %1", $vs_type_name) . ": </strong>\n";
                } else {
                    $vs_buf .= "<strong>" . _t("Viewing %1", $vs_type_name) . ": </strong>\n";
                }
            }
            if ($t_item->hasField('is_deaccessioned') && $t_item->get('is_deaccessioned') && $t_item->get('deaccession_date', array('getDirectDate' => true)) <= caDateToHistoricTimestamp(_t('now'))) {
                // If currently deaccessioned then display deaccession message
                $vs_buf .= "<br/><div class='inspectorDeaccessioned'>" . _t('Deaccessioned %1', $t_item->get('deaccession_date')) . "</div>\n";
                if ($vs_deaccession_notes = $t_item->get('deaccession_notes')) {
                    TooltipManager::add(".inspectorDeaccessioned", $vs_deaccession_notes);
                }
            } else {
                if ($po_view->request->user->canDoAction('can_see_current_location_in_inspector_ca_objects')) {
                    if ($t_ui && method_exists($t_item, "getObjectHistory") && (is_array($va_placements = $t_ui->getPlacementsForBundle('ca_objects_history')) && sizeof($va_placements) > 0)) {
                        //
                        // Output current "location" of object in life cycle. Configuration is taken from a ca_objects_history bundle configured for the current editor
                        //
                        $va_placement = array_shift($va_placements);
                        $va_bundle_settings = $va_placement['settings'];
                        if (is_array($va_history = $t_item->getObjectHistory($va_bundle_settings, array('limit' => 1, 'currentOnly' => true))) && sizeof($va_history) > 0) {
                            $va_current_location = array_shift(array_shift($va_history));
                            if (!($vs_inspector_current_location_label = $po_view->request->config->get("ca_objects_inspector_current_location_label"))) {
                                $vs_inspector_current_location_label = _t('Current');
                            }
                            if ($va_current_location['display']) {
                                $vs_buf .= "<div class='inspectorCurrentLocation'><strong>" . $vs_inspector_current_location_label . ':</strong><br/>' . $va_current_location['display'] . "</div>";
                            }
                        }
                    } elseif (method_exists($t_item, "getLastLocationForDisplay")) {
                        // If no ca_objects_history bundle is configured then display the last storage location
                        if ($vs_current_location = $t_item->getLastLocationForDisplay("<ifdef code='ca_storage_locations.parent.preferred_labels'>^ca_storage_locations.parent.preferred_labels ➜ </ifdef>^ca_storage_locations.preferred_labels.name")) {
                            $vs_buf .= "<br/><div class='inspectorCurrentLocation'>" . _t('Location: %1', $vs_current_location) . "</div>\n";
                            $vs_full_location_hierarchy = $t_item->getLastLocationForDisplay("^ca_storage_locations.hierarchy.preferred_labels.name%delimiter=_➜_");
                            if ($vs_full_location_hierarchy !== $vs_current_location) {
                                TooltipManager::add(".inspectorCurrentLocation", $vs_full_location_hierarchy);
                            }
                        }
                    }
                }
            }
            //
            // Display flags; expressions for these are defined in app.conf in the <table_name>_inspector_display_flags directive
            //
            if (is_array($va_display_flags = $po_view->request->config->getAssoc("{$vs_table_name}_inspector_display_flags"))) {
                $va_display_flag_buf = array();
                foreach ($va_display_flags as $vs_exp => $vs_display_flag) {
                    $va_exp_vars = array();
                    foreach (ExpressionParser::getVariableList($vs_exp) as $vs_var_name) {
                        $va_exp_vars[$vs_var_name] = $t_item->get($vs_var_name, array('returnIdno' => true));
                    }
                    if (ExpressionParser::evaluate($vs_exp, $va_exp_vars)) {
                        $va_display_flag_buf[] = $t_item->getWithTemplate("{$vs_display_flag}");
                    }
                }
                if (!($vs_display_flag_delim = $po_view->request->config->get("{$vs_table_name}_inspector_display_flags_delimiter"))) {
                    $vs_display_flag_delim = '; ';
                }
                if (sizeof($va_display_flag_buf) > 0) {
                    $vs_buf .= join($vs_display_flag_delim, $va_display_flag_buf);
                }
            }
            $vs_label = '';
            $vb_dont_use_labels_for_ca_objects = (bool) $t_item->getAppConfig()->get('ca_objects_dont_use_labels');
            if (!($vs_table_name === 'ca_objects' && $vb_dont_use_labels_for_ca_objects)) {
                if ($vs_get_spec = $po_view->request->config->get("{$vs_table_name}_inspector_display_title")) {
                    $vs_label = caProcessTemplateForIDs($vs_get_spec, $vs_table_name, array($t_item->getPrimaryKey()));
                } else {
                    $va_object_collection_collection_ancestors = $po_view->getVar('object_collection_collection_ancestors');
                    if ($t_item->tableName() == 'ca_objects' && $t_item->getAppConfig()->get('ca_objects_x_collections_hierarchy_enabled') && is_array($va_object_collection_collection_ancestors) && sizeof($va_object_collection_collection_ancestors)) {
                        $va_collection_links = array();
                        foreach ($va_object_collection_collection_ancestors as $va_collection_ancestor) {
                            $va_collection_links[] = caEditorLink($po_view->request, $va_collection_ancestor['label'], '', 'ca_collections', $va_collection_ancestor['collection_id']);
                        }
                        $vs_label .= join(" / ", $va_collection_links) . ' &gt; ';
                    }
                    if (method_exists($t_item, 'getLabelForDisplay')) {
                        $vn_parent_index = sizeof($va_ancestors) - 1;
                        if ($vn_parent_id && ($vs_table_name != 'ca_places' || $vn_parent_index > 0)) {
                            $va_parent = $va_ancestors[$vn_parent_index];
                            $vs_disp_fld = $t_item->getLabelDisplayField();
                            if ($va_parent['NODE'][$vs_disp_fld] && ($vs_editor_link = caEditorLink($po_view->request, $va_parent['NODE'][$vs_disp_fld], '', $vs_table_name, $va_parent['NODE'][$t_item->primaryKey()]))) {
                                $vs_label .= $vs_editor_link . ' &gt; ' . $t_item->getLabelForDisplay();
                            } else {
                                $vs_label .= ($va_parent['NODE'][$vs_disp_fld] ? $va_parent['NODE'][$vs_disp_fld] . ' &gt; ' : '') . $t_item->getLabelForDisplay();
                            }
                        } else {
                            $vs_label .= $t_item->getLabelForDisplay();
                            if ($vs_table_name === 'ca_editor_uis' && in_array($po_view->request->getAction(), array('EditScreen', 'DeleteScreen', 'SaveScreen'))) {
                                $t_screen = new ca_editor_ui_screens($po_view->request->getParameter('screen_id', pInteger));
                                if (!($vs_screen_name = $t_screen->getLabelForDisplay())) {
                                    $vs_screen_name = _t('new screen');
                                }
                                $vs_label .= " &gt; " . $vs_screen_name;
                            }
                        }
                    } else {
                        $vs_label .= $t_item->get('name');
                    }
                }
            }
            $vb_show_idno = (bool) ($vs_idno = $t_item->get($t_item->getProperty('ID_NUMBERING_ID_FIELD')));
            if (!$vs_label) {
                switch ($vs_table_name) {
                    default:
                        if ($vs_table_name === 'ca_objects' && $vb_dont_use_labels_for_ca_objects) {
                            $vs_label = $vs_idno;
                            $vb_show_idno = false;
                        } else {
                            $vs_label = '[' . _t('BLANK') . ']';
                        }
                        break;
                }
            }
            $vs_buf .= "<div class='recordTitle {$vs_table_name}' style='width:190px; overflow:hidden;'>{$vs_label}" . ($vb_show_idno ? "<a title='{$vs_idno}'>" . ($vs_idno ? " ({$vs_idno})" : '') : "") . "</a></div>";
            if ($vs_table_name === 'ca_object_lots' && $t_item->getPrimaryKey()) {
                $vs_buf .= "<div id='inspectorLotMediaDownload'><strong>" . (($vn_num_objects = $t_item->numObjects()) == 1 ? _t('Lot contains %1 object', $vn_num_objects) : _t('Lot contains %1 objects', $vn_num_objects)) . "</strong>\n";
            }
            if ($po_view->request->config->get("include_custom_inspector")) {
                if (file_exists($po_view->request->getViewsDirectoryPath() . "/bundles/inspector_info.php")) {
                    $vo_inspector_view = new View($po_view->request, $po_view->request->getViewsDirectoryPath() . "/bundles/");
                    $vo_inspector_view->setVar('t_item', $t_item);
                    $vs_buf .= $vo_inspector_view->render('inspector_info.php');
                }
            }
        } else {
            $vs_parent_name = '';
            if ($vn_parent_id = $po_view->request->getParameter('parent_id', pInteger)) {
                $t_parent = clone $t_item;
                $t_parent->load($vn_parent_id);
                $vs_parent_name = $t_parent->getLabelForDisplay();
            }
            $vs_buf .= "<div class='creatingNew'>" . _t("Creating new %1", $vs_type_name) . " " . ($vs_parent_name ? _t("%1 &gt; New %2", $vs_parent_name, $vs_type_name) : '') . "</div>\n";
            $vs_buf .= "<br/>\n";
        }
        // -------------------------------------------------------------------------------------
        if ($t_item->getPrimaryKey()) {
            if (sizeof($va_reps) > 0) {
                $va_imgs = array();
                $vs_buf .= "<div id='inspectorMedia'>";
                $vn_r = $vn_primary_index = 0;
                foreach ($va_reps as $va_rep) {
                    if (!($va_rep['info']['preview170']['WIDTH'] && $va_rep['info']['preview170']['HEIGHT'])) {
                        continue;
                    }
                    if ($vb_is_primary = isset($va_rep['is_primary']) && (bool) $va_rep['is_primary']) {
                        $vn_primary_index = $vn_r;
                    }
                    $va_imgs[] = "{url:'" . $va_rep['urls']['preview170'] . "', width: " . $va_rep['info']['preview170']['WIDTH'] . ", height: " . $va_rep['info']['preview170']['HEIGHT'] . ", link: '#', onclick:  'caMediaPanel.showPanel(\\'" . caNavUrl($po_view->request, '*', '*', 'GetMediaOverlay', array($t_item->primaryKey() => $vn_item_id, 'representation_id' => $va_rep['representation_id'])) . "\\')'}";
                    $vn_r++;
                }
                if (sizeof($va_reps) > 1) {
                    $vs_buf .= "\n\t\t\t\t\t<div class='leftScroll'>\n\t\t\t\t\t\t<a href='#' onclick='inspectorInfoRepScroller.scrollToPreviousImage(); return false;'>" . caNavIcon($po_view->request, __CA_NAV_BUTTON_SCROLL_LT__) . "</a>\n\t\t\t\t\t</div>\n\t\t";
                }
                if (sizeof($va_imgs) > 0) {
                    $vs_buf .= "\n\t\t\t\t<div id='inspectorInfoRepScrollingViewer' style='position: relative;'>\n\t\t\t\t\t<div id='inspectorInfoRepScrollingViewerContainer'>\n\t\t\t\t\t\t<div id='inspectorInfoRepScrollingViewerImageContainer'></div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t";
                    if (sizeof($va_reps) > 1) {
                        $vs_buf .= "\n\t\t\t\t\t<div class='rightScroll'>\n\t\t\t\t\t\t<a href='#' onclick='inspectorInfoRepScroller.scrollToNextImage(); return false;'>" . caNavIcon($po_view->request, __CA_NAV_BUTTON_SCROLL_RT__) . "</a>\n\t\t\t\t\t</div>\n\t\t";
                    }
                    TooltipManager::add(".leftScroll", _t('Previous'));
                    TooltipManager::add(".rightScroll", _t('Next'));
                    $vs_buf .= "<script type='text/javascript'>";
                    $vs_buf .= "\n\t\t\t\t\tvar inspectorInfoRepScroller = caUI.initImageScroller([" . join(",", $va_imgs) . "], 'inspectorInfoRepScrollingViewerImageContainer', {\n\t\t\t\t\t\t\tcontainerWidth: 170, containerHeight: 170,\n\t\t\t\t\t\t\timageCounterID: 'inspectorInfoRepScrollingViewerCounter',\n\t\t\t\t\t\t\tscrollingImageClass: 'inspectorInfoRepScrollerImage',\n\t\t\t\t\t\t\tscrollingImagePrefixID: 'inspectorInfoRep',\n\t\t\t\t\t\t\tinitialIndex: {$vn_primary_index}\n\t\t\t\t\t\t\t\n\t\t\t\t\t});\n\t\t\t\t</script>";
                }
                $vs_buf .= "</div>\n";
                if ($vs_get_spec = $po_view->request->config->get("{$vs_table_name}_inspector_display_below_media")) {
                    $vs_buf .= caProcessTemplateForIDs($vs_get_spec, $vs_table_name, array($t_item->getPrimaryKey()));
                }
            }
            //
            // Output configurable additional info from config, if set
            //
            if ($vs_additional_info = $po_view->request->config->get("{$vs_table_name}_inspector_additional_info")) {
                if (is_array($vs_additional_info)) {
                    $vs_buf .= "<br/>";
                    foreach ($vs_additional_info as $vs_info) {
                        $vs_buf .= caProcessTemplateForIDs($vs_info, $vs_table_name, array($t_item->getPrimaryKey()), array('requireLinkTags' => true)) . "<br/>\n";
                    }
                } else {
                    $vs_buf .= "<br/>" . caProcessTemplateForIDs($vs_additional_info, $vs_table_name, array($t_item->getPrimaryKey()), array('requireLinkTags' => true)) . "<br/>\n";
                }
            }
            $vs_buf .= "<div id='toolIcons'>";
            if ($vn_item_id) {
                # --- watch this link
                $vs_watch = "";
                if (in_array($vs_table_name, array('ca_objects', 'ca_object_lots', 'ca_entities', 'ca_places', 'ca_occurrences', 'ca_collections', 'ca_storage_locations'))) {
                    require_once __CA_MODELS_DIR__ . '/ca_watch_list.php';
                    $t_watch_list = new ca_watch_list();
                    $vs_watch = "<div class='watchThis'><a href='#' title='" . _t('Add/remove item to/from watch list.') . "' onclick='caToggleItemWatch(); return false;' id='caWatchItemButton'>" . caNavIcon($po_view->request, $t_watch_list->isItemWatched($vn_item_id, $t_item->tableNum(), $po_view->request->user->get("user_id")) ? __CA_NAV_BUTTON_UNWATCH__ : __CA_NAV_BUTTON_WATCH__) . "</a></div>";
                    $vs_buf .= "\n<script type='text/javascript'>\n\t\tfunction caToggleItemWatch() {\n\t\t\tvar url = '" . caNavUrl($po_view->request, $po_view->request->getModulePath(), $po_view->request->getController(), 'toggleWatch', array($t_item->primaryKey() => $vn_item_id)) . "';\n\t\t\t\n\t\t\tjQuery.getJSON(url, {}, function(data, status) {\n\t\t\t\tif (data['status'] == 'ok') {\n\t\t\t\t\tjQuery('#caWatchItemButton').html((data['state'] == 'watched') ? '" . addslashes(caNavIcon($po_view->request, __CA_NAV_BUTTON_UNWATCH__)) . "' : '" . addslashes(caNavIcon($po_view->request, __CA_NAV_BUTTON_WATCH__)) . "');\n\t\t\t\t} else {\n\t\t\t\t\tconsole.log('Error toggling watch status for item: ' + data['errors']);\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t\t</script>\n";
                }
                $vs_buf .= "{$vs_watch}\n";
                TooltipManager::add("#caWatchItemButton", _t('Watch/Unwatch this record'));
                if ($po_view->request->user->canDoAction("can_change_type_{$vs_table_name}")) {
                    $vs_buf .= "<div id='inspectorChangeType'><div id='inspectorChangeTypeButton'><a href='#' onclick='caTypeChangePanel.showPanel(); return false;'>" . caNavIcon($po_view->request, __CA_NAV_BUTTON_CHANGE__ . " Change Type", array('title' => _t('Change type'))) . "</a></div></div>\n";
                    $vo_change_type_view = new View($po_view->request, $po_view->request->getViewsDirectoryPath() . "/bundles/");
                    $vo_change_type_view->setVar('t_item', $t_item);
                    FooterManager::add($vo_change_type_view->render("change_type_html.php"));
                    TooltipManager::add("#inspectorChangeType", _t('Change Record Type'));
                }
                if ($t_item->getPrimaryKey() && $po_view->request->config->get($vs_table_name . '_show_add_child_control_in_inspector')) {
                    $vb_show_add_child_control = true;
                    if (is_array($va_restrict_add_child_control_to_types = $po_view->request->config->getList($vs_table_name . '_restrict_child_control_in_inspector_to_types')) && sizeof($va_restrict_add_child_control_to_types)) {
                        $t_type_instance = $t_item->getTypeInstance();
                        if (!in_array($t_type_instance->get('idno'), $va_restrict_add_child_control_to_types) && !in_array($t_type_instance->getPrimaryKey(), $va_restrict_add_child_control_to_types)) {
                            $vb_show_add_child_control = false;
                        }
                    }
                    if ($vb_show_add_child_control) {
                        if ((bool) $po_view->request->config->get($vs_table_name . '_enforce_strict_type_hierarchy')) {
                            // strict menu
                            $vs_type_list = $t_item->getTypeListAsHTMLFormElement('type_id', array('style' => 'width: 90px; font-size: 9px;'), array('childrenOfCurrentTypeOnly' => true, 'directChildrenOnly' => $po_view->request->config->get($vs_table_name . '_enforce_strict_type_hierarchy') == '~' ? false : true, 'returnHierarchyLevels' => true, 'access' => __CA_BUNDLE_ACCESS_EDIT__));
                        } else {
                            // all types
                            $vs_type_list = $t_item->getTypeListAsHTMLFormElement('type_id', array('style' => 'width: 90px; font-size: 9px;'), array('access' => __CA_BUNDLE_ACCESS_EDIT__));
                        }
                        if ($vs_type_list) {
                            $vs_buf .= "<div id='inspectorCreateChild'><div id='inspectorCreateChildButton'><a href='#' onclick='caCreateChildPanel.showPanel(); return false;'>" . caNavIcon($po_view->request, __CA_NAV_BUTTON_CHILD__, array('title' => _t('Create Child Record'))) . "</a></div></div>\n";
                            $vo_create_child_view = new View($po_view->request, $po_view->request->getViewsDirectoryPath() . "/bundles/");
                            $vo_create_child_view->setVar('t_item', $t_item);
                            $vo_create_child_view->setVar('type_list', $vs_type_list);
                            FooterManager::add($vo_create_child_view->render("create_child_html.php"));
                            TooltipManager::add("#inspectorCreateChildButton", _t('Create a child record under this one'));
                        }
                    }
                }
            }
            if ($po_view->request->user->canDoAction('can_duplicate_' . $vs_table_name) && $t_item->getPrimaryKey()) {
                $vs_buf .= '<div id="caDuplicateItemButton">';
                $vs_buf .= caFormTag($po_view->request, 'Edit', 'DuplicateItemForm', $po_view->request->getModulePath() . '/' . $po_view->request->getController(), 'post', 'multipart/form-data', '_top', array('disableUnsavedChangesWarning' => true, 'noTimestamp' => true));
                $vs_buf .= caFormSubmitLink($po_view->request, caNavIcon($po_view->request, __CA_NAV_BUTTON_DUPLICATE__), '', 'DuplicateItemForm');
                $vs_buf .= caHTMLHiddenInput($t_item->primaryKey(), array('value' => $t_item->getPrimaryKey()));
                $vs_buf .= caHTMLHiddenInput('mode', array('value' => 'dupe'));
                $vs_buf .= "</form>";
                $vs_buf .= "</div>";
                TooltipManager::add("#caDuplicateItemButton", _t('Duplicate this %1', mb_strtolower($vs_type_name, 'UTF-8')));
            }
            //
            // Download media in lot ($vn_num_objects is only set for object lots)
            if ($vn_num_objects > 0) {
                $vs_buf .= "<div id='inspectorLotMediaDownloadButton'>" . caNavLink($po_view->request, caNavIcon($po_view->request, __CA_NAV_BUTTON_DOWNLOAD__), "button", $po_view->request->getModulePath(), $po_view->request->getController(), 'getLotMedia', array('lot_id' => $t_item->getPrimaryKey(), 'download' => 1), array()) . "</div>\n";
                TooltipManager::add('#inspectorLotMediaDownloadButton', _t("Download all media associated with objects in this lot"));
            }
            //
            // Download media in set
            if ($vs_table_name == 'ca_sets' && sizeof($t_item->getItemRowIDs()) > 0) {
                $vs_buf .= "<div id='inspectorSetMediaDownloadButton'>" . caNavLink($po_view->request, caNavIcon($po_view->request, __CA_NAV_BUTTON_DOWNLOAD__), "button", $po_view->request->getModulePath(), $po_view->request->getController(), 'getSetMedia', array('set_id' => $t_item->getPrimaryKey(), 'download' => 1), array()) . "</div>\n";
                TooltipManager::add('#inspectorSetMediaDownloadButton', _t("Download all media associated with records in this set"));
            }
            $vs_more_info = '';
            // list of sets in which item is a member
            $t_set = new ca_sets();
            if (is_array($va_sets = caExtractValuesByUserLocale($t_set->getSetsForItem($t_item->tableNum(), $t_item->getPrimaryKey(), array('user_id' => $po_view->request->getUserID(), 'access' => __CA_SET_READ_ACCESS__)))) && sizeof($va_sets)) {
                $va_links = array();
                foreach ($va_sets as $vn_set_id => $va_set) {
                    $va_links[] = "<a href='" . caEditorUrl($po_view->request, 'ca_sets', $vn_set_id) . "'>" . $va_set['name'] . "</a>";
                }
                $vs_more_info .= "<div><strong>" . (sizeof($va_links) == 1 ? _t("In set") : _t("In sets")) . "</strong> " . join(", ", $va_links) . "</div>\n";
            }
            // export options
            if ($vn_item_id && ($vs_select = $po_view->getVar('available_mappings_as_html_select'))) {
                $vs_more_info .= "<div class='inspectorExportControls'>" . caFormTag($po_view->request, 'exportItem', 'caExportForm', null, 'post', 'multipart/form-data', '_top', array('disableUnsavedChangesWarning' => true));
                $vs_more_info .= $vs_select;
                $vs_more_info .= caHTMLHiddenInput($t_item->primaryKey(), array('value' => $t_item->getPrimaryKey()));
                $vs_more_info .= caHTMLHiddenInput('download', array('value' => 1));
                $vs_more_info .= caFormSubmitLink($po_view->request, 'Export &rsaquo;', 'button', 'caExportForm');
                $vs_more_info .= "</form></div>";
            }
            $va_creation = $t_item->getCreationTimestamp();
            $va_last_change = $t_item->getLastChangeTimestamp();
            if ($va_creation['timestamp'] || $va_last_change['timestamp']) {
                $vs_more_info .= "<div class='inspectorChangeDateList'>";
                if ($va_creation['timestamp']) {
                    if (!trim($vs_name = $va_creation['fname'] . ' ' . $va_creation['lname'])) {
                        $vs_name = null;
                    }
                    $vs_interval = ($vn_t = time() - $va_creation['timestamp']) == 0 ? _t('Just now') : _t('%1 ago', caFormatInterval($vn_t, 2));
                    $vs_more_info .= "<div class='inspectorChangeDateListLine'  id='caInspectorCreationDate'>" . ($vs_name ? _t('<strong>Created</strong><br/>%1 by %2', $vs_interval, $vs_name) : _t('<strong>Created</strong><br/>%1', $vs_interval)) . "</div>";
                    TooltipManager::add("#caInspectorCreationDate", "<h2>" . _t('Created on') . "</h2>" . _t('Created on %1', caGetLocalizedDate($va_creation['timestamp'], array('dateFormat' => 'delimited'))));
                }
                if ($va_last_change['timestamp'] && $va_creation['timestamp'] != $va_last_change['timestamp']) {
                    if (!trim($vs_name = $va_last_change['fname'] . ' ' . $va_last_change['lname'])) {
                        $vs_name = null;
                    }
                    $vs_interval = ($vn_t = time() - $va_last_change['timestamp']) == 0 ? _t('Just now') : _t('%1 ago', caFormatInterval($vn_t, 2));
                    $vs_more_info .= "<div class='inspectorChangeDateListLine' id='caInspectorChangeDate'>" . ($vs_name ? _t('<strong>Last changed</strong><br/>%1 by %2', $vs_interval, $vs_name) : _t('<strong>Last changed</strong><br/>%1', $vs_interval)) . "</div>";
                    TooltipManager::add("#caInspectorChangeDate", "<h2>" . _t('Last changed on') . "</h2>" . _t('Last changed on %1', caGetLocalizedDate($va_last_change['timestamp'], array('dateFormat' => 'delimited'))));
                }
                if (method_exists($t_item, 'getMetadataDictionaryRuleViolations') && is_array($va_violations = $t_item->getMetadataDictionaryRuleViolations()) && ($vn_num_violations = sizeof($va_violations)) > 0) {
                    $va_violation_messages = array();
                    foreach ($va_violations as $vn_violation_id => $va_violation) {
                        $vs_label = $t_item->getDisplayLabel($va_violation['bundle_name']);
                        $va_violation_messages[] = "<li><em><u>{$vs_label}</u></em> " . $va_violation['violationMessage'] . "</li>";
                    }
                    $vs_more_info .= "<div id='caInspectorViolationsList'>" . ($vs_num_violations_display = "<img src='" . $po_view->request->getThemeUrlPath() . "/graphics/icons/warning_small.gif' border='0'/> " . ($vn_num_violations > 1 ? _t('%1 problems require attention', $vn_num_violations) : _t('%1 problem requires attention', $vn_num_violations))) . "</div>\n";
                    TooltipManager::add("#caInspectorViolationsList", "<h2>{$vs_num_violations_display}</h2><ol>" . join("\n", $va_violation_messages)) . "</ol>\n";
                }
                $vs_more_info .= "</div>\n";
            }
            if ($vs_get_spec = $po_view->request->config->get("{$vs_table_name}_inspector_display_more_info")) {
                $vs_more_info .= caProcessTemplateForIDs($vs_get_spec, $vs_table_name, array($t_item->getPrimaryKey()));
            }
            if ($vs_more_info) {
                $vs_buf .= "<div class='button info'><a href='#' id='inspectorMoreInfo'>" . caNavIcon($po_view->request, __CA_NAV_BUTTON_INFO2__) . "</a></div>\n\t\t\t<div id='inspectorInfo' >";
                $vs_buf .= $vs_more_info . "</div>\n";
                TooltipManager::add("#inspectorMoreInfo", _t('See more information about this record'));
            }
            $vs_buf .= "</div><!--End tooIcons-->";
        }
        // -------------------------------------------------------------------------------------
        //
        // Item-specific information
        //
        //
        // Output info for related items
        //
        if (!$t_item->getPrimaryKey()) {
            // only applies to new records
            $vs_rel_table = $po_view->request->getParameter('rel_table', pString);
            $vn_rel_type_id = $po_view->request->getParameter('rel_type_id', pString);
            $vn_rel_id = $po_view->request->getParameter('rel_id', pInteger);
            if ($vs_rel_table && $po_view->request->datamodel->tableExists($vs_rel_table) && $vn_rel_type_id && $vn_rel_id) {
                $t_rel = $po_view->request->datamodel->getTableInstance($vs_rel_table);
                if ($t_rel && $t_rel->load($vn_rel_id)) {
                    $vs_buf .= '<strong>' . _t("Will be related to %1", $t_rel->getTypeName()) . '</strong>: ' . $t_rel->getLabelForDisplay();
                }
            }
        }
        //
        // Output lot info for ca_objects
        //
        $vb_is_currently_part_of_lot = true;
        if (!($vn_lot_id = $t_item->get('lot_id'))) {
            $vn_lot_id = $po_view->request->getParameter('lot_id', pInteger);
            $vb_is_currently_part_of_lot = false;
        }
        if ($vs_table_name === 'ca_objects' && $vn_lot_id) {
            require_once __CA_MODELS_DIR__ . '/ca_object_lots.php';
            $va_lot_lots = caGetTypeListForUser('ca_object_lots', array('access' => __CA_BUNDLE_ACCESS_READONLY__));
            $t_lot = new ca_object_lots($vn_lot_id);
            if ($t_lot->get('deleted') == 0 && in_array($t_lot->get('type_id'), $va_lot_lots)) {
                if (!($vs_lot_displayname = $t_lot->get('idno_stub'))) {
                    if (!($vs_lot_displayname = $t_lot->getLabelForDisplay())) {
                        $vs_lot_displayname = "Lot {$vn_lot_id}";
                    }
                }
                if ($vs_lot_displayname) {
                    if (!($vs_part_of_lot_msg = $po_view->request->config->get("ca_objects_inspector_part_of_lot_msg"))) {
                        $vs_part_of_lot_msg = _t('Part of lot');
                    }
                    if (!($vs_will_be_part_of_lot_msg = $po_view->request->config->get("ca_objects_inspector_will_be_part_of_lot_msg"))) {
                        $vs_will_be_part_of_lot_msg = _t('Will be part of lot');
                    }
                    $vs_buf .= "<strong>" . ($vb_is_currently_part_of_lot ? $vs_part_of_lot_msg : $vs_will_be_part_of_lot_msg) . "</strong>: " . caNavLink($po_view->request, $vs_lot_displayname, '', 'editor/object_lots', 'ObjectLotEditor', 'Edit', array('lot_id' => $vn_lot_id));
                }
            }
        }
        $va_object_container_types = $po_view->request->config->getList('ca_objects_container_types');
        $va_object_component_types = $po_view->request->config->getList('ca_objects_component_types');
        $vb_can_add_component = $vs_table_name === 'ca_objects' && $t_item->getPrimaryKey() && $po_view->request->user->canDoAction('can_create_ca_objects') && $t_item->canTakeComponents();
        if (method_exists($t_item, 'getComponentCount')) {
            if ($vn_component_count = $t_item->getComponentCount()) {
                if ($t_ui && ($vs_component_list_screen = $t_ui->getScreenWithBundle("ca_objects_components_list", $po_view->request)) && $vs_component_list_screen !== $po_view->request->getActionExtra()) {
                    $vs_component_count_link = caNavLink($po_view->request, $vn_component_count == 1 ? _t('%1 component', $vn_component_count) : _t('%1 components', $vn_component_count), '', '*', '*', $po_view->request->getAction() . '/' . $vs_component_list_screen, array($t_item->primaryKey() => $t_item->getPrimaryKey()));
                } else {
                    $vs_component_count_link = $vn_component_count == 1 ? _t('%1 component', $vn_component_count) : _t('%1 components', $vn_component_count);
                }
                $vs_buf .= "<br/><strong>" . _t('Has') . ":</strong> {$vs_component_count_link}";
            }
        }
        if ($vb_can_add_component) {
            $vs_buf .= ' <a href="#" onclick=\'caObjectComponentPanel.showPanel("' . caNavUrl($po_view->request, '*', 'ObjectComponent', 'Form', array('parent_id' => $t_item->getPrimaryKey())) . '"); return false;\')>' . caNavIcon($po_view->request, __CA_NAV_BUTTON_ADD__) . '</a>';
            $vo_change_type_view = new View($po_view->request, $po_view->request->getViewsDirectoryPath() . "/bundles/");
            $vo_change_type_view->setVar('t_item', $t_item);
            FooterManager::add($vo_change_type_view->render("create_component_html.php"));
        }
        //
        // Output lot info for ca_object_lots
        //
        if ($vs_table_name === 'ca_object_lots' && $t_item->getPrimaryKey()) {
            $va_component_types = $po_view->request->config->getList('ca_objects_component_types');
            if (is_array($va_component_types) && sizeof($va_component_types)) {
                $vs_buf .= "<strong>" . (($vn_num_objects = $t_item->numObjects(null, array('return' => 'objects'))) == 1 ? _t('Lot contains %1 object', $vn_num_objects) : _t('Lot contains %1 objects', $vn_num_objects)) . "</strong>\n";
                $vs_buf .= "<strong>" . (($vn_num_components = $t_item->numObjects(null, array('return' => 'components'))) == 1 ? _t('Lot contains %1 component', $vn_num_components) : _t('Lot contains %1 components', $vn_num_components)) . "</strong>\n";
            } else {
                $vs_buf .= "<strong>" . (($vn_num_objects = $t_item->numObjects()) == 1 ? _t('Lot contains %1 object', $vn_num_objects) : _t('Lot contains %1 objects', $vn_num_objects)) . "</strong>\n";
            }
            if ((bool) $po_view->request->config->get('allow_automated_renumbering_of_objects_in_a_lot') && ($va_nonconforming_objects = $t_item->getObjectsWithNonConformingIdnos())) {
                $vs_buf .= '<br/><br/><em>' . (($vn_c = sizeof($va_nonconforming_objects)) == 1 ? _t('There is %1 object with non-conforming numbering', $vn_c) : _t('There are %1 objects with non-conforming numbering', $vn_c)) . "</em>\n";
                $vs_buf .= "<a href='#' onclick='jQuery(\"#inspectorNonConformingNumberList\").toggle(250); return false;'>" . caNavIcon($po_view->request, __CA_NAV_BUTTON_ADD__);
                $vs_buf .= "<div id='inspectorNonConformingNumberList' class='inspectorNonConformingNumberList'><div class='inspectorNonConformingNumberListScroll'><ol>\n";
                foreach ($va_nonconforming_objects as $vn_object_id => $va_object_info) {
                    $vs_buf .= '<li>' . caEditorLink($po_view->request, $va_object_info['idno'], '', 'ca_objects', $vn_object_id) . "</li>\n";
                }
                $vs_buf .= "</ol></div>";
                $vs_buf .= caNavLink($po_view->request, _t('Re-number objects') . ' &rsaquo;', 'button', $po_view->request->getModulePath(), $po_view->request->getController(), 'renumberObjects', array('lot_id' => $t_item->getPrimaryKey()));
                $vs_buf .= "</div>\n";
            }
            require_once __CA_MODELS_DIR__ . '/ca_objects.php';
            $t_object = new ca_objects();
            $vs_buf .= "<div class='inspectorLotObjectTypeControls'><form action='#' id='caAddObjectToLotForm'>";
            if ((bool) $po_view->request->config->get('ca_objects_enforce_strict_type_hierarchy')) {
                // strict menu
                $vs_buf .= _t('Add new %1 to lot', $t_object->getTypeListAsHTMLFormElement('type_id', array('id' => 'caAddObjectToLotForm_type_id'), array('childrenOfCurrentTypeOnly' => true, 'directChildrenOnly' => $po_view->request->config->get('ca_objects_enforce_strict_type_hierarchy') == '~' ? false : true, 'returnHierarchyLevels' => true, 'access' => __CA_BUNDLE_ACCESS_EDIT__)));
            } else {
                // all types
                $vs_buf .= _t('Add new %1 to lot', $t_object->getTypeListAsHTMLFormElement('type_id', array('id' => 'caAddObjectToLotForm_type_id'), array('access' => __CA_BUNDLE_ACCESS_EDIT__)));
            }
            $vs_buf .= " <a href='#' onclick='caAddObjectToLotForm()'>" . caNavIcon($po_view->request, __CA_NAV_BUTTON_ADD__) . '</a>';
            $vs_buf .= "</form></div>\n";
            $vs_buf .= "<script type='text/javascript'>\n\tfunction caAddObjectToLotForm() { \n\t\twindow.location='" . caEditorUrl($po_view->request, 'ca_objects', 0, false, array('lot_id' => $t_item->getPrimaryKey(), 'rel' => 1, 'type_id' => '')) . "' + jQuery('#caAddObjectToLotForm_type_id').val();\n\t}\n\tjQuery(document).ready(function() {\n\t\tjQuery('#objectLotsNonConformingNumberList').hide();\n\t});\n</script>\n";
        }
        //
        // Output related objects for ca_object_representations
        //
        if ($vs_table_name === 'ca_object_representations') {
            foreach (array('ca_objects', 'ca_object_lots', 'ca_entities', 'ca_places', 'ca_occurrences', 'ca_collections', 'ca_storage_locations', 'ca_loans', 'ca_movements') as $vs_rel_table) {
                if (sizeof($va_objects = $t_item->getRelatedItems($vs_rel_table))) {
                    $vs_buf .= "<div><strong>" . _t("Related %1", $o_dm->getTableProperty($vs_rel_table, 'NAME_PLURAL')) . "</strong>: <br/>\n";
                    $vs_screen = '';
                    if ($t_ui = ca_editor_uis::loadDefaultUI($vs_rel_table, $po_view->request, null)) {
                        $vs_screen = $t_ui->getScreenWithBundle('ca_object_representations', $po_view->request);
                    }
                    foreach ($va_objects as $vn_rel_id => $va_rel_info) {
                        if ($vs_label = array_shift($va_rel_info['labels'])) {
                            $vs_buf .= caEditorLink($po_view->request, '&larr; ' . $vs_label . ' (' . $va_rel_info['idno'] . ')', '', $vs_rel_table, $va_rel_info[$o_dm->getTablePrimaryKeyName($vs_rel_table)], array(), array(), array('action' => 'Edit' . ($vs_screen ? "/{$vs_screen}" : ""))) . "<br/>\n";
                        }
                    }
                    $vs_buf .= "</div>\n";
                }
            }
        }
        //
        // Output related object reprsentation for ca_representation_annotation
        //
        if ($vs_table_name === 'ca_representation_annotations') {
            if ($vn_representation_id = $t_item->get('representation_id')) {
                $vs_buf .= "<div><strong>" . _t("Applied to representation") . "</strong>: <br/>\n";
                $t_rep = new ca_object_representations($vn_representation_id);
                $vs_buf .= caNavLink($po_view->request, '&larr; ' . $t_rep->getLabelForDisplay(), '', 'editor/object_representations', 'ObjectRepresentationEditor', 'Edit/' . $po_view->getVar('representation_editor_screen'), array('representation_id' => $vn_representation_id)) . '<br/>';
                $vs_buf .= "</div>\n";
            }
        }
        //
        // Output extra useful info for sets
        //
        if ($vs_table_name === 'ca_sets') {
            $vn_set_item_count = $t_item->getItemCount(array('user_id' => $po_view->request->getUserID()));
            if ($vn_set_item_count > 0 && $po_view->request->user->canDoAction('can_batch_edit_' . $o_dm->getTableName($t_item->get('table_num')))) {
                $vs_buf .= caNavButton($po_view->request, __CA_NAV_BUTTON_BATCH_EDIT__, _t('Batch edit'), 'editorBatchSetEditorLink', 'batch', 'Editor', 'Edit', array('set_id' => $t_item->getPrimaryKey()), array(), array('icon_position' => __CA_NAV_BUTTON_ICON_POS_LEFT__, 'no_background' => true, 'dont_show_content' => true));
            }
            $vs_buf .= "<div><strong>" . _t("Number of items") . "</strong>: {$vn_set_item_count}<br/>\n";
            if ($t_item->getPrimaryKey()) {
                $vn_set_table_num = $t_item->get('table_num');
                $vs_set_table_name = $o_dm->getTableName($vn_set_table_num);
                $vs_buf .= "<strong>" . _t("Type of content") . "</strong>: " . caGetTableDisplayName($vn_set_table_num) . "<br/>\n";
                $vs_buf .= "</div>\n";
                if ($po_view->request->user->canDoAction('can_duplicate_' . $vs_set_table_name)) {
                    $vs_buf .= '<div style="border-top: 1px solid #aaaaaa; margin-top: 5px; font-size: 10px; text-align: right;" ></div>';
                    $vs_buf .= caFormTag($po_view->request, 'DuplicateItems', 'caDupeSetItemsForm', 'manage/sets/SetEditor', 'post', 'multipart/form-data', '_top', array('disableUnsavedChangesWarning' => true));
                    $vs_buf .= _t("Duplicate items in this set and add to") . " ";
                    $vs_buf .= caHTMLSelect('setForDupes', array(_t('current set') => 'current', _t('new set') => 'new'));
                    $vs_buf .= caHTMLHiddenInput('set_id', array('value' => $t_item->getPrimaryKey()));
                    $vs_buf .= caFormSubmitLink($po_view->request, _t('Go') . " &rsaquo;", "button", "caDupeSetItemsForm");
                    $vs_buf .= "</form>";
                    $vs_buf .= '<div style="border-top: 1px solid #aaaaaa; margin-top: 5px; font-size: 10px; text-align: right;" ></div>';
                }
            } else {
                if ($vn_set_table_num = $po_view->request->getParameter('table_num', pInteger)) {
                    $vs_buf .= "<div><strong>" . _t("Type of content") . "</strong>: " . caGetTableDisplayName($vn_set_table_num) . "<br/>\n";
                    $vs_buf .= "</div>\n";
                }
            }
            $t_user = new ca_users(($vn_user_id = $t_item->get('user_id')) ? $vn_user_id : $po_view->request->getUserID());
            if ($t_user->getPrimaryKey()) {
                $vs_buf .= "<div><strong>" . _t('Owner') . "</strong>: " . $t_user->get('fname') . ' ' . $t_user->get('lname') . "</div>\n";
            }
            if ($po_view->request->user->canDoAction('can_export_' . $vs_set_table_name) && $t_item->getPrimaryKey() && sizeof(ca_data_exporters::getExporters($vn_set_table_num)) > 0) {
                $vs_buf .= '<div style="border-top: 1px solid #aaaaaa; margin-top: 5px; font-size: 10px; text-align: right;" id="caExportItemButton">';
                $vs_buf .= _t('Export this set of records') . "&nbsp; ";
                $vs_buf .= "<a class='button' onclick='jQuery(\"#exporterFormList\").show();' style='text-align:right;' href='#'>" . caNavIcon($po_view->request, __CA_NAV_BUTTON_ADD__) . "</a>";
                $vs_buf .= caFormTag($po_view->request, 'ExportData', 'caExportForm', 'manage/MetadataExport', 'post', 'multipart/form-data', '_top', array('disableUnsavedChangesWarning' => true));
                $vs_buf .= "<div id='exporterFormList'>";
                $vs_buf .= ca_data_exporters::getExporterListAsHTMLFormElement('exporter_id', $vn_set_table_num, array('id' => 'caExporterList'), array('width' => '135px'));
                $vs_buf .= caHTMLHiddenInput('set_id', array('value' => $t_item->getPrimaryKey()));
                $vs_buf .= caFormSubmitLink($po_view->request, _t('Export') . " &rsaquo;", "button", "caExportForm");
                $vs_buf .= "</div>\n";
                $vs_buf .= "</form>";
                $vs_buf .= "</div>";
                $vs_buf .= "<script type='text/javascript'>";
                $vs_buf .= "jQuery(document).ready(function() {";
                $vs_buf .= "jQuery(\"#exporterFormList\").hide();";
                $vs_buf .= "});";
                $vs_buf .= "</script>";
            }
        }
        //
        // Output extra useful info for set items
        //
        if ($vs_table_name === 'ca_set_items') {
            AssetLoadManager::register("panel");
            $t_set = new ca_sets();
            if ($t_set->load($vn_set_id = $t_item->get('set_id'))) {
                $vs_buf .= "<div><strong>" . _t("Part of set") . "</strong>: " . caEditorLink($po_view->request, $t_set->getLabelForDisplay(), '', 'ca_sets', $vn_set_id) . "<br/>\n";
                $t_content_instance = $t_item->getAppDatamodel()->getInstanceByTableNum($vn_item_table_num = $t_item->get('table_num'));
                if ($t_content_instance->load($vn_row_id = $t_item->get('row_id'))) {
                    $vs_label = $t_content_instance->getLabelForDisplay();
                    if ($vs_id_fld = $t_content_instance->getProperty('ID_NUMBERING_ID_FIELD')) {
                        $vs_label .= " (" . $t_content_instance->get($vs_id_fld) . ")";
                    }
                    $vs_buf .= "<strong>" . _t("Is %1", caGetTableDisplayName($vn_item_table_num, false) . "</strong>: " . caEditorLink($po_view->request, $vs_label, '', $vn_item_table_num, $vn_row_id)) . "<br/>\n";
                }
                $vs_buf .= "</div>\n";
            }
        }
        //
        // Output extra useful info for lists
        //
        if ($vs_table_name === 'ca_lists' && $t_item->getPrimaryKey()) {
            $vs_buf .= "<strong>" . _t("Number of items") . "</strong>: " . $t_item->numItemsInList() . "<br/>\n";
            $t_list_item = new ca_list_items();
            $t_list_item->load(array('list_id' => $t_item->getPrimaryKey(), 'parent_id' => null));
            $vs_type_list = $t_list_item->getTypeListAsHTMLFormElement('type_id', array('style' => 'width: 90px; font-size: 9px;'), array('access' => __CA_BUNDLE_ACCESS_EDIT__));
            if ($vs_type_list) {
                $vs_buf .= '<div style="border-top: 1px solid #aaaaaa; margin-top: 5px; font-size: 10px;">';
                $vs_buf .= caFormTag($po_view->request, 'Edit', 'NewChildForm', 'administrate/setup/list_item_editor/ListItemEditor', 'post', 'multipart/form-data', '_top', array('disableUnsavedChangesWarning' => true));
                $vs_buf .= _t('Add a %1 to this list', $vs_type_list) . caHTMLHiddenInput($t_list_item->primaryKey(), array('value' => '0')) . caHTMLHiddenInput('parent_id', array('value' => $t_list_item->getPrimaryKey()));
                $vs_buf .= caFormSubmitLink($po_view->request, caNavIcon($po_view->request, __CA_NAV_BUTTON_ADD__), '', 'NewChildForm');
                $vs_buf .= "</form></div>\n";
            }
        }
        //
        // Output containing list for list items
        //
        if ($vs_table_name === 'ca_list_items') {
            if ($t_list = $po_view->getVar('t_list')) {
                $vn_list_id = $t_list->getPrimaryKey();
                $vs_buf .= "<strong>" . _t("Part of") . "</strong>: " . caEditorLink($po_view->request, $t_list->getLabelForDisplay(), '', 'ca_lists', $vn_list_id) . "<br/>\n";
                if ($t_item->get('is_default')) {
                    $vs_buf .= "<strong>" . _t("Is default for list") . "</strong><br/>\n";
                }
            }
        }
        //
        // Output containing relationship type name for relationship types
        //
        if ($vs_table_name === 'ca_relationship_types') {
            if (!($t_rel_instance = $t_item->getAppDatamodel()->getInstanceByTableNum($t_item->get('table_num'), true))) {
                if ($vn_parent_id = $po_view->request->getParameter('parent_id', pInteger)) {
                    $t_rel_type = new ca_relationship_types($vn_parent_id);
                    $t_rel_instance = $t_item->getAppDatamodel()->getInstanceByTableNum($t_rel_type->get('table_num'), true);
                }
            }
            if ($t_rel_instance) {
                $vs_buf .= "<div><strong>" . _t("Is a") . "</strong>: " . $t_rel_instance->getProperty('NAME_SINGULAR') . "<br/></div>\n";
            }
        }
        //
        // Output extra useful info for metadata elements
        //
        if ($vs_table_name === 'ca_metadata_elements' && $t_item->getPrimaryKey()) {
            $vs_buf .= "<div><strong>" . _t("Element code") . "</strong>: " . $t_item->get('element_code') . "<br/></div>\n";
            if (sizeof($va_uis = $t_item->getUIs()) > 0) {
                $vs_buf .= "<div><strong>" . _t("Referenced by user interfaces") . "</strong>:<br/>\n";
                foreach ($va_uis as $vn_ui_id => $va_ui_info) {
                    $vs_buf .= caNavLink($po_view->request, $va_ui_info['name'], '', 'administrate/setup/interface_screen_editor', 'InterfaceScreenEditor', 'Edit', array('ui_id' => $vn_ui_id, 'screen_id' => $va_ui_info['screen_id']));
                    $vs_buf .= " (" . $o_dm->getTableProperty($va_ui_info['editor_type'], 'NAME_PLURAL') . ")<br/>\n";
                }
                $vs_buf .= "</div>\n";
            }
        }
        //
        // Output related objects for ca_editor_uis and ca_editor_ui_screens
        //
        if ($vs_table_name === 'ca_editor_uis') {
            $vs_buf .= "<div><strong>" . _t("Number of screens") . "</strong>: " . (int) $t_item->getScreenCount() . "\n";
            if ($t_item->getPrimaryKey()) {
                $vs_buf .= "<div><strong>" . _t("Edits") . "</strong>: " . caGetTableDisplayName($t_item->get('editor_type')) . "<br/>\n";
            } else {
                $vs_buf .= "<div><strong>" . _t("Edits") . "</strong>: " . caGetTableDisplayName($po_view->request->getParameter('editor_type', pInteger)) . "<br/>\n";
            }
            $vs_buf .= "</div>\n";
        }
        //
        // Output related objects for ca_editor_uis and ca_editor_ui_screens
        //
        if ($vs_table_name === 'ca_editor_ui_screens') {
            $t_ui = new ca_editor_uis($vn_ui_id = $t_item->get('ui_id'));
            $vs_buf .= "<div><strong>" . _t("Part of") . "</strong>: " . caNavLink($po_view->request, $t_ui->getLabelForDisplay(), '', 'administrate/setup/interface_editor', 'InterfaceEditor', 'Edit', array('ui_id' => $vn_ui_id)) . "\n";
            $vs_buf .= "</div>\n";
        }
        //
        // Output extra useful info for bundle displays
        //
        if ($vs_table_name === 'ca_bundle_displays') {
            $vs_buf .= "<div><strong>" . _t("Number of placements") . "</strong>: " . $t_item->getPlacementCount(array('user_id' => $po_view->request->getUserID())) . "<br/>\n";
            if ($t_item->getPrimaryKey()) {
                $vn_content_table_num = $t_item->get('table_num');
                $vs_buf .= "<strong>" . _t("Type of content") . "</strong>: " . caGetTableDisplayName($vn_content_table_num) . "\n";
                $vs_buf .= "</div>\n";
            } else {
                if ($vn_content_table_num = $po_view->request->getParameter('table_num', pInteger)) {
                    $vs_buf .= "<div><strong>" . _t("Type of content") . "</strong>: " . caGetTableDisplayName($vn_content_table_num) . "\n";
                    $vs_buf .= "</div>\n";
                }
            }
            $t_user = new ca_users(($vn_user_id = $t_item->get('user_id')) ? $vn_user_id : $po_view->request->getUserID());
            if ($t_user->getPrimaryKey()) {
                $vs_buf .= "<div><strong>" . _t('Owner') . "</strong>: " . $t_user->get('fname') . ' ' . $t_user->get('lname') . "</div>\n";
            }
        }
        //
        // Output extra useful info for search forms
        //
        if ($vs_table_name === 'ca_search_forms') {
            $vs_buf .= "<div><strong>" . _t("Number of placements") . "</strong>: " . $t_item->getPlacementCount(array('user_id' => $po_view->request->getUserID())) . "<br/>\n";
            if ($t_item->getPrimaryKey()) {
                $vn_content_table_num = $t_item->get('table_num');
                $vs_buf .= "<strong>" . _t("Searches for") . "</strong>: " . caGetTableDisplayName($vn_content_table_num) . "\n";
                $vs_buf .= "</div>\n";
            } else {
                if ($vn_content_table_num = $po_view->request->getParameter('table_num', pInteger)) {
                    $vs_buf .= "<strong>" . _t("Searches for") . "</strong>: " . caGetTableDisplayName($vn_content_table_num) . "\n";
                    $vs_buf .= "</div>\n";
                }
            }
            $t_user = new ca_users(($vn_user_id = $t_item->get('user_id')) ? $vn_user_id : $po_view->request->getUserID());
            if ($t_user->getPrimaryKey()) {
                $vs_buf .= "<div><strong>" . _t('Owner') . "</strong>: " . $t_user->get('fname') . ' ' . $t_user->get('lname') . "</div>\n";
            }
        }
        //
        // Output extra useful info for tours
        //
        if ($vs_table_name === 'ca_tours' && $t_item->getPrimaryKey()) {
            $vs_buf .= "<br/><strong>" . _t("Number of stops") . "</strong>: " . $t_item->getStopCount() . "<br/>\n";
        }
        //
        // Output containing tour for tour stops
        //
        if ($vs_table_name === 'ca_tour_stops') {
            $t_tour = new ca_tours($vn_tour_id = $t_item->get('tour_id'));
            $vs_buf .= "<strong>" . _t("Part of") . "</strong>: " . caEditorLink($po_view->request, $t_tour->getLabelForDisplay(), '', 'ca_tours', $vn_tour_id) . "<br/>\n";
        }
        //
        // Output extra useful info for bundle mappings
        //
        if ($vs_table_name === 'ca_bundle_mappings') {
            if ($t_item->getPrimaryKey()) {
                $vn_content_table_num = $t_item->get('table_num');
                $vs_buf .= "<br/><strong>" . _t("Type of content") . "</strong>: " . caGetTableDisplayName($vn_content_table_num) . "<br/>\n";
                $vs_buf .= "<strong>" . _t("Type") . "</strong>: " . $t_item->getChoiceListValue('direction', $t_item->get('direction')) . "<br/>\n";
                $vs_buf .= "<strong>" . _t("Target format") . "</strong>: " . $t_item->get('target') . "<br/>\n";
                $va_stats = $t_item->getMappingStatistics();
                $vs_buf .= "<div><strong>" . _t("Number of groups") . "</strong>: " . $va_stats['groupCount'] . "<br/>\n";
                $vs_buf .= "<strong>" . _t("Number of rules") . "</strong>: " . $va_stats['ruleCount'] . "<br/>\n";
                $vs_buf .= "</div>\n";
            } else {
                if ($vn_content_table_num = $po_view->request->getParameter('table_num', pInteger)) {
                    $vs_buf .= "<div><strong>" . _t("Type of content") . "</strong>: " . caGetTableDisplayName($vn_content_table_num) . "<br/>\n";
                    $vs_buf .= "<strong>" . _t("Type") . "</strong>: " . $t_item->getChoiceListValue('direction', $po_view->request->getParameter('direction', pString)) . "<br/>\n";
                    $vs_buf .= "<strong>" . _t("Target format") . "</strong>: " . $po_view->request->getParameter('target', pString) . "<br/>\n";
                    $vs_buf .= "<div><strong>" . _t("Number of groups") . "</strong>: 0<br/>\n";
                    $vs_buf .= "<strong>" . _t("Number of rules") . "</strong>: 0</div>\n";
                    $vs_buf .= "</div>\n";
                }
            }
        }
        //
        // Output configurable additional info from config, if set
        //
        if ($vs_additional_info = $po_view->request->config->get("{$vs_table_name}_inspector_additional_info")) {
            if (is_array($vs_additional_info)) {
                $vs_buf .= "<br/>";
                foreach ($vs_additional_info as $vs_info) {
                    $vs_buf .= caProcessTemplateForIDs($vs_info, $vs_table_name, array($t_item->getPrimaryKey()), array('requireLinkTags' => true)) . "<br/>\n";
                }
            } else {
                $vs_buf .= "<br/>" . caProcessTemplateForIDs($vs_additional_info, $vs_table_name, array($t_item->getPrimaryKey()), array('requireLinkTags' => true)) . "<br/>\n";
            }
        }
        // -------------------------------------------------------------------------------------
        // Export
        if ($t_item->getPrimaryKey() && $po_view->request->config->get($vs_table_name . '_show_add_child_control_in_inspector')) {
            $vb_show_add_child_control = true;
            if (is_array($va_restrict_add_child_control_to_types = $po_view->request->config->getList($vs_table_name . '_restrict_child_control_in_inspector_to_types')) && sizeof($va_restrict_add_child_control_to_types)) {
                $t_type_instance = $t_item->getTypeInstance();
                if (!in_array($t_type_instance->get('idno'), $va_restrict_add_child_control_to_types) && !in_array($t_type_instance->getPrimaryKey(), $va_restrict_add_child_control_to_types)) {
                    $vb_show_add_child_control = false;
                }
            }
        }
        if ($po_view->request->user->canDoAction('can_export_' . $vs_table_name) && $t_item->getPrimaryKey() && sizeof(ca_data_exporters::getExporters($t_item->tableNum())) > 0) {
            $vs_buf .= '<div style="border-top: 1px solid #aaaaaa; margin-top: 5px; font-size: 10px; text-align: right;" id="caExportItemButton">';
            $vs_buf .= _t('Export this %1', mb_strtolower($vs_type_name, 'UTF-8')) . " ";
            $vs_buf .= "<a class='button' onclick='jQuery(\"#exporterFormList\").show();' style='text-align:right;' href='#'>" . caNavIcon($po_view->request, __CA_NAV_BUTTON_ADD__) . "</a>";
            $vs_buf .= caFormTag($po_view->request, 'ExportSingleData', 'caExportForm', 'manage/MetadataExport', 'post', 'multipart/form-data', '_top', array('disableUnsavedChangesWarning' => true));
            $vs_buf .= "<div id='exporterFormList'>";
            $vs_buf .= ca_data_exporters::getExporterListAsHTMLFormElement('exporter_id', $t_item->tableNum(), array('id' => 'caExporterList'), array('width' => '120px'));
            $vs_buf .= caHTMLHiddenInput('item_id', array('value' => $t_item->getPrimaryKey()));
            $vs_buf .= caFormSubmitLink($po_view->request, _t('Export') . " &rsaquo;", "button", "caExportForm");
            $vs_buf .= "</div>\n";
            $vs_buf .= "</form>";
            $vs_buf .= "</div>";
            $vs_buf .= "<script type='text/javascript'>";
            $vs_buf .= "jQuery(document).ready(function() {";
            $vs_buf .= "jQuery(\"#exporterFormList\").hide();";
            $vs_buf .= "});";
            $vs_buf .= "</script>";
        }
        $vs_buf .= "</div></h4>\n";
        $vs_buf .= "<script type='text/javascript'>\n\t\t\tvar inspectorCookieJar = jQuery.cookieJar('caCookieJar');";
        if ($t_item->getPrimaryKey()) {
            if ($vs_more_info) {
                $vs_buf .= "\t\t\t\n\t\t\tif (inspectorCookieJar.get('inspectorMoreInfoIsOpen') == undefined) {\t\t// default is to have info open\n\t\t\t\tinspectorCookieJar.set('inspectorMoreInfoIsOpen', 1);\n\t\t\t}\n\t\t\tif (inspectorCookieJar.get('inspectorMoreInfoIsOpen') == 1) {\n\t\t\t\tjQuery('#inspectorInfo').toggle(0);\n\t\t\t\tjQuery('#inspectorMoreInfo').html('" . addslashes(caNavIcon($po_view->request, __CA_NAV_BUTTON_COLLAPSE__)) . "');\n\t\t\t}\n\t\t\n\t\t\tjQuery('#inspectorMoreInfo').click(function() {\n\t\t\t\tjQuery('#inspectorInfo').slideToggle(350, function() { \n\t\t\t\t\tinspectorCookieJar.set('inspectorMoreInfoIsOpen', (this.style.display == 'block') ? 1 : 0); \n\t\t\t\t\tjQuery('#inspectorMoreInfo').html((this.style.display == 'block') ? '" . addslashes(caNavIcon($po_view->request, __CA_NAV_BUTTON_COLLAPSE__)) . "' : '" . addslashes(caNavIcon($po_view->request, __CA_NAV_BUTTON_INFO2__)) . "');\n\t\t\t\t\tcaResizeSideNav();\n\t\t\t\t}); \n\t\t\t\treturn false;\n\t\t\t});\n\t\t";
            }
            if (sizeof($va_reps)) {
                $vs_buf .= "\n\t\tif (inspectorCookieJar.get('inspectorShowMediaIsOpen') == undefined) {\t\t// default is to have media open\n\t\t\tinspectorCookieJar.set('inspectorShowMediaIsOpen', 1);\n\t\t}\n\t\t\n\t\tif (inspectorCookieJar.get('inspectorShowMediaIsOpen') == 1) {\n\t\t\tjQuery('#inspectorMedia').toggle();\n\t\t}\n\t\n\t\tjQuery('#caColorbox').on('click', function(e) {\n\t\t\tif (e.altKey) {\n\t\t\t\tjQuery('#inspectorMedia').slideToggle(200, function() { \n\t\t\t\t\tinspectorCookieJar.set('inspectorShowMediaIsOpen', (this.style.display == 'block') ? 1 : 0); \n\t\t\t\t\t\tcaResizeSideNav();\n\t\t\t\t}); \n\t\t\t\treturn false;\n\t\t\t}\n\t\t});\n\t\t\t\t\t";
            }
        }
        $vs_buf .= "</script>\n";
    }
    $o_app_plugin_manager = new ApplicationPluginManager();
    $va_hookAppend = $o_app_plugin_manager->hookAppendToEditorInspector(array("t_item" => $t_item));
    if (is_string($va_hookAppend["caEditorInspectorAppend"])) {
        $vs_buf .= $va_hookAppend["caEditorInspectorAppend"];
    }
    return $vs_buf;
}
 /**
  *
  */
 public function refine(&$pa_destination_data, $pa_group, $pa_item, $pa_source_data, $pa_options = null)
 {
     $o_log = isset($pa_options['log']) && is_object($pa_options['log']) ? $pa_options['log'] : null;
     $pm_value = $pa_source_data[$pa_item['source']];
     // not actually used
     $va_item_dest = explode(".", $pa_item['destination']);
     $vs_item_terminal = array_pop($va_item_dest);
     $vs_group_terminal = array_pop($va_item_dest);
     $o_tep = new TimeExpressionParser();
     $o_tep->setLanguage('en_US');
     switch ($vs_mode = $pa_item['settings']['dateJoiner_mode']) {
         default:
         case 'range':
             $vs_date_expression = $pa_item['settings']['dateJoiner_expression'];
             $vs_date_start = $pa_item['settings']['dateJoiner_start'];
             $vs_date_end = $pa_item['settings']['dateJoiner_end'];
             if ($vs_date_expression && ($vs_exp = BaseRefinery::parsePlaceholder($vs_date_expression, $pa_source_data, $pa_item, $vn_c, array('reader' => caGetOption('reader', $pa_options, null), 'returnAsString' => true, 'delimiter' => ' ')))) {
                 if ($o_tep->parse($vs_exp)) {
                     return $o_tep->getText();
                 } else {
                     if ($o_log) {
                         $o_log->logWarn(_t('[dateJoinerRefinery] Could not parse date expression %1 assembled from range', $vs_exp));
                     }
                 }
             }
             $va_date = array();
             if ($vs_date_start = BaseRefinery::parsePlaceholder($vs_date_start, $pa_source_data, $pa_item, $vn_c, array('reader' => caGetOption('reader', $pa_options, null), 'returnAsString' => true, 'delimiter' => ' '))) {
                 if (!($vs_skip_start_exp = $pa_item['settings']['dateJoiner_skipStartIfExpression']) || !ExpressionParser::evaluate($vs_skip_start_exp, array_merge($pa_source_data, array('start' => $vs_date_start, 'end' => $vs_date_end, 'expression' => $ps_expression)))) {
                     $va_date[] = $vs_date_start;
                 } elseif ($vs_skip_start_replacement = $pa_item['settings']['dateJoiner_skipStartIfExpressionReplacementValue']) {
                     $va_date[] = $vs_skip_start_replacement;
                 }
             }
             if ($vs_date_end = BaseRefinery::parsePlaceholder($vs_date_end, $pa_source_data, $pa_item, $vn_c, array('reader' => caGetOption('reader', $pa_options, null), 'returnAsString' => true, 'delimiter' => ' '))) {
                 if (!($vs_skip_end_exp = $pa_item['settings']['dateJoiner_skipEndIfExpression']) || !ExpressionParser::evaluate($vs_skip_end_exp, array_merge($pa_source_data, array('start' => $vs_date_start, 'end' => $vs_date_end, 'expression' => $ps_expression)))) {
                     $va_date[] = $vs_date_end;
                 } elseif ($vs_skip_end_replacement = $pa_item['settings']['dateJoiner_skipEndIfExpressionReplacementValue']) {
                     $va_date[] = $vs_skip_end_replacement;
                 }
             }
             foreach ($va_date as $vn_i => $vs_date) {
                 $va_date[$vn_i] = preg_replace("![^\\d]+\$!", "", $vs_date);
             }
             $vs_date_expression = join(" - ", $va_date);
             if ($vs_date_expression && ($vs_exp = BaseRefinery::parsePlaceholder($vs_date_expression, $pa_source_data, $pa_item, $vn_c, array('reader' => caGetOption('reader', $pa_options, null), 'returnAsString' => true, 'delimiter' => ' ')))) {
                 if ($o_tep->parse($vs_exp)) {
                     return $o_tep->getText();
                 } else {
                     if ($o_log) {
                         $o_log->logWarn(_t('[dateJoinerRefinery] Could not parse date expression %1 assembled from range', $vs_exp));
                     }
                 }
             }
             break;
         case 'multiColumnDate':
             $va_month_list = $o_tep->getMonthList();
             $va_date = array();
             if ($vs_date_month = trim(BaseRefinery::parsePlaceholder($pa_item['settings']['dateJoiner_month'], $pa_source_data, $pa_item, $vn_c, array('reader' => caGetOption('reader', $pa_options, null), 'returnAsString' => true, 'delimiter' => ' ')))) {
                 if (($vn_m = array_search($vs_date_month, $va_month_list)) !== false) {
                     $vs_date_month = $vn_m + 1;
                 }
                 $va_date[] = $vs_date_month;
             }
             if ($vs_date_day = trim(BaseRefinery::parsePlaceholder($pa_item['settings']['dateJoiner_day'], $pa_source_data, $pa_item, $vn_c, array('reader' => caGetOption('reader', $pa_options, null), 'returnAsString' => true, 'delimiter' => ' ')))) {
                 $va_date[] = $vs_date_day;
             }
             if ($vs_date_year = trim(BaseRefinery::parsePlaceholder($pa_item['settings']['dateJoiner_year'], $pa_source_data, $pa_item, $vn_c, array('reader' => caGetOption('reader', $pa_options, null), 'returnAsString' => true, 'delimiter' => ' ')))) {
                 $va_date[] = $vs_date_year;
             }
             if (sizeof($va_date)) {
                 // TODO: this is assuming US-style dates for now
                 if ($o_tep->parse(join("/", $va_date))) {
                     return $o_tep->getText();
                 } else {
                     if ($o_log) {
                         $o_log->logWarn(_t('[dateJoinerRefinery] Could not parse date expression %1 assembled from multiColumnDate', join("/", $va_date)));
                     }
                 }
             }
             break;
         case 'multiColumnRange':
             $va_dates = array();
             $va_month_list = $o_tep->getMonthList();
             // Process start date
             $va_date = array();
             if ($vs_date_month = trim(BaseRefinery::parsePlaceholder($pa_item['settings']['dateJoiner_startMonth'], $pa_source_data, $pa_item, $vn_c, array('reader' => caGetOption('reader', $pa_options, null), 'returnAsString' => true, 'delimiter' => ' ')))) {
                 if (($vn_m = array_search($vs_date_month, $va_month_list)) !== false) {
                     $vs_date_month = $vn_m + 1;
                 }
                 $va_date[] = $vs_date_month;
             }
             if ($vs_date_day = trim(BaseRefinery::parsePlaceholder($pa_item['settings']['dateJoiner_startDay'], $pa_source_data, $pa_item, $vn_c, array('reader' => caGetOption('reader', $pa_options, null), 'returnAsString' => true, 'delimiter' => ' ')))) {
                 $va_date[] = $vs_date_day;
             }
             if ($vs_date_year = trim(BaseRefinery::parsePlaceholder($pa_item['settings']['dateJoiner_startYear'], $pa_source_data, $pa_item, $vn_c, array('reader' => caGetOption('reader', $pa_options, null), 'returnAsString' => true, 'delimiter' => ' ')))) {
                 $va_date[] = $vs_date_year;
             }
             if (sizeof($va_date)) {
                 if ($o_tep->parse(join("/", $va_date))) {
                     // TODO: this is assuming US-style dates for now
                     $va_dates[] = $o_tep->getText();
                 } else {
                     if ($o_log) {
                         $o_log->logWarn(_t('[dateJoinerRefinery] Could not parse date expression %1 assembled from multiColumnRange', join("/", $va_date)));
                     }
                 }
             }
             // Process end date
             $va_date = array();
             if ($vs_date_month = trim(BaseRefinery::parsePlaceholder($pa_item['settings']['dateJoiner_endMonth'], $pa_source_data, $pa_item, $vn_c, array('reader' => caGetOption('reader', $pa_options, null), 'returnAsString' => true, 'delimiter' => ' ')))) {
                 if (($vn_m = array_search($vs_date_month, $va_month_list)) !== false) {
                     $vs_date_month = $vn_m + 1;
                 }
                 $va_date[] = $vs_date_month;
             }
             if ($vs_date_day = trim(BaseRefinery::parsePlaceholder($pa_item['settings']['dateJoiner_endDay'], $pa_source_data, $pa_item, $vn_c, array('reader' => caGetOption('reader', $pa_options, null), 'returnAsString' => true, 'delimiter' => ' ')))) {
                 $va_date[] = $vs_date_day;
             }
             if ($vs_date_year = trim(BaseRefinery::parsePlaceholder($pa_item['settings']['dateJoiner_endYear'], $pa_source_data, $pa_item, $vn_c, array('reader' => caGetOption('reader', $pa_options, null), 'returnAsString' => true, 'delimiter' => ' ')))) {
                 $va_date[] = $vs_date_year;
             }
             if (sizeof($va_date)) {
                 if ($o_tep->parse(join("/", $va_date))) {
                     // TODO: this is assuming US-style dates for now
                     $va_dates[] = $o_tep->getText();
                 } else {
                     if ($o_log) {
                         $o_log->logWarn(_t('[dateJoinerRefinery] Could not parse date expression %1 assembled from multiColumnRange', join("/", $va_date)));
                     }
                 }
             }
             if (sizeof($va_dates) > 0) {
                 return join(" - ", $va_dates);
             }
             break;
     }
     return null;
 }
 /**
  * Random expressions we used for testing while writing the AST processor. Might as well leave them in here
  */
 public function testEvaluate()
 {
     $this->assertTrue(ExpressionParser::evaluate('"Software is great" =~ /Soft/'));
     $this->assertFalse(ExpressionParser::evaluate('"Software is great" =~ /soft/'));
     $this->assertFalse(ExpressionParser::evaluate('"Software is great" !~ /Soft/'));
     $this->assertTrue(ExpressionParser::evaluate('"Software is great" !~ /soft/'));
     $this->assertEquals(true, ExpressionParser::evaluate('5 > 1'));
     $this->assertEquals(false, ExpressionParser::evaluate('5 > 6'));
     $this->assertEquals(false, ExpressionParser::evaluate('5 < 1'));
     $this->assertEquals(true, ExpressionParser::evaluate('5 < 6'));
     $this->assertEquals(false, ExpressionParser::evaluate('5 <= 1'));
     $this->assertEquals(true, ExpressionParser::evaluate('5 <= 6'));
     $this->assertEquals(true, ExpressionParser::evaluate('5 <= 5'));
     $this->assertEquals(true, ExpressionParser::evaluate('5 >= 1'));
     $this->assertEquals(false, ExpressionParser::evaluate('5 >= 6'));
     $this->assertEquals(true, ExpressionParser::evaluate('5 >= 5'));
     $this->assertEquals(true, ExpressionParser::evaluate('5 != 1'));
     $this->assertEquals(false, ExpressionParser::evaluate('5 != 5'));
     $this->assertEquals(false, ExpressionParser::evaluate('5 = 1'));
     $this->assertEquals(true, ExpressionParser::evaluate('5 = 5'));
     $this->assertEquals(true, ExpressionParser::evaluate('"Seth" IN ["Julia", "Allison", "Sophie", "Maria", "Angie", "Seth"]'));
     $this->assertEquals(false, ExpressionParser::evaluate('"Joe" IN ["Julia", "Allison", "Sophie", "Maria", "Angie", "Seth"]'));
     $this->assertEquals(false, ExpressionParser::evaluate('"Seth" NOT IN ["Julia", "Allison", "Sophie", "Maria", "Angie", "Seth"]'));
     $this->assertEquals(true, ExpressionParser::evaluate('"Joe" NOT IN ["Julia", "Allison", "Sophie", "Maria", "Angie", "Seth"]'));
     $this->assertEquals(3.1725, ExpressionParser::evaluate('avg(abs(-1.345), max(4,5))'));
     $this->assertEquals(4, ExpressionParser::evaluate('length("test")'));
     $this->assertEquals(1, ExpressionParser::evaluate('length(4)'));
     $this->assertEquals('Stefan parses', ExpressionParser::evaluate('"Stefan" + " parses"'));
     $this->assertEquals(11, ExpressionParser::evaluate('6 + 5'));
     $this->assertEquals(15, ExpressionParser::evaluate('6 + 5 + 4'));
     $this->assertEquals(1, ExpressionParser::evaluate('6 - 5'));
     $this->assertEquals(30, ExpressionParser::evaluate('6 * 5'));
     $this->assertEquals(2, ExpressionParser::evaluate('4 ÷ 2'));
     $this->assertEquals(49.5, ExpressionParser::evaluate('1 ÷ 2 ÷ 3 + 4 * (5 * 2 - 6) * 3'));
     $this->assertEquals(13, ExpressionParser::evaluate('5 + 4 * 2'));
     $this->assertEquals(14, ExpressionParser::evaluate('5 * 2 + 4'));
 }
 /**
  * Returns array of attributes, each of which is an array of values ready for display. 
  * The returned array is indexed by attribute element ID. Each array value is an array
  * indexed by attribute_id. The corresponding values are arrays of attribute values indexed by element_code
  *
  * @param $pm_element_code_or_id string|integer -
  * @param $pn_row_id integer -
  * @param $pa_options array -
  *				convertLineBreaks = if set to true, will attempt to convert line break characters to HTML <p> and <br> tags; default is false.
  *				locale = if set to a valid locale_id or locale code, values will be returned in locale *if available*, otherwise will fallback to values in languages that are available using the standard fallback mechanism. Default is to use user's current locale.
  *				returnAllLocales = if set to true, values for all locales are returned, locale option is ignored and the returned array is indexed first by attribute_id and then by locale_id. Default is false.
  *				indexByRowID = if true first index of returned array is $pn_row_id, otherwise it is the element_id of the retrieved metadata element	
  *				convertCodesToDisplayText =
  *				filter =
  * @return array
  */
 public function getAttributeDisplayValues($pm_element_code_or_id, $pn_row_id, $pa_options = null)
 {
     if (!is_array($pa_options)) {
         $pa_options = array();
     }
     $ps_filter_expression = caGetOption('filter', $pa_options, null);
     $va_attribute_list = $this->getAttributesByElement($pm_element_code_or_id, array('row_id' => $pn_row_id));
     if (!is_array($va_attribute_list)) {
         return array();
     }
     $va_attributes = array();
     $vs_table_name = $this->tableName();
     $vs_container_element_code = $this->_getElementCode($pm_element_code_or_id);
     foreach ($va_attribute_list as $o_attribute) {
         $va_values = $o_attribute->getValues();
         $va_raw_values = array();
         $va_display_values = array();
         foreach ($va_values as $o_value) {
             $vs_element_code = $this->_getElementCode($o_value->getElementID());
             if (get_class($o_value) == 'ListAttributeValue') {
                 $t_element = $this->_getElementInstance($o_value->getElementID());
                 $vn_list_id = !isset($pa_options['convertCodesToDisplayText']) || !$pa_options['convertCodesToDisplayText'] ? null : $t_element->get('list_id');
             } else {
                 $vn_list_id = null;
             }
             if ($ps_filter_expression) {
                 $va_raw_values[$vs_container_element_code == $vs_element_code ? "{$vs_table_name}.{$vs_element_code}" : "{$vs_table_name}.{$vs_container_element_code}.{$vs_element_code}"] = $va_raw_values[$vs_element_code] = $o_value->getDisplayValue(array('list_id' => $vn_list_id, 'returnIdno' => true));
             }
             if (isset($pa_options['convertLineBreaks']) && $pa_options['convertLineBreaks']) {
                 $vs_converted_value = preg_replace("!(\n|\r\n){2}!", "<p/>", $o_value->getDisplayValue(array_merge($pa_options, array('list_id' => $vn_list_id))));
                 $va_display_values[$vs_element_code] = preg_replace("![\n]{1}!", "<br/>", $vs_converted_value);
             } else {
                 $va_display_values[$vs_element_code] = $o_value->getDisplayValue(array_merge($pa_options, array('list_id' => $vn_list_id)));
             }
         }
         // Process filter if defined
         if ($ps_filter_expression && !ExpressionParser::evaluate($ps_filter_expression, $va_raw_values)) {
             continue;
         }
         if (isset($pa_options['indexByRowID']) && $pa_options['indexByRowID']) {
             $vs_index = $pn_row_id;
         } else {
             $vs_index = $o_attribute->getElementID();
         }
         $va_attributes[$vs_index][(int) $o_attribute->getLocaleID()][$o_attribute->getAttributeID()] = $va_display_values;
     }
     if (!isset($pa_options['returnAllLocales']) || !$pa_options['returnAllLocales']) {
         // if desired try to return values in a preferred language/locale
         $va_preferred_locales = null;
         if (isset($pa_options['locale']) && $pa_options['locale']) {
             $va_preferred_locales = array($pa_options['locale']);
         }
         return caExtractValuesByUserLocale($va_attributes, null, $va_preferred_locales, array());
     }
     return $va_attributes;
 }
 /**
  *
  */
 private static function _processChildren(SearchResult $pr_res, $po_nodes, array $pa_vals, array $pa_options = null)
 {
     if (!is_array($pa_options)) {
         $pa_options = [];
     }
     if (!$po_nodes) {
         return '';
     }
     $vs_acc = '';
     $ps_tablename = $pr_res->tableName();
     $o_dm = Datamodel::load();
     $t_instance = $o_dm->getInstanceByTableName($ps_tablename, true);
     $ps_delimiter = caGetOption('delimiter', $pa_options, '; ');
     $pb_is_case = caGetOption('isCase', $pa_options, false, ['castTo' => 'boolean']);
     $pb_quote = caGetOption('quote', $pa_options, false, ['castTo' => 'boolean']);
     $pa_primary_ids = caGetOption('primaryIDs', $pa_options, null);
     $pb_include_blanks = caGetOption('includeBlankValuesInArray', $pa_options, false);
     unset($pa_options['quote']);
     $vn_last_unit_omit_count = null;
     foreach ($po_nodes as $vn_index => $o_node) {
         switch ($vs_tag = strtolower($o_node->tag)) {
             case 'case':
                 if (!$pb_is_case) {
                     $vs_acc .= DisplayTemplateParser::_processChildren($pr_res, $o_node->children, $pa_vals, array_merge($pa_options, ['isCase' => true]));
                 }
                 break;
             case 'if':
                 if (strlen($vs_rule = $o_node->rule) && ExpressionParser::evaluate($vs_rule, $pa_vals)) {
                     $vs_acc .= DisplayTemplateParser::_processChildren($pr_res, $o_node->children, DisplayTemplateParser::_getValues($pr_res, DisplayTemplateParser::_getTags($o_node->children, $pa_options), $pa_options), $pa_options);
                     if ($pb_is_case) {
                         break 2;
                     }
                 }
                 break;
             case 'ifdef':
             case 'ifnotdef':
                 $vb_defined = DisplayTemplateParser::_evaluateCodeAttribute($pr_res, $o_node, ['index' => caGetOption('index', $pa_options, null), 'mode' => $vs_tag == 'ifdef' ? 'present' : 'not_present']);
                 if ($vs_tag == 'ifdef' && $vb_defined || $vs_tag == 'ifnotdef' && $vb_defined) {
                     // Make sure returned values are not empty
                     $vs_acc .= DisplayTemplateParser::_processChildren($pr_res, $o_node->children, DisplayTemplateParser::_getValues($pr_res, DisplayTemplateParser::_getTags($o_node->children, $pa_options), $pa_options), $pa_options);
                     if ($pb_is_case) {
                         break 2;
                     }
                 }
                 break;
             case 'ifcount':
                 $vn_min = (int) $o_node->min;
                 $vn_max = (int) $o_node->max;
                 if (!is_array($va_codes = DisplayTemplateParser::_getCodesFromAttribute($o_node)) || !sizeof($va_codes)) {
                     break;
                 }
                 $pa_check_access = $t_instance->hasField('access') ? caGetOption('checkAccess', $pa_options, null) : null;
                 if (!is_array($pa_check_access) || !sizeof($pa_check_access)) {
                     $pa_check_access = null;
                 }
                 $vb_bool = DisplayTemplateParser::_getCodesBooleanModeAttribute($o_node);
                 $va_restrict_to_types = DisplayTemplateParser::_getCodesFromAttribute($o_node, ['attribute' => 'restrictToTypes']);
                 $va_exclude_types = DisplayTemplateParser::_getCodesFromAttribute($o_node, ['attribute' => 'excludeTypes']);
                 $va_restrict_to_relationship_types = DisplayTemplateParser::_getCodesFromAttribute($o_node, ['attribute' => 'restrictToRelationshipTypes']);
                 $va_exclude_to_relationship_types = DisplayTemplateParser::_getCodesFromAttribute($o_node, ['attribute' => 'excludeRelationshipTypes']);
                 $vm_count = $vb_bool == 'AND' ? 0 : [];
                 foreach ($va_codes as $vs_code) {
                     $va_vals = $pr_res->get($vs_code, ['checkAccess' => $pa_check_access, 'returnAsArray' => true, 'restrictToTypes' => $va_restrict_to_types, 'excludeTypes' => $va_exclude_types, 'restrictToRelationshipTypes' => $va_restrict_to_relationship_types, 'excludeRelationshipTypes' => $va_exclude_to_relationship_types]);
                     if (is_array($va_vals)) {
                         if ($vb_bool == 'AND') {
                             $vm_count += sizeof($va_vals);
                         } else {
                             $vm_count[$vs_code] = sizeof($va_vals);
                         }
                     }
                 }
                 if ($vb_bool == 'AND') {
                     if ($vn_min <= $vm_count && ($vn_max >= $vm_count || !$vn_max)) {
                         $vs_acc .= DisplayTemplateParser::_processChildren($pr_res, $o_node->children, DisplayTemplateParser::_getValues($pr_res, DisplayTemplateParser::_getTags($o_node->children, $pa_options), $pa_options), $pa_options);
                         if ($pb_is_case) {
                             break 2;
                         }
                     }
                 } else {
                     $vb_all_have_count = true;
                     foreach ($vm_count as $vs_code => $vn_count) {
                         if (!($vn_min <= $vn_count && ($vn_max >= $vn_count || !$vn_max))) {
                             $vb_all_have_count = false;
                             break 2;
                         }
                     }
                     if ($vb_all_have_count) {
                         $vs_acc .= DisplayTemplateParser::_processChildren($pr_res, $o_node->children, $pa_vals, $pa_options);
                         if ($pb_is_case) {
                             break 2;
                         }
                     }
                 }
                 break;
             case 'more':
                 // Does a placeholder with value follow this tag?
                 // NOTE: 	We don't take into account <ifdef> and friends when finding a set placeholder; it may be set but not visible due to a conditional
                 // 			This case is not covered at the moment on the assumption that if you're using <more> you're not using conditionals. This may or may not be a good assumption.
                 for ($vn_i = $vn_index + 1; $vn_i < sizeof($po_nodes); $vn_i++) {
                     if ($po_nodes[$vn_i] && $po_nodes[$vn_i]->tag == '~text~' && is_array($va_following_tags = caGetTemplateTags($po_nodes[$vn_i]->text))) {
                         foreach ($va_following_tags as $vs_following_tag) {
                             if (isset($pa_vals[$vs_following_tag]) && strlen($pa_vals[$vs_following_tag])) {
                                 $vs_acc .= DisplayTemplateParser::_processChildren($pr_res, $o_node->children, $pa_vals, $pa_options);
                                 if ($pb_is_case) {
                                     break 2;
                                 }
                             }
                         }
                     }
                 }
                 break;
             case 'between':
                 // Does a placeholder with value precede this tag?
                 // NOTE: 	We don't take into account <ifdef> and friends when finding a set placeholder; it may be set but not visible due to a conditional
                 // 			This case is not covered at the moment on the assumption that if you're using <between> you're not using conditionals. This may or may not be a good assumption.
                 $vb_has_preceding_value = false;
                 for ($vn_i = 0; $vn_i < $vn_index; $vn_i++) {
                     if ($po_nodes[$vn_i] && $po_nodes[$vn_i]->tag == '~text~' && is_array($va_preceding_tags = caGetTemplateTags($po_nodes[$vn_i]->text))) {
                         foreach ($va_preceding_tags as $vs_preceding_tag) {
                             if (isset($pa_vals[$vs_preceding_tag]) && strlen($pa_vals[$vs_preceding_tag])) {
                                 $vb_has_preceding_value = true;
                             }
                         }
                     }
                 }
                 if ($vb_has_preceding_value) {
                     // Does it have a value immediately following it?
                     for ($vn_i = $vn_index + 1; $vn_i < sizeof($po_nodes); $vn_i++) {
                         if ($po_nodes[$vn_i] && $po_nodes[$vn_i]->tag == '~text~' && is_array($va_following_tags = caGetTemplateTags($po_nodes[$vn_i]->text))) {
                             foreach ($va_following_tags as $vs_following_tag) {
                                 if (isset($pa_vals[$vs_following_tag]) && strlen($pa_vals[$vs_following_tag])) {
                                     $vs_acc .= DisplayTemplateParser::_processChildren($pr_res, $o_node->children, $pa_vals, $pa_options);
                                     if ($pb_is_case) {
                                         break 2;
                                     }
                                 }
                                 break;
                             }
                         }
                     }
                 }
                 break;
             case 'expression':
                 if ($vs_exp = trim($o_node->getInnerText())) {
                     $vs_acc .= ExpressionParser::evaluate(DisplayTemplateParser::_processChildren($pr_res, $o_node->children, DisplayTemplateParser::_getValues($pr_res, DisplayTemplateParser::_getTags($o_node->children, $pa_options), $pa_options), array_merge($pa_options, ['quote' => true])), $pa_vals);
                     if ($pb_is_case) {
                         break 2;
                     }
                 }
                 break;
             case 'unit':
                 $va_relative_to_tmp = $o_node->relativeTo ? explode(".", $o_node->relativeTo) : [$ps_tablename];
                 if ($va_relative_to_tmp[0] && !($t_rel_instance = $o_dm->getInstanceByTableName($va_relative_to_tmp[0], true))) {
                     continue;
                 }
                 $vn_last_unit_omit_count = 0;
                 // <unit> attributes
                 $vs_unit_delimiter = $o_node->delimiter ? (string) $o_node->delimiter : $ps_delimiter;
                 $vb_unique = $o_node->unique ? (bool) $o_node->unique : false;
                 $vb_aggregate_unique = $o_node->aggregateUnique ? (bool) $o_node->aggregateUnique : false;
                 $vs_unit_skip_if_expression = (string) $o_node->skipIfExpression;
                 $vn_start = (int) $o_node->start;
                 $vn_length = (int) $o_node->length;
                 $pa_check_access = $t_instance->hasField('access') ? caGetOption('checkAccess', $pa_options, null) : null;
                 if (!is_array($pa_check_access) || !sizeof($pa_check_access)) {
                     $pa_check_access = null;
                 }
                 // additional get options for pulling related records
                 $va_get_options = ['returnAsArray' => true, 'checkAccess' => $pa_check_access];
                 $va_get_options['restrictToTypes'] = DisplayTemplateParser::_getCodesFromAttribute($o_node, ['attribute' => 'restrictToTypes']);
                 $va_get_options['excludeTypes'] = DisplayTemplateParser::_getCodesFromAttribute($o_node, ['attribute' => 'excludeTypes']);
                 $va_get_options['restrictToRelationshipTypes'] = DisplayTemplateParser::_getCodesFromAttribute($o_node, ['attribute' => 'restrictToRelationshipTypes']);
                 $va_get_options['excludeRelationshipTypes'] = DisplayTemplateParser::_getCodesFromAttribute($o_node, ['attribute' => 'excludeRelationshipTypes']);
                 if ($o_node->sort) {
                     $va_get_options['sort'] = preg_split('![ ,;]+!', $o_node->sort);
                     $va_get_options['sortDirection'] = $o_node->sortDirection;
                 }
                 if (sizeof($va_relative_to_tmp) == 1 && $va_relative_to_tmp[0] == $ps_tablename || sizeof($va_relative_to_tmp) >= 1 && $va_relative_to_tmp[0] == $ps_tablename && $va_relative_to_tmp[1] != 'related') {
                     $vs_relative_to_container = null;
                     switch (strtolower($va_relative_to_tmp[1])) {
                         case 'hierarchy':
                             $va_relative_ids = $pr_res->get($t_rel_instance->tableName() . ".hierarchy." . $t_rel_instance->primaryKey(), $va_get_options);
                             $va_relative_ids = array_values($va_relative_ids);
                             break;
                         case 'parent':
                             $va_relative_ids = $pr_res->get($t_rel_instance->tableName() . ".parent." . $t_rel_instance->primaryKey(), $va_get_options);
                             $va_relative_ids = array_values($va_relative_ids);
                             break;
                         case 'children':
                             $va_relative_ids = $pr_res->get($t_rel_instance->tableName() . ".children." . $t_rel_instance->primaryKey(), $va_get_options);
                             $va_relative_ids = array_values($va_relative_ids);
                             break;
                         case 'siblings':
                             $va_relative_ids = $pr_res->get($t_rel_instance->tableName() . ".siblings." . $t_rel_instance->primaryKey(), $va_get_options);
                             $va_relative_ids = array_values($va_relative_ids);
                             break;
                         default:
                             // If relativeTo is not set to a valid attribute try to guess from template
                             if ($t_rel_instance->isValidMetadataElement(join(".", array_slice($va_relative_to_tmp, 1, 1)), true)) {
                                 $vs_relative_to_container = join(".", array_slice($va_relative_to_tmp, 0, 2));
                             } else {
                                 $va_tags = caGetTemplateTags($o_node->getInnerText());
                                 foreach ($va_tags as $vs_tag) {
                                     $va_tag = explode('.', $vs_tag);
                                     while (sizeof($va_tag) > 1) {
                                         $vs_end = array_pop($va_tag);
                                         if ($t_rel_instance->isValidMetadataElement($vs_end, true)) {
                                             $va_tag[] = $vs_end;
                                             $vs_relative_to_container = join(".", $va_tag);
                                             break 2;
                                         }
                                     }
                                 }
                             }
                             $va_relative_ids = array($pr_res->getPrimaryKey());
                             break;
                     }
                     // process template for all records selected by unit tag
                     $va_tmpl_val = DisplayTemplateParser::evaluate($o_node->getInnerText(), $ps_tablename, $va_relative_ids, array_merge($pa_options, ['sort' => $va_get_options['sort'], 'sortDirection' => $va_get_options['sortDirection'], 'returnAsArray' => true, 'delimiter' => $vs_unit_delimiter, 'skipIfExpression' => $vs_unit_skip_if_expression, 'placeholderPrefix' => (string) $o_node->relativeTo, 'restrictToTypes' => $va_get_options['restrictToTypes'], 'excludeTypes' => $va_get_options['excludeTypes'], 'isUnit' => true, 'unitStart' => $vn_start, 'unitLength' => $vn_length, 'relativeToContainer' => $vs_relative_to_container, 'includeBlankValuesInTopLevelForPrefetch' => false, 'unique' => $vb_unique, 'aggregateUnique' => $vb_aggregate_unique]));
                     if ($vb_unique) {
                         $va_tmpl_val = array_unique($va_tmpl_val);
                     }
                     if ($vn_start > 0 || !is_null($vn_length)) {
                         $vn_last_unit_omit_count = sizeof($va_tmpl_val) - ($vn_length - $vn_start);
                     }
                     if (caGetOption('returnAsArray', $pa_options, false)) {
                         return $va_tmpl_val;
                     }
                     $vs_acc .= join($vs_unit_delimiter, $va_tmpl_val);
                     if ($pb_is_case) {
                         break 2;
                     }
                 } else {
                     switch (strtolower($va_relative_to_tmp[1])) {
                         case 'hierarchy':
                             $va_relative_ids = $pr_res->get($t_rel_instance->tableName() . ".hierarchy." . $t_rel_instance->primaryKey(), $va_get_options);
                             $va_relative_ids = array_values($va_relative_ids);
                             break;
                         case 'parent':
                             $va_relative_ids = $pr_res->get($t_rel_instance->tableName() . ".parent." . $t_rel_instance->primaryKey(), $va_get_options);
                             $va_relative_ids = array_values($va_relative_ids);
                             break;
                         case 'children':
                             $va_relative_ids = $pr_res->get($t_rel_instance->tableName() . ".children." . $t_rel_instance->primaryKey(), $va_get_options);
                             $va_relative_ids = array_values($va_relative_ids);
                             break;
                         case 'siblings':
                             $va_relative_ids = $pr_res->get($t_rel_instance->tableName() . ".siblings." . $t_rel_instance->primaryKey(), $va_get_options);
                             $va_relative_ids = array_values($va_relative_ids);
                             break;
                         case 'related':
                             $va_relative_ids = $pr_res->get($t_rel_instance->tableName() . ".related." . $t_rel_instance->primaryKey(), $va_get_options);
                             $va_relative_ids = array_values($va_relative_ids);
                             break;
                         default:
                             if (method_exists($t_instance, 'isSelfRelationship') && $t_instance->isSelfRelationship() && is_array($pa_primary_ids) && isset($pa_primary_ids[$t_rel_instance->tableName()])) {
                                 $va_relative_ids = array_values($t_instance->getRelatedIDsForSelfRelationship($pa_primary_ids[$t_rel_instance->tableName()], array($pr_res->getPrimaryKey())));
                             } else {
                                 $va_relative_ids = $pr_res->get($t_rel_instance->primaryKey(true), $va_get_options);
                                 $va_relative_ids = is_array($va_relative_ids) ? array_values($va_relative_ids) : array();
                             }
                             break;
                     }
                     $va_tmpl_val = DisplayTemplateParser::evaluate($o_node->getInnerText(), $va_relative_to_tmp[0], $va_relative_ids, array_merge($pa_options, ['sort' => $va_unit['sort'], 'sortDirection' => $va_unit['sortDirection'], 'delimiter' => $vs_unit_delimiter, 'returnAsArray' => true, 'skipIfExpression' => $vs_unit_skip_if_expression, 'placeholderPrefix' => (string) $o_node->relativeTo, 'restrictToTypes' => $va_get_options['restrictToTypes'], 'excludeTypes' => $va_get_options['excludeTypes'], 'isUnit' => true, 'unitStart' => $vn_start, 'unitLength' => $vn_length, 'includeBlankValuesInTopLevelForPrefetch' => false, 'unique' => $vb_unique, 'aggregateUnique' => $vb_aggregate_unique]));
                     if ($vb_unique) {
                         $va_tmpl_val = array_unique($va_tmpl_val);
                     }
                     if ($vn_start > 0 || !is_null($vn_length)) {
                         $vn_num_vals = sizeof($va_tmpl_val);
                         $va_tmpl_val = array_slice($va_tmpl_val, $vn_start, $vn_length > 0 ? $vn_length : null);
                         $vn_last_unit_omit_count = $vn_num_vals - ($vn_length - $vn_start);
                     }
                     if (caGetOption('returnAsArray', $pa_options, false)) {
                         return $va_tmpl_val;
                     }
                     $vs_acc .= join($vs_unit_delimiter, $va_tmpl_val);
                     if ($pb_is_case) {
                         break 2;
                     }
                 }
                 break;
             case 'whenunitomits':
                 if ($vn_last_unit_omit_count > 0) {
                     $vs_proc_template = caProcessTemplate($o_node->getInnerText(), array_merge($pa_vals, ['omitcount' => (int) $vn_last_unit_omit_count]), ['quote' => $pb_quote]);
                     $vs_acc .= $vs_proc_template;
                 }
                 break;
             default:
                 if ($o_node->children && sizeof($o_node->children) > 0) {
                     $vs_proc_template = DisplayTemplateParser::_processChildren($pr_res, $o_node->children, $pa_vals, $pa_options);
                 } else {
                     $vs_proc_template = caProcessTemplate($o_node->html(), $pa_vals, ['quote' => $pb_quote]);
                 }
                 if ($vs_tag === 'l') {
                     $va_proc_templates = caCreateLinksFromText(["{$vs_proc_template}"], $ps_tablename, [$pr_res->getPrimaryKey()], null, caGetOption('linkTarget', $pa_options, null), array_merge(['addRelParameter' => true, 'requireLinkTags' => false], $pa_options));
                     $vs_proc_template = array_shift($va_proc_templates);
                 } elseif (strlen($vs_tag) && $vs_tag[0] !== '~') {
                     if ($o_node->children && sizeof($o_node->children) > 0) {
                         $vs_attr = '';
                         if ($o_node->attributes) {
                             foreach ($o_node->attributes as $attribute => $value) {
                                 $vs_attr .= " {$attribute}=\"" . htmlspecialchars(caProcessTemplate($value, $pa_vals, ['quote' => $pb_quote])) . "\"";
                             }
                         }
                         $vs_proc_template = "<{$vs_tag}{$vs_attr}>{$vs_proc_template}</{$vs_tag}>";
                     } elseif ($o_node->attributes && sizeof($o_node->attributes) > 0) {
                         $vs_attr = '';
                         foreach ($o_node->attributes as $attribute => $value) {
                             $vs_attr .= " {$attribute}=\"" . htmlspecialchars(caProcessTemplate($value, $pa_vals, ['quote' => $pb_quote])) . "\"";
                         }
                         switch (strtolower($vs_tag)) {
                             case 'br':
                             case 'hr':
                             case 'meta':
                             case 'link':
                             case 'base':
                             case 'img':
                             case 'embed':
                             case 'param':
                             case 'area':
                             case 'col':
                             case 'input':
                                 $vs_proc_template = "<{$vs_tag}{$vs_attr} />";
                                 break;
                             default:
                                 $vs_proc_template = "<{$vs_tag}{$vs_attr}></{$vs_tag}>";
                                 break;
                         }
                     } else {
                         $vs_proc_template = $o_node->html();
                     }
                 }
                 $vs_acc .= $vs_proc_template;
                 break;
         }
     }
     return $vs_acc;
 }
 /**
  * Misc function that checks if the OSML tag $node has an if attribute, returns
  * true if the expression is true or no if attribute is set
  *
  * @param DOMElement $node
  */
 private function checkIf(DOMElement &$node)
 {
     if ($if = $node->getAttribute('if')) {
         $expressions = array();
         preg_match_all('/(\\$\\{)(.*)(\\})/imsxU', $if, $expressions);
         if (!count($expressions[2])) {
             throw new ExpressionException("Invalid os:If tag, missing condition expression");
         }
         $expression = $expressions[2][0];
         $expressionResult = ExpressionParser::evaluate($expression, $this->dataContext);
         return $expressionResult ? true : false;
     }
     return true;
 }
 /**
  * Returns array of attributes, each of which is an array of values ready for display. 
  * The returned array is indexed by attribute element ID. Each array value is an array
  * indexed by attribute_id. The corresponding values are arrays of attribute values indexed by element_code
  *
  * @param $pm_element_code_or_id string|integer -
  * @param $pn_row_id integer -
  * @param $pa_options array -
  *				convertLineBreaks = if set to true, will attempt to convert line break characters to HTML <p> and <br> tags; default is false.
  *				locale = if set to a valid locale_id or locale code, values will be returned in locale *if available*, otherwise will fallback to values in languages that are available using the standard fallback mechanism. Default is to use user's current locale.
  *				returnAllLocales = if set to true, values for all locales are returned, locale option is ignored and the returned array is indexed first by attribute_id and then by locale_id. Default is false.
  *				indexByRowID = if true first index of returned array is $pn_row_id, otherwise it is the element_id of the retrieved metadata element	
  *				indexValuesByValueID = index value array by value_id [default=false]
  *				indexValuesByElementCode = index value array by element code [default=true]
  *				indexValuesByElementCodeAndValueID = index value array by element codes, each of which has a sub-array indexed by value_id. Implies indexValuesByElementCode. [default=false]
  *				convertCodesToDisplayText =
  *				filter =
  *				ignoreLocale = if set all values are returned regardless of locale, but in the flattened structure returned when returnAllLocales is false
  *				sort = the full bundle specification for the element value or subvalue (if the element is a container) to sort returned values on.
  *				sortDirection = determines the direction of the sort. Valid values are ASC (ascending) and DESC (descending). [Default=ASC]
  * @return array
  */
 public function getAttributeDisplayValues($pm_element_code_or_id, $pn_row_id, $pa_options = null)
 {
     if (!is_array($pa_options)) {
         $pa_options = array();
     }
     $ps_filter_expression = caGetOption('filter', $pa_options, null);
     $pb_ignore_locale = caGetOption('ignoreLocale', $pa_options, null);
     $ps_sort = caGetOption('sort', $pa_options, null);
     $ps_sort_direction = caGetOption('sortDirection', $pa_options, 'asc', array('forceLowercase' => true, 'validValues' => array('asc', 'desc')));
     $pb_index_by_element_code = caGetOption('indexValuesByElementCode', $pa_options, true);
     $pb_index_by_value_id = caGetOption('indexValuesByValueID', $pa_options, false);
     $pb_index_by_element_code_and_value_id = caGetOption('indexValuesByElementCodeAndValueID', $pa_options, false);
     if ($pb_index_by_element_code_and_value_id) {
         $pb_index_by_element_code = true;
     }
     $va_attribute_list = $this->getAttributesByElement($pm_element_code_or_id, array('row_id' => $pn_row_id));
     if (!is_array($va_attribute_list)) {
         return array();
     }
     $va_attributes = array();
     $vs_table_name = $this->tableName();
     $vs_container_element_code = $this->_getElementCode($pm_element_code_or_id);
     $vs_sort_fld = null;
     if ($ps_sort) {
         if (is_array($ps_sort)) {
             $ps_sort = array_shift($ps_sort);
         }
         $va_sort_tmp = explode(".", $ps_sort);
         if ($va_sort_tmp[0] == $vs_table_name && $va_sort_tmp[1] == $vs_container_element_code) {
             $vs_sort_fld = array_pop($va_sort_tmp);
         }
     }
     foreach ($va_attribute_list as $o_attribute) {
         $va_values = $o_attribute->getValues();
         $va_raw_values = array();
         $va_display_values = array();
         $vs_sort_key = null;
         foreach ($va_values as $o_value) {
             $vs_element_code = $this->_getElementCode($o_value->getElementID());
             if (get_class($o_value) == 'ListAttributeValue') {
                 $t_element = $this->_getElementInstance($o_value->getElementID());
                 $vn_list_id = !isset($pa_options['convertCodesToDisplayText']) || !(bool) $pa_options['convertCodesToDisplayText'] ? null : $t_element->get('list_id');
             } else {
                 $vn_list_id = null;
             }
             if ($ps_filter_expression) {
                 $va_raw_values[$vs_container_element_code == $vs_element_code ? "{$vs_table_name}.{$vs_element_code}" : "{$vs_table_name}.{$vs_container_element_code}.{$vs_element_code}"] = $va_raw_values[$vs_element_code] = $o_value->getDisplayValue(array('list_id' => $vn_list_id, 'returnIdno' => true));
             }
             if (isset($pa_options['convertLineBreaks']) && $pa_options['convertLineBreaks']) {
                 $vs_converted_value = preg_replace("!(\n|\r\n){2}!", "<p/>", $o_value->getDisplayValue(array_merge($pa_options, array('list_id' => $vn_list_id))));
                 $vs_display_value = preg_replace("![\n]{1}!", "<br/>", $vs_converted_value);
             } else {
                 $vs_display_value = $o_value->getDisplayValue(array_merge($pa_options, array('list_id' => $vn_list_id)));
             }
             if ($pb_index_by_element_code) {
                 if ($pb_index_by_element_code_and_value_id) {
                     $va_display_values[$vs_element_code][$o_value->getValueID()] = $vs_display_value;
                 } else {
                     $va_display_values[$vs_element_code] = $vs_display_value;
                 }
             }
             if ($pb_index_by_value_id) {
                 $va_display_values[$o_value->getValueID()] = $vs_display_value;
             }
             if ($vs_sort_fld && $vs_sort_fld == $vs_element_code) {
                 $vs_sort_key = $o_value->getDisplayValue(array_merge($pa_options, array('getDirectDate' => true, 'list_id' => $vn_list_id)));
             }
         }
         if (!$vs_sort_key) {
             $vs_sort_key = '0';
         }
         // Process filter if defined
         if ($ps_filter_expression && !ExpressionParser::evaluate($ps_filter_expression, $va_raw_values)) {
             continue;
         }
         if (isset($pa_options['indexByRowID']) && $pa_options['indexByRowID']) {
             $vs_index = $pn_row_id;
         } else {
             $vs_index = $o_attribute->getElementID();
         }
         if ($pb_ignore_locale) {
             $va_attributes[$vs_sort_key][$vs_index][$o_attribute->getAttributeID()] = $va_display_values;
         } else {
             $va_attributes[$vs_sort_key][$vs_index][(int) $o_attribute->getLocaleID()][$o_attribute->getAttributeID()] = $va_display_values;
         }
     }
     ksort($va_attributes);
     if ($ps_sort_direction == 'desc') {
         $va_attributes = array_reverse($va_attributes);
     }
     $va_sorted_attr = array();
     foreach ($va_attributes as $vs_sort_key => $va_attr_by_index) {
         foreach ($va_attr_by_index as $vs_index => $va_attr_by_id) {
             foreach ($va_attr_by_id as $vs_id => $va_attr) {
                 if ($pb_ignore_locale) {
                     $va_sorted_attr[$vs_index][$vs_id] = $va_attr;
                 } else {
                     foreach ($va_attr as $vn_attr_id => $va_val) {
                         $va_sorted_attr[$vs_index][$vs_id][$vn_attr_id] = $va_val;
                     }
                 }
             }
         }
     }
     $va_attributes = $va_sorted_attr;
     if (!$pb_ignore_locale) {
         if (!isset($pa_options['returnAllLocales']) || !$pa_options['returnAllLocales']) {
             // if desired try to return values in a preferred language/locale
             $va_preferred_locales = null;
             if (isset($pa_options['locale']) && $pa_options['locale']) {
                 $va_preferred_locales = array($pa_options['locale']);
             }
             return caExtractValuesByUserLocale($va_attributes, null, $va_preferred_locales, array());
         }
     }
     return $va_attributes;
 }
Example #18
0
 /**
  *  Statically evaluate an expression, returning the value
  */
 public static function evaluate($ps_expression, $pa_variables = null)
 {
     $e = new ExpressionParser();
     return $e->evaluateExpression($ps_expression, $pa_variables);
 }
Example #19
0
 /**
  * 
  *
  * @param string $ps_source
  * @param string $ps_mapping
  * @param array $pa_options
  *		user_id = user to execute import for
  *		description = Text describing purpose of import to be logged.
  *		showCLIProgressBar = Show command-line progress bar. Default is false.
  *		format = Format of data being imported. MANDATORY
  *		useNcurses = Use ncurses library to format output
  *		logDirectory = path to directory where logs should be written
  *		logLevel = KLogger constant for minimum log level to record. Default is KLogger::INFO. Constants are, in descending order of shrillness:
  *			KLogger::EMERG = Emergency messages (system is unusable)
  *			KLogger::ALERT = Alert messages (action must be taken immediately)
  *			KLogger::CRIT = Critical conditions
  *			KLogger::ERR = Error conditions
  *			KLogger::WARN = Warnings
  *			KLogger::NOTICE = Notices (normal but significant conditions)
  *			KLogger::INFO = Informational messages
  *			KLogger::DEBUG = Debugging messages
  *		dryRun = do import but don't actually save data
  *		environment = an array of environment values to provide to the import process. The keys manifest themselves as mappable tags.
  *		forceImportForPrimaryKeys = list of primary key ids to force mapped source data into. The number of keys passed should equal or exceed the number of rows in the source data. [Default is empty] 
  *		transaction = transaction to perform import within. Will not be used if noTransaction option is set. [Default is to create a new transaction]
  *		noTransaction = don't wrap the import in a transaction. [Default is false]
  */
 public static function importDataFromSource($ps_source, $ps_mapping, $pa_options = null)
 {
     ca_data_importers::$s_num_import_errors = 0;
     ca_data_importers::$s_num_records_processed = 0;
     ca_data_importers::$s_num_records_skipped = 0;
     ca_data_importers::$s_import_error_list = array();
     $opa_app_plugin_manager = new ApplicationPluginManager();
     $va_notices = $va_errors = array();
     $pb_no_transaction = caGetOption('noTransaction', $pa_options, false, array('castTo' => 'bool'));
     $pa_force_import_for_primary_keys = caGetOption('forceImportForPrimaryKeys', $pa_options, null);
     if (!($t_mapping = ca_data_importers::mappingExists($ps_mapping))) {
         return null;
     }
     $o_event = ca_data_import_events::newEvent(isset($pa_options['user_id']) ? $pa_options['user_id'] : null, $pa_options['format'], $ps_source, isset($pa_options['description']) ? $pa_options['description'] : '');
     $o_trans = null;
     if (!$pb_no_transaction) {
         if (!($o_trans = caGetOption('transaction', $pa_options, null))) {
             $o_trans = new Transaction();
         }
         $t_mapping->setTransaction($o_trans);
     }
     $po_request = caGetOption('request', $pa_options, null);
     $pb_dry_run = caGetOption('dryRun', $pa_options, false);
     $pn_file_number = caGetOption('fileNumber', $pa_options, 0);
     $pn_number_of_files = caGetOption('numberOfFiles', $pa_options, 1);
     $o_config = Configuration::load();
     if (!is_array($pa_options) || !isset($pa_options['logLevel']) || !$pa_options['logLevel']) {
         $pa_options['logLevel'] = KLogger::INFO;
     }
     if (!is_array($pa_options) || !isset($pa_options['logDirectory']) || !$pa_options['logDirectory'] || !file_exists($pa_options['logDirectory'])) {
         if (!($pa_options['logDirectory'] = $o_config->get('batch_metadata_import_log_directory'))) {
             $pa_options['logDirectory'] = ".";
         }
     }
     if (!is_writeable($pa_options['logDirectory'])) {
         $pa_options['logDirectory'] = caGetTempDirPath();
     }
     $o_log = new KLogger($pa_options['logDirectory'], $pa_options['logLevel']);
     $vb_show_cli_progress_bar = isset($pa_options['showCLIProgressBar']) && $pa_options['showCLIProgressBar'] ? true : false;
     $o_progress = caGetOption('progressBar', $pa_options, new ProgressBar('WebUI'));
     if ($vb_show_cli_progress_bar) {
         $o_progress->setMode('CLI');
         $o_progress->set('outputToTerminal', true);
     }
     if ($vb_use_ncurses = isset($pa_options['useNcurses']) && $pa_options['useNcurses'] ? true : false) {
         $vb_use_ncurses = caCLIUseNcurses();
     }
     $vn_error_window_height = null;
     $vn_max_x = $vn_max_y = null;
     if ($vb_use_ncurses) {
         ncurses_init();
         $r_screen = ncurses_newwin(0, 0, 0, 0);
         ncurses_border(0, 0, 0, 0, 0, 0, 0, 0);
         ncurses_getmaxyx($r_screen, $vn_max_y, $vn_max_x);
         $vn_error_window_height = $vn_max_y - 8;
         $r_errors = ncurses_newwin($vn_error_window_height, $vn_max_x - 4, 4, 2);
         ncurses_wborder($r_errors, 0, 0, 0, 0, 0, 0, 0, 0);
         ncurses_wattron($r_errors, NCURSES_A_REVERSE);
         ncurses_mvwaddstr($r_errors, 0, 1, _t(" Recent errors "));
         ncurses_wattroff($r_errors, NCURSES_A_REVERSE);
         $r_progress = ncurses_newwin(3, $vn_max_x - 4, $vn_max_y - 4, 2);
         ncurses_wborder($r_progress, 0, 0, 0, 0, 0, 0, 0, 0);
         ncurses_wattron($r_progress, NCURSES_A_REVERSE);
         ncurses_mvwaddstr($r_progress, 0, 1, _t(" Progress "));
         ncurses_wattroff($r_progress, NCURSES_A_REVERSE);
         $r_status = ncurses_newwin(3, $vn_max_x - 4, 1, 2);
         ncurses_wborder($r_status, 0, 0, 0, 0, 0, 0, 0, 0);
         ncurses_wattron($r_status, NCURSES_A_REVERSE);
         ncurses_mvwaddstr($r_status, 0, 1, _t(" Import status "));
         ncurses_wattroff($r_status, NCURSES_A_REVERSE);
         ncurses_refresh(0);
         ncurses_wrefresh($r_progress);
         ncurses_wrefresh($r_errors);
         ncurses_wrefresh($r_status);
     }
     $o_log->logInfo(_t('Started import of %1 using mapping %2', $ps_source, $t_mapping->get("importer_code")));
     $t = new Timer();
     $vn_start_time = time();
     $va_log_import_error_opts = array('startTime' => $vn_start_time, 'window' => $r_errors, 'log' => $o_log, 'request' => $po_request, 'progressCallback' => isset($pa_options['progressCallback']) && ($ps_callback = $pa_options['progressCallback']) ? $ps_callback : null, 'reportCallback' => isset($pa_options['reportCallback']) && ($ps_callback = $pa_options['reportCallback']) ? $ps_callback : null);
     global $g_ui_locale_id;
     // constant locale set by index.php for web requests
     $vn_locale_id = isset($pa_options['locale_id']) && (int) $pa_options['locale_id'] ? (int) $pa_options['locale_id'] : $g_ui_locale_id;
     $o_dm = $t_mapping->getAppDatamodel();
     $o_progress->start(_t('Reading %1', $ps_source), array('window' => $r_progress));
     if ($po_request && isset($pa_options['progressCallback']) && ($ps_callback = $pa_options['progressCallback'])) {
         $ps_callback($po_request, $pn_file_number, $pn_number_of_files, $ps_source, 0, 100, _t('Reading %1', $ps_source), time() - $vn_start_time, memory_get_usage(true), 0, ca_data_importers::$s_num_import_errors);
     }
     // Open file
     $ps_format = isset($pa_options['format']) && $pa_options['format'] ? $pa_options['format'] : null;
     if (!($o_reader = $t_mapping->getDataReader($ps_source, $ps_format))) {
         ca_data_importers::logImportError(_t("Could not open source %1 (format=%2)", $ps_source, $ps_format), $va_log_import_error_opts);
         if ($vb_use_ncurses) {
             ncurses_end();
         }
         if ($o_trans) {
             $o_trans->rollback();
         }
         return false;
     }
     if (!$o_reader->read($ps_source, array('basePath' => $t_mapping->getSetting('basePath')))) {
         ca_data_importers::logImportError(_t("Could not read source %1 (format=%2)", $ps_source, $ps_format), $va_log_import_error_opts);
         if ($vb_use_ncurses) {
             ncurses_end();
         }
         if ($o_trans) {
             $o_trans->rollback();
         }
         return false;
     }
     $o_log->logDebug(_t('Finished reading input source at %1 seconds', $t->getTime(4)));
     $vn_num_items = $o_reader->numRows();
     $o_progress->setTotal($vn_num_items);
     $o_progress->start(_t('Importing from %1', $ps_source), array('window' => $r_progress));
     if ($po_request && isset($pa_options['progressCallback']) && ($ps_callback = $pa_options['progressCallback'])) {
         $ps_callback($po_request, $pn_file_number, $pn_number_of_files, $ps_source, 0, $vn_num_items, _t('Importing from %1', $ps_source), time() - $vn_start_time, memory_get_usage(true), 0, ca_data_importers::$s_num_import_errors);
     }
     // What are we importing?
     $vn_table_num = $t_mapping->get('table_num');
     if (!($t_subject = $o_dm->getInstanceByTableNum($vn_table_num))) {
         // invalid table
         if ($vb_use_ncurses) {
             ncurses_end();
         }
         if ($o_trans) {
             $o_trans->rollback();
         }
         return false;
     }
     $t_subject->setTransaction($o_trans);
     $vs_subject_table_name = $t_subject->tableName();
     $t_label = $t_subject->getLabelTableInstance();
     $t_label->setTransaction($o_trans);
     $vs_label_display_fld = $t_subject->getLabelDisplayField();
     $vs_subject_table = $t_subject->tableName();
     $vs_type_id_fld = $t_subject->getTypeFieldName();
     $vs_idno_fld = $t_subject->getProperty('ID_NUMBERING_ID_FIELD');
     // get mapping rules
     $va_mapping_rules = $t_mapping->getRules();
     // get mapping groups
     $va_mapping_groups = $t_mapping->getGroups();
     $va_mapping_items = $t_mapping->getItems();
     //
     // Mapping-level settings
     //
     $vs_type_mapping_setting = $t_mapping->getSetting('type');
     $vn_num_initial_rows_to_skip = $t_mapping->getSetting('numInitialRowsToSkip');
     if (!in_array($vs_import_error_policy = $t_mapping->getSetting('errorPolicy'), array('ignore', 'stop'))) {
         $vs_import_error_policy = 'ignore';
     }
     if (!in_array($vs_existing_record_policy = $t_mapping->getSetting('existingRecordPolicy'), array('none', 'skip_on_idno', 'skip_on_preferred_labels', 'merge_on_idno', 'merge_on_preferred_labels', 'merge_on_idno_and_preferred_labels', 'merge_on_idno_with_replace', 'merge_on_preferred_labels_with_replace', 'merge_on_idno_and_preferred_labels_with_replace', 'overwrite_on_idno', 'overwrite_on_preferred_labels', 'overwrite_on_idno_and_preferred_labels'))) {
         $vs_existing_record_policy = 'none';
     }
     // Analyze mapping for figure out where type, idno, preferred label and other mandatory fields are coming from
     $vn_type_id_mapping_item_id = $vn_idno_mapping_item_id = null;
     $va_preferred_label_mapping_ids = array();
     $va_mandatory_field_mapping_ids = array();
     $va_mandatory_fields = $t_subject->getMandatoryFields();
     foreach ($va_mapping_items as $vn_item_id => $va_item) {
         $vs_destination = $va_item['destination'];
         if (sizeof($va_dest_tmp = explode(".", $vs_destination)) >= 2) {
             if ($va_dest_tmp[0] == $vs_subject_table && $va_dest_tmp[1] == 'preferred_labels') {
                 if (isset($va_dest_tmp[2])) {
                     $va_preferred_label_mapping_ids[$vn_item_id] = $va_dest_tmp[2];
                 } else {
                     $va_preferred_label_mapping_ids[$vn_item_id] = $vs_label_display_fld;
                 }
                 continue;
             }
         }
         switch ($vs_destination) {
             case 'representation_id':
                 if ($vs_subject_table == 'ca_representation_annotations') {
                     $vn_type_id_mapping_item_id = $vn_item_id;
                 }
                 break;
             case "{$vs_subject_table}.{$vs_type_id_fld}":
                 $vn_type_id_mapping_item_id = $vn_item_id;
                 break;
             case "{$vs_subject_table}.{$vs_idno_fld}":
                 $vn_idno_mapping_item_id = $vn_item_id;
                 break;
         }
         foreach ($va_mandatory_fields as $vs_mandatory_field) {
             if ($vs_mandatory_field == $vs_type_id_fld) {
                 continue;
             }
             // type is handled separately
             if ($vs_destination == "{$vs_subject_table}.{$vs_mandatory_field}") {
                 $va_mandatory_field_mapping_ids[$vs_mandatory_field] = $vn_item_id;
             }
         }
     }
     $va_items_by_group = array();
     foreach ($va_mapping_items as $vn_item_id => $va_item) {
         $va_items_by_group[$va_item['group_id']][$va_item['item_id']] = $va_item;
     }
     $o_log->logDebug(_t('Finished analyzing mapping at %1 seconds', $t->getTime(4)));
     //
     // Set up environment
     //
     $va_environment = caGetOption('environment', $pa_options, array(), array('castTo' => 'array'));
     if (is_array($va_environment_config = $t_mapping->getEnvironment())) {
         foreach ($va_environment_config as $vn_i => $va_environment_item) {
             $va_env_tmp = explode("|", $va_environment_item['value']);
             if (!($o_env_reader = $t_mapping->getDataReader($ps_source, $ps_format))) {
                 break;
             }
             if (!$o_env_reader->read($ps_source, array('basePath' => ''))) {
                 break;
             }
             $o_env_reader->nextRow();
             switch (sizeof($va_env_tmp)) {
                 case 1:
                     $vs_env_value = $o_env_reader->get($va_environment_item['value'], array('returnAsArray' => false));
                     break;
                 case 2:
                     $vn_seek = (int) $va_env_tmp[0];
                     $o_reader->seek($vn_seek > 0 ? $vn_seek - 1 : $vn_seek);
                     $o_env_reader->nextRow();
                     $vs_env_value = $o_env_reader->get($va_env_tmp[1], array('returnAsArray' => false));
                     $o_reader->seek(0);
                     break;
                 default:
                     $vs_env_value = $va_environment_item['value'];
                     break;
             }
             $va_environment[$va_environment_item['name']] = $vs_env_value;
         }
     }
     //
     // Run through rows
     //
     $vn_row = 0;
     ca_data_importers::$s_num_records_processed = 0;
     while ($o_reader->nextRow()) {
         $va_mandatory_field_values = array();
         $vs_preferred_label_for_log = null;
         if ($vn_row < $vn_num_initial_rows_to_skip) {
             // skip over initial header rows
             $vn_row++;
             continue;
         }
         $vn_row++;
         $t->startTimer();
         $o_log->logDebug(_t('Started reading row %1 at %2 seconds', $vn_row, $t->getTime(4)));
         $t_subject = $o_dm->getInstanceByTableNum($vn_table_num);
         if ($o_trans) {
             $t_subject->setTransaction($o_trans);
         }
         $t_subject->setMode(ACCESS_WRITE);
         // Update status display
         if ($vb_use_ncurses && ca_data_importers::$s_num_records_processed) {
             ncurses_mvwaddstr($r_status, 1, 2, _t("Items processed/skipped: %1/%2", ca_data_importers::$s_num_records_processed, ca_data_importers::$s_num_records_skipped) . str_repeat(" ", 5) . _t("Errors: %1 (%2)", ca_data_importers::$s_num_import_errors, sprintf("%3.1f", ca_data_importers::$s_num_import_errors / ca_data_importers::$s_num_records_processed * 100) . "%") . str_repeat(" ", 6) . _t("Mapping: %1", $ps_mapping) . str_repeat(" ", 5) . _t("Source: %1", $ps_source) . str_repeat(" ", 5) . date("Y-m-d H:i:s") . str_repeat(" ", 5));
             ncurses_refresh(0);
             ncurses_wrefresh($r_status);
         }
         //
         // Get data for current row
         //
         $va_row = array_merge($o_reader->getRow(), $va_environment);
         //
         // Apply rules
         //
         foreach ($va_mapping_rules as $va_rule) {
             if (!isset($va_rule['trigger']) || !$va_rule['trigger']) {
                 continue;
             }
             if (!isset($va_rule['actions']) || !is_array($va_rule['actions']) || !sizeof($va_rule['actions'])) {
                 continue;
             }
             $vm_ret = ExpressionParser::evaluate($va_rule['trigger'], $va_row);
             if (!ExpressionParser::hadError() && (bool) $vm_ret) {
                 foreach ($va_rule['actions'] as $va_action) {
                     if (!is_array($va_action) && strtolower($va_action) == 'skip') {
                         $va_action = array('action' => 'skip');
                     }
                     switch ($vs_action_code = strtolower($va_action['action'])) {
                         case 'set':
                             $va_row[$va_action['target']] = $va_action['value'];
                             // TODO: transform value using mapping rules?
                             break;
                         case 'skip':
                         default:
                             if ($vs_action_code != 'skip') {
                                 $o_log->logInfo(_t('Row was skipped using rule "%1" with default action because an invalid action ("%2") was specified', $va_rule['trigger'], $vs_action_code));
                             } else {
                                 $o_log->logDebug(_t('Row was skipped using rule "%1" with action "%2"', $va_rule['trigger'], $vs_action_code));
                             }
                             continue 4;
                             break;
                     }
                 }
             }
         }
         //
         // Perform mapping and insert
         //
         // Get minimal info for imported row (type_id, idno, label)
         // Get type
         if ($vn_type_id_mapping_item_id) {
             // Type is specified in row
             $vs_type = ca_data_importers::getValueFromSource($va_mapping_items[$vn_type_id_mapping_item_id], $o_reader, array('environment' => $va_environment));
         } else {
             // Type is constant for all rows
             $vs_type = $vs_type_mapping_setting;
         }
         // Get idno
         $vs_idno = null;
         if ($vn_idno_mapping_item_id) {
             // idno is specified in row
             $vs_idno = ca_data_importers::getValueFromSource($va_mapping_items[$vn_idno_mapping_item_id], $o_reader, array('environment' => $va_environment));
             if (isset($va_mapping_items[$vn_idno_mapping_item_id]['settings']['default']) && strlen($va_mapping_items[$vn_idno_mapping_item_id]['settings']['default']) && !strlen($vs_idno)) {
                 $vs_idno = $va_mapping_items[$vn_idno_mapping_item_id]['settings']['default'];
             }
             if (!is_array($vs_idno) && $vs_idno[0] == '^' && preg_match("!^\\^[^ ]+\$!", $vs_idno)) {
                 // Parse placeholder when it's at the beginning of the value
                 if (!is_null($vm_parsed_val = BaseRefinery::parsePlaceholder($vs_idno, $va_row, $va_item, null, array('reader' => $o_reader, 'returnAsString' => true)))) {
                     $vs_idno = $vm_parsed_val;
                 }
             }
             // Apply prefix/suffix *AFTER* setting default
             if ($vs_idno && isset($va_mapping_items[$vn_idno_mapping_item_id]['settings']['prefix']) && strlen($va_mapping_items[$vn_idno_mapping_item_id]['settings']['prefix'])) {
                 $vs_idno = $va_mapping_items[$vn_idno_mapping_item_id]['settings']['prefix'] . $vs_idno;
             }
             if ($vs_idno && isset($va_mapping_items[$vn_idno_mapping_item_id]['settings']['suffix']) && strlen($va_mapping_items[$vn_idno_mapping_item_id]['settings']['suffix'])) {
                 $vs_idno .= $va_mapping_items[$vn_idno_mapping_item_id]['settings']['suffix'];
             }
             if (isset($va_mapping_items[$vn_idno_mapping_item_id]['settings']['formatWithTemplate']) && strlen($va_mapping_items[$vn_idno_mapping_item_id]['settings']['formatWithTemplate'])) {
                 $vs_idno = caProcessTemplate($va_mapping_items[$vn_idno_mapping_item_id]['settings']['formatWithTemplate'], $va_row);
             }
         } else {
             $vs_idno = "%";
         }
         $vb_idno_is_template = (bool) preg_match('![%]+!', $vs_idno);
         // get preferred labels
         $va_pref_label_values = array();
         foreach ($va_preferred_label_mapping_ids as $vn_preferred_label_mapping_id => $vs_preferred_label_mapping_fld) {
             $vs_label_val = ca_data_importers::getValueFromSource($va_mapping_items[$vn_preferred_label_mapping_id], $o_reader, array('environment' => $va_environment));
             // If a template is specified format the label value with it so merge-on-preferred_label doesn't fail
             if (isset($va_mapping_items[$vn_preferred_label_mapping_id]['settings']['formatWithTemplate']) && strlen($va_mapping_items[$vn_preferred_label_mapping_id]['settings']['formatWithTemplate'])) {
                 $vs_label_val = caProcessTemplate($va_mapping_items[$vn_preferred_label_mapping_id]['settings']['formatWithTemplate'], $va_row);
             }
             $va_pref_label_values[$vs_preferred_label_mapping_fld] = $vs_label_val;
         }
         $vs_display_label = isset($va_pref_label_values[$vs_label_display_fld]) ? $va_pref_label_values[$vs_label_display_fld] : $vs_idno;
         //
         // Look for existing record?
         //
         if (is_array($pa_force_import_for_primary_keys) && sizeof($pa_force_import_for_primary_keys) > 0) {
             $vn_id = array_shift($pa_force_import_for_primary_keys);
             if (!$t_subject->load($vn_id)) {
                 $o_log->logInfo(_t('[%1] Skipped import because of forced primary key \'%1\' does not exist', $vn_id));
                 ca_data_importers::$s_num_records_skipped++;
                 continue;
                 // skip because primary key does not exist
             }
         } elseif ($vs_existing_record_policy != 'none') {
             switch ($vs_existing_record_policy) {
                 case 'skip_on_idno':
                     if (!$vb_idno_is_template) {
                         $va_ids = call_user_func_array($t_subject->tableName() . "::find", array(array('type_id' => $vs_type, $t_subject->getProperty('ID_NUMBERING_ID_FIELD') => $vs_idno, 'deleted' => 0), array('returnAs' => 'ids')));
                         if (is_array($va_ids) && sizeof($va_ids) > 0) {
                             $o_log->logInfo(_t('[%1] Skipped import because of existing record matched on identifier by policy %2', $vs_idno, $vs_existing_record_policy));
                             ca_data_importers::$s_num_records_skipped++;
                             continue 2;
                             // skip because idno matched
                         }
                     }
                     break;
                 case 'skip_on_preferred_labels':
                     $va_ids = call_user_func_array($t_subject->tableName() . "::find", array(array('type_id' => $vs_type, 'preferred_labels' => $va_pref_label_values, 'deleted' => 0), array('returnAs' => 'ids')));
                     if (is_array($va_ids) && sizeof($va_ids) > 0) {
                         $o_log->logInfo(_t('[%1] Skipped import because of existing record matched on label by policy %2', $vs_idno, $vs_existing_record_policy));
                         ca_data_importers::$s_num_records_skipped++;
                         continue 2;
                         // skip because label matched
                     }
                     break;
                 case 'merge_on_idno_and_preferred_labels':
                 case 'merge_on_idno':
                 case 'merge_on_idno_and_preferred_labels_with_replace':
                 case 'merge_on_idno_with_replace':
                     if (!$vb_idno_is_template) {
                         $va_ids = call_user_func_array($t_subject->tableName() . "::find", array(array('type_id' => $vs_type, $t_subject->getProperty('ID_NUMBERING_ID_FIELD') => $vs_idno, 'deleted' => 0), array('returnAs' => 'ids')));
                         if (is_array($va_ids) && sizeof($va_ids) > 0) {
                             $t_subject->load($va_ids[0]);
                             $o_log->logInfo(_t('[%1] Merged with existing record matched on identifer by policy %2', $vs_idno, $vs_existing_record_policy));
                             break;
                         }
                     }
                     if (in_array($vs_existing_record_policy, array('merge_on_idno', 'merge_on_idno_with_replace'))) {
                         break;
                     }
                     // fall through if merge_on_idno_and_preferred_labels
                 // fall through if merge_on_idno_and_preferred_labels
                 case 'merge_on_preferred_labels':
                 case 'merge_on_preferred_labels_with_replace':
                     $va_ids = call_user_func_array($t_subject->tableName() . "::find", array(array('type_id' => $vs_type, 'preferred_labels' => $va_pref_label_values, 'deleted' => 0), array('returnAs' => 'ids')));
                     if (is_array($va_ids) && sizeof($va_ids) > 0) {
                         $t_subject->load($va_ids[0]);
                         $o_log->logInfo(_t('[%1] Merged with existing record matched on label by policy %2', $vs_idno, $vs_existing_record_policy));
                     }
                     break;
                 case 'overwrite_on_idno_and_preferred_labels':
                 case 'overwrite_on_idno':
                     if (!$vb_idno_is_template && $vs_idno) {
                         $va_ids = call_user_func_array($t_subject->tableName() . "::find", array(array('type_id' => $vs_type, $t_subject->getProperty('ID_NUMBERING_ID_FIELD') => $vs_idno, 'deleted' => 0), array('returnAs' => 'ids')));
                         if (is_array($va_ids) && sizeof($va_ids) > 0) {
                             $t_subject->load($va_ids[0]);
                             $t_subject->setMode(ACCESS_WRITE);
                             $t_subject->delete(true, array('hard' => true));
                             if ($t_subject->numErrors()) {
                                 ca_data_importers::logImportError(_t('[%1] Could not delete existing record matched on identifier by policy %2', $vs_idno, $vs_existing_record_policy));
                                 // Don't stop?
                             } else {
                                 $o_log->logInfo(_t('[%1] Overwrote existing record matched on identifier by policy %2', $vs_idno, $vs_existing_record_policy));
                                 $t_subject->clear();
                                 break;
                             }
                         }
                     }
                     if ($vs_existing_record_policy == 'overwrite_on_idno') {
                         break;
                     }
                     // fall through if overwrite_on_idno_and_preferred_labels
                 // fall through if overwrite_on_idno_and_preferred_labels
                 case 'overwrite_on_preferred_labels':
                     $va_ids = call_user_func_array($t_subject->tableName() . "::find", array(array('type_id' => $vs_type, 'preferred_labels' => $va_pref_label_values, 'deleted' => 0), array('returnAs' => 'ids')));
                     if (is_array($va_ids) && sizeof($va_ids) > 0) {
                         $t_subject->load($va_ids[0]);
                         $t_subject->setMode(ACCESS_WRITE);
                         $t_subject->delete(true, array('hard' => true));
                         if ($t_subject->numErrors()) {
                             ca_data_importers::logImportError(_t('[%1] Could not delete existing record matched on label by policy %2', $vs_idno, $vs_existing_record_policy));
                             // Don't stop?
                         } else {
                             $o_log->logInfo(_t('[%1] Overwrote existing record matched on label by policy %2', $vs_idno, $vs_existing_record_policy));
                             break;
                         }
                         $t_subject->clear();
                     }
                     break;
             }
         }
         $o_progress->next(_t("Importing %1", $vs_idno), array('window' => $r_progress));
         if ($po_request && isset($pa_options['progressCallback']) && ($ps_callback = $pa_options['progressCallback'])) {
             $ps_callback($po_request, $pn_file_number, $pn_number_of_files, $ps_source, ca_data_importers::$s_num_records_processed, $vn_num_items, _t("[%3/%4] Processing %1 (%2)", caTruncateStringWithEllipsis($vs_display_label, 50), $vs_idno, ca_data_importers::$s_num_records_processed, $vn_num_items), time() - $vn_start_time, memory_get_usage(true), ca_data_importers::$s_num_records_processed, ca_data_importers::$s_num_import_errors);
         }
         $vb_output_subject_preferred_label = false;
         $va_content_tree = array();
         foreach ($va_items_by_group as $vn_group_id => $va_items) {
             $va_group = $va_mapping_groups[$vn_group_id];
             $vs_group_destination = $va_group['destination'];
             $va_group_tmp = explode(".", $vs_group_destination);
             if (sizeof($va_items) < 2 && sizeof($va_group_tmp) > 2) {
                 array_pop($va_group_tmp);
             }
             $vs_target_table = $va_group_tmp[0];
             if (!($t_target = $o_dm->getInstanceByTableName($vs_target_table, true))) {
                 // Invalid target table
                 $o_log->logWarn(_t('[%1] Skipped group %2 because target %3 is invalid', $vs_idno, $vn_group_id, $vs_target_table));
                 continue;
             }
             if ($o_trans) {
                 $t_target->setTransaction($o_trans);
             }
             $va_group_buf = array();
             foreach ($va_items as $vn_item_id => $va_item) {
                 if ($vb_use_as_single_value = caGetOption('useAsSingleValue', $va_item['settings'], false)) {
                     // Force repeating values to be imported as a single value
                     $va_vals = array(ca_data_importers::getValueFromSource($va_item, $o_reader, array('delimiter' => caGetOption('delimiter', $va_item['settings'], ''), 'returnAsArray' => false)));
                 } else {
                     $va_vals = ca_data_importers::getValueFromSource($va_item, $o_reader, array('returnAsArray' => true, 'environment' => $va_environment));
                 }
                 if (!sizeof($va_vals)) {
                     $va_vals = array(0 => null);
                 }
                 // consider missing values equivalent to blanks
                 // Do value conversions
                 foreach ($va_vals as $vn_i => $vm_val) {
                     if (isset($va_item['settings']['default']) && strlen($va_item['settings']['default']) && !strlen($vm_val)) {
                         $vm_val = $va_item['settings']['default'];
                     }
                     // Apply prefix/suffix *AFTER* setting default
                     if ($vm_val && isset($va_item['settings']['prefix']) && strlen($va_item['settings']['prefix'])) {
                         $vm_val = $va_item['settings']['prefix'] . $vm_val;
                     }
                     if ($vm_val && isset($va_item['settings']['suffix']) && strlen($va_item['settings']['suffix'])) {
                         $vm_val .= $va_item['settings']['suffix'];
                     }
                     if (!is_array($vm_val) && $vm_val[0] == '^' && preg_match("!^\\^[^ ]+\$!", $vm_val)) {
                         // Parse placeholder
                         if (!is_null($vm_parsed_val = BaseRefinery::parsePlaceholder($vm_val, $va_row, $va_item, $vn_i, array('reader' => $o_reader, 'returnAsString' => true)))) {
                             $vm_val = $vm_parsed_val;
                         }
                     }
                     if (isset($va_item['settings']['formatWithTemplate']) && strlen($va_item['settings']['formatWithTemplate'])) {
                         $vm_val = caProcessTemplate($va_item['settings']['formatWithTemplate'], array_replace($va_row, array((string) $va_item['source'] => ca_data_importers::replaceValue($vm_val, $va_item))), array('getFrom' => $o_reader));
                     }
                     if (isset($va_item['settings']['applyRegularExpressions']) && is_array($va_item['settings']['applyRegularExpressions'])) {
                         if (is_array($va_item['settings']['applyRegularExpressions'])) {
                             foreach ($va_item['settings']['applyRegularExpressions'] as $vn_regex_index => $va_regex) {
                                 if (!strlen($va_regex['match'])) {
                                     continue;
                                 }
                                 $vm_val = preg_replace("!" . str_replace("!", "\\!", $va_regex['match']) . "!" . (isset($va_regex['caseSensitive']) && (bool) $va_regex['caseSensitive'] ? '' : 'i'), $va_regex['replaceWith'], $vm_val);
                             }
                         }
                     }
                     $va_vals[$vn_i] = $vm_val;
                     if ($o_reader->valuesCanRepeat()) {
                         $va_row[$va_item['source']][$vn_i] = $va_row[mb_strtolower($va_item['source'])][$vn_i] = $vm_val;
                     } else {
                         $va_row[$va_item['source']] = $va_row[mb_strtolower($va_item['source'])] = $vm_val;
                     }
                 }
                 // Process each value
                 $vn_c = -1;
                 foreach ($va_vals as $vn_i => $vm_val) {
                     $vn_c++;
                     if (isset($va_item['settings']['convertNewlinesToHTML']) && (bool) $va_item['settings']['convertNewlinesToHTML'] && is_string($vm_val)) {
                         $vm_val = nl2br($vm_val);
                     }
                     // Get location in content tree for addition of new content
                     $va_item_dest = explode(".", $va_item['destination']);
                     $vs_item_terminal = $va_item_dest[sizeof($va_item_dest) - 1];
                     if (isset($va_item['settings']['restrictToTypes']) && is_array($va_item['settings']['restrictToTypes']) && !in_array($vs_type, $va_item['settings']['restrictToTypes'])) {
                         $o_log->logInfo(_t('[%1] Skipped row %2 because of type restriction', $vs_idno, $vn_row));
                         continue 4;
                     }
                     if (isset($va_item['settings']['skipRowIfEmpty']) && (bool) $va_item['settings']['skipRowIfEmpty'] && !strlen($vm_val)) {
                         $o_log->logInfo(_t('[%1] Skipped row %2 because value for %3 in group %4 is empty', $vs_idno, $vn_row, $vs_item_terminal, $vn_group_id));
                         continue 4;
                     }
                     if (isset($va_item['settings']['skipRowIfValue']) && is_array($va_item['settings']['skipRowIfValue']) && strlen($vm_val) && in_array($vm_val, $va_item['settings']['skipRowIfValue'])) {
                         $o_log->logInfo(_t('[%1] Skipped row %2 because value for %3 in group %4 matches value %5', $vs_idno, $vn_row, $vs_item_terminal, $vn_group_id));
                         continue 4;
                     }
                     if (isset($va_item['settings']['skipRowIfNotValue']) && is_array($va_item['settings']['skipRowIfNotValue']) && strlen($vm_val) && !in_array($vm_val, $va_item['settings']['skipRowIfNotValue'])) {
                         $o_log->logInfo(_t('[%1] Skipped row %2 because value for %3 in group %4 is not in list of values', $vs_idno, $vn_row, $vs_item_terminal, $vn_group_id, $vm_val));
                         continue 4;
                     }
                     if (isset($va_item['settings']['skipGroupIfEmpty']) && (bool) $va_item['settings']['skipGroupIfEmpty'] && !strlen($vm_val)) {
                         $o_log->logInfo(_t('[%1] Skipped group %2 because value for %3 is empty', $vs_idno, $vn_group_id, $vs_item_terminal));
                         continue 3;
                     }
                     if (isset($va_item['settings']['skipGroupIfExpression']) && strlen(trim($va_item['settings']['skipGroupIfExpression']))) {
                         if ($vm_ret = ExpressionParser::evaluate($va_item['settings']['skipGroupIfExpression'], $va_row)) {
                             $o_log->logInfo(_t('[%1] Skipped group %2 because expression %3 is true', $vs_idno, $vn_group_id, $va_item['settings']['skipGroupIfExpression']));
                             continue 3;
                         }
                     }
                     if (isset($va_item['settings']['skipGroupIfValue']) && is_array($va_item['settings']['skipGroupIfValue']) && strlen($vm_val) && in_array($vm_val, $va_item['settings']['skipGroupIfValue'])) {
                         $o_log->logInfo(_t('[%1] Skipped group %2 because value for %3 matches value %4', $vs_idno, $vn_group_id, $vs_item_terminal, $vm_val));
                         continue 3;
                     }
                     if (isset($va_item['settings']['skipGroupIfNotValue']) && is_array($va_item['settings']['skipGroupIfNotValue']) && strlen($vm_val) && !in_array($vm_val, $va_item['settings']['skipGroupIfNotValue'])) {
                         $o_log->logInfo(_t('[%1] Skipped group %2 because value for %3 matches is not in list of values', $vs_idno, $vn_group_id, $vs_item_terminal));
                         continue 3;
                     }
                     if (isset($va_item['settings']['skipIfExpression']) && strlen(trim($va_item['settings']['skipIfExpression']))) {
                         if ($vm_ret = ExpressionParser::evaluate($va_item['settings']['skipIfExpression'], $va_row)) {
                             $o_log->logInfo(_t('[%1] Skipped mapping because expression %2 is true', $vs_idno, $va_item['settings']['skipIfExpression']));
                             continue 2;
                         }
                     }
                     if (isset($va_item['settings']['skipIfEmpty']) && (bool) $va_item['settings']['skipIfEmpty'] && !strlen($vm_val)) {
                         $o_log->logInfo(_t('[%1] Skipped mapping because value for %2 is empty', $vs_idno, $vs_item_terminal));
                         continue 2;
                     }
                     if ($vn_type_id_mapping_item_id && $vn_item_id == $vn_type_id_mapping_item_id) {
                         continue;
                     }
                     if ($vn_idno_mapping_item_id && $vn_item_id == $vn_idno_mapping_item_id) {
                         continue;
                     }
                     if (is_null($vm_val)) {
                         continue;
                     }
                     // Get mapping error policy
                     $vb_item_error_policy_is_default = false;
                     if (!isset($va_item['settings']['errorPolicy']) || !in_array($vs_item_error_policy = $va_item['settings']['errorPolicy'], array('ignore', 'stop'))) {
                         $vs_item_error_policy = 'ignore';
                         $vb_item_error_policy_is_default = true;
                     }
                     //
                     if (isset($va_item['settings']['relationshipType']) && strlen($vs_rel_type = $va_item['settings']['relationshipType']) && $vs_target_table != $vs_subject_table) {
                         $va_group_buf[$vn_c]['_relationship_type'] = $vs_rel_type;
                     }
                     // Is it a constant value?
                     if (preg_match("!^_CONSTANT_:[\\d]+:(.*)!", $va_item['source'], $va_matches)) {
                         $va_group_buf[$vn_c][$vs_item_terminal] = $va_matches[1];
                         // Set it and go onto the next item
                         if ($vs_target_table == $vs_subject_table_name && ($vs_k = array_search($vn_item_id, $va_mandatory_field_mapping_ids)) !== false) {
                             $va_mandatory_field_values[$vs_k] = $vm_val;
                         }
                         continue;
                     }
                     // Perform refinery call (if required) per value
                     if (isset($va_item['settings']['refineries']) && is_array($va_item['settings']['refineries'])) {
                         foreach ($va_item['settings']['refineries'] as $vs_refinery) {
                             if (!$vs_refinery) {
                                 continue;
                             }
                             if ($o_refinery = RefineryManager::getRefineryInstance($vs_refinery)) {
                                 $va_refined_values = $o_refinery->refine($va_content_tree, $va_group, $va_item, $va_row, array('mapping' => $t_mapping, 'source' => $ps_source, 'subject' => $t_subject, 'locale_id' => $vn_locale_id, 'log' => $o_log, 'transaction' => $o_trans, 'importEvent' => $o_event, 'importEventSource' => $vn_row, 'reader' => $o_reader, 'valueIndex' => $vn_i));
                                 if (!$va_refined_values || is_array($va_refined_values) && !sizeof($va_refined_values)) {
                                     continue 2;
                                 }
                                 if ($o_refinery->returnsMultipleValues()) {
                                     foreach ($va_refined_values as $va_refined_value) {
                                         $va_refined_value['_errorPolicy'] = $vs_item_error_policy;
                                         if (!is_array($va_group_buf[$vn_c])) {
                                             $va_group_buf[$vn_c] = array();
                                         }
                                         $va_group_buf[$vn_c] = array_merge($va_group_buf[$vn_c], $va_refined_value);
                                         $vn_c++;
                                     }
                                 } else {
                                     $va_group_buf[$vn_c]['_errorPolicy'] = $vs_item_error_policy;
                                     $va_group_buf[$vn_c][$vs_item_terminal] = $va_refined_values;
                                     $vn_c++;
                                 }
                                 if ($vs_target_table == $vs_subject_table_name && ($vs_k = array_search($vn_item_id, $va_mandatory_field_mapping_ids)) !== false) {
                                     $va_mandatory_field_values[$vs_k] = $vm_val;
                                 }
                                 continue 2;
                             } else {
                                 ca_data_importers::logImportError(_t('[%1] Invalid refinery %2 specified', $vs_idno, $vs_refinery));
                             }
                         }
                     }
                     if ($vs_target_table == $vs_subject_table_name && ($vs_k = array_search($vn_item_id, $va_mandatory_field_mapping_ids)) !== false) {
                         $va_mandatory_field_values[$vs_k] = $vm_val;
                     }
                     $vn_max_length = !is_array($vm_val) && isset($va_item['settings']['maxLength']) && (int) $va_item['settings']['maxLength'] ? (int) $va_item['settings']['maxLength'] : null;
                     if (isset($va_item['settings']['delimiter']) && $va_item['settings']['delimiter']) {
                         if (!is_array($va_item['settings']['delimiter'])) {
                             $va_item['settings']['delimiter'] = array($va_item['settings']['delimiter']);
                         }
                         if (sizeof($va_item['settings']['delimiter'])) {
                             foreach ($va_item['settings']['delimiter'] as $vn_index => $vs_delim) {
                                 $va_item['settings']['delimiter'][$vn_index] = preg_quote($vs_delim, "!");
                             }
                             $va_val_list = preg_split("!(" . join("|", $va_item['settings']['delimiter']) . ")!", $vm_val);
                             // Add delimited values
                             foreach ($va_val_list as $vs_list_val) {
                                 $vs_list_val = trim(ca_data_importers::replaceValue($vs_list_val, $va_item));
                                 if ($vn_max_length && mb_strlen($vs_list_val) > $vn_max_length) {
                                     $vs_list_val = mb_substr($vs_list_val, 0, $vn_max_length);
                                 }
                                 $va_group_buf[$vn_c] = array($vs_item_terminal => $vs_list_val, '_errorPolicy' => $vs_item_error_policy);
                                 $vn_c++;
                             }
                             $vn_row++;
                             continue;
                             // Don't add "regular" value below
                         }
                     }
                     if ($vn_max_length && mb_strlen($vm_val) > $vn_max_length) {
                         $vm_val = mb_substr($vm_val, 0, $vn_max_length);
                     }
                     switch ($vs_item_terminal) {
                         case 'preferred_labels':
                         case 'nonpreferred_labels':
                             if ($t_instance = $o_dm->getInstanceByTableName($vs_target_table, true)) {
                                 $va_group_buf[$vn_c][$t_instance->getLabelDisplayField()] = $vm_val;
                             }
                             if ($o_trans) {
                                 $t_instance->setTransaction($o_trans);
                             }
                             if (!$vb_item_error_policy_is_default || !isset($va_group_buf[$vn_c]['_errorPolicy'])) {
                                 if (is_array($va_group_buf[$vn_c])) {
                                     $va_group_buf[$vn_c]['_errorPolicy'] = $vs_item_error_policy;
                                 }
                             }
                             if ($vs_item_terminal == 'preferred_labels') {
                                 $vs_preferred_label_for_log = $vm_val;
                             }
                             break;
                         default:
                             $va_group_buf[$vn_c][$vs_item_terminal] = $vm_val;
                             if (!$vb_item_error_policy_is_default || !isset($va_group_buf[$vn_c]['_errorPolicy'])) {
                                 if (is_array($va_group_buf[$vn_c])) {
                                     $va_group_buf[$vn_c]['_errorPolicy'] = $vs_item_error_policy;
                                 }
                             }
                             break;
                     }
                 }
                 // end foreach($va_vals as $vm_val)
             }
             foreach ($va_group_buf as $vn_group_index => $va_group_data) {
                 $va_ptr =& $va_content_tree;
                 foreach ($va_group_tmp as $vs_tmp) {
                     if (!is_array($va_ptr[$vs_tmp])) {
                         $va_ptr[$vs_tmp] = array();
                     }
                     $va_ptr =& $va_ptr[$vs_tmp];
                     if ($vs_tmp == $vs_target_table) {
                         // add numeric index after table to ensure repeat values don't overwrite each other
                         $va_parent =& $va_ptr;
                         $va_ptr[] = array();
                         $va_ptr =& $va_ptr[sizeof($va_ptr) - 1];
                     }
                 }
                 $va_ptr = $va_group_data;
             }
         }
         //
         // Process out self-relationships
         //
         if (is_array($va_content_tree[$vs_subject_table])) {
             $va_self_related_content = array();
             foreach ($va_content_tree[$vs_subject_table] as $vn_i => $va_element_data) {
                 if (isset($va_element_data['_relationship_type'])) {
                     $va_self_related_content[] = $va_element_data;
                     unset($va_content_tree[$vs_subject_table][$vn_i]);
                 }
             }
             if (sizeof($va_self_related_content) > 0) {
                 $va_content_tree["related.{$vs_subject_table}"] = $va_self_related_content;
             }
         }
         $vn_row++;
         $o_log->logDebug(_t('Finished building content tree for %1 at %2 seconds', $vs_idno, $t->getTime(4)));
         $o_log->logDebug(_t("Content tree is\n%1", print_R($va_content_tree, true)));
         //
         // Process data in subject record
         //
         //print_r($va_content_tree);
         //die("END\n\n");
         //continue;
         $opa_app_plugin_manager->hookDataImportContentTree(array('mapping' => $t_mapping, 'content_tree' => &$va_content_tree, 'idno' => &$vs_idno, 'transaction' => &$o_trans, 'log' => &$o_log, 'reader' => $o_reader, 'environment' => $va_environment, 'importEvent' => $o_event, 'importEventSource' => $vn_row));
         //print_r($va_content_tree);
         //die("done\n");
         if (!sizeof($va_content_tree) && !str_replace("%", "", $vs_idno)) {
             continue;
         }
         if (!$t_subject->getPrimaryKey()) {
             $o_event->beginItem($vn_row, $t_subject->tableNum(), 'I');
             $t_subject->setMode(ACCESS_WRITE);
             $t_subject->set($vs_type_id_fld, $vs_type);
             if ($vb_idno_is_template) {
                 $t_subject->setIdnoWithTemplate($vs_idno);
             } else {
                 $t_subject->set($vs_idno_fld, $vs_idno, array('assumeIdnoForRepresentationID' => true, 'assumeIdnoStubForLotID' => true));
                 // assumeIdnoStubForLotID forces ca_objects.lot_id values to always be considered as a potential idno_stub first, before use as a ca_objects.lot_id
             }
             // Look for parent_id in the content tree
             $vs_parent_id_fld = $t_subject->getProperty('HIERARCHY_PARENT_ID_FLD');
             foreach ($va_content_tree as $vs_table_name => $va_content) {
                 if ($vs_table_name == $vs_subject_table) {
                     foreach ($va_content as $va_element_data) {
                         foreach ($va_element_data as $vs_element => $va_element_content) {
                             switch ($vs_element) {
                                 case $vs_parent_id_fld:
                                     if ($va_element_content[$vs_parent_id_fld]) {
                                         $t_subject->set($vs_parent_id_fld, $va_element_content[$vs_parent_id_fld], array('treatParentIDAsIdno' => true));
                                     }
                                     break;
                             }
                         }
                     }
                 }
             }
             foreach ($va_mandatory_field_mapping_ids as $vs_mandatory_field => $vn_mandatory_mapping_item_id) {
                 $t_subject->set($vs_mandatory_field, $va_mandatory_field_values[$vs_mandatory_field], array('assumeIdnoStubForLotID' => true));
             }
             $t_subject->insert();
             if ($vs_error = DataMigrationUtils::postError($t_subject, _t("Could not insert new record"), array('dontOutputLevel' => true, 'dontPrint' => true))) {
                 ca_data_importers::logImportError($vs_error, $va_log_import_error_opts);
                 if ($vs_import_error_policy == 'stop') {
                     $o_log->logAlert(_t('Import stopped due to import error policy'));
                     if ($vb_use_ncurses) {
                         ncurses_end();
                     }
                     $o_event->endItem($t_subject->getPrimaryKey(), __CA_DATA_IMPORT_ITEM_FAILURE__, _t('Failed to import %1', $vs_idno));
                     if ($o_trans) {
                         $o_trans->rollback();
                     }
                     return false;
                 }
                 continue;
             }
             $o_log->logDebug(_t('Created idno %1 at %2 seconds', $vs_idno, $t->getTime(4)));
         } else {
             $o_event->beginItem($vn_row, $t_subject->tableNum(), 'U');
             // update
             $t_subject->setMode(ACCESS_WRITE);
             if ($vb_idno_is_template) {
                 $t_subject->setIdnoWithTemplate($vs_idno);
             } else {
                 $t_subject->set($vs_idno_fld, $vs_idno, array('assumeIdnoStubForLotID' => true));
                 // assumeIdnoStubForLotID forces ca_objects.lot_id values to always be considered as a potential idno_stub first, before use as a ca_objects.lot_di
             }
             $t_subject->update();
             if ($vs_error = DataMigrationUtils::postError($t_subject, _t("Could not update matched record"), array('dontOutputLevel' => true, 'dontPrint' => true))) {
                 ca_data_importers::logImportError($vs_error, $va_log_import_error_opts);
                 if ($vs_import_error_policy == 'stop') {
                     $o_log->logAlert(_t('Import stopped due to import error policy'));
                     if ($vb_use_ncurses) {
                         ncurses_end();
                     }
                     $o_event->endItem($t_subject->getPrimaryKey(), __CA_DATA_IMPORT_ITEM_FAILURE__, _t('Failed to import %1', $vs_idno));
                     if ($o_trans) {
                         $o_trans->rollback();
                     }
                     return false;
                 }
                 continue;
             }
             $t_subject->clearErrors();
             if (sizeof($va_preferred_label_mapping_ids) && $t_subject->getPreferredLabelCount() > 0) {
                 $t_subject->removeAllLabels(__CA_LABEL_TYPE_PREFERRED__);
                 if ($vs_error = DataMigrationUtils::postError($t_subject, _t("Could not update remove preferred labels from matched record"), array('dontOutputLevel' => true, 'dontPrint' => true))) {
                     ca_data_importers::logImportError($vs_error, $va_log_import_error_opts);
                     if ($vs_import_error_policy == 'stop') {
                         $o_log->logAlert(_t('Import stopped due to import error policy'));
                         if ($vb_use_ncurses) {
                             ncurses_end();
                         }
                         if ($o_trans) {
                             $o_trans->rollback();
                         }
                         $o_event->endItem($t_subject->getPrimaryKey(), __CA_DATA_IMPORT_ITEM_FAILURE__, _t('Failed to import %1', $vs_idno));
                         return false;
                     }
                 }
             }
             $o_log->logDebug(_t('Updated idno %1 at %2 seconds', $vs_idno, $t->getTime(4)));
         }
         if ($vs_idno_fld && ($o_idno = $t_subject->getIDNoPlugInInstance())) {
             $va_values = $o_idno->htmlFormValuesAsArray($vs_idno_fld, $t_subject->get($vs_idno_fld));
             if (!is_array($va_values)) {
                 $va_values = array($va_values);
             }
             if (($vs_proc_idno = join($o_idno->getSeparator(), $va_values)) && $vs_proc_idno != $vs_idno) {
                 $t_subject->set($vs_idno_fld, $vs_proc_idno);
                 $t_subject->update();
                 if ($vs_error = DataMigrationUtils::postError($t_subject, _t("Could update idno"), array('dontOutputLevel' => true, 'dontPrint' => true))) {
                     ca_data_importers::logImportError($vs_error, $va_log_import_error_opts);
                     if ($vs_import_error_policy == 'stop') {
                         $o_log->logAlert(_t('Import stopped due to import error policy'));
                         if ($vb_use_ncurses) {
                             ncurses_end();
                         }
                         $o_event->endItem($t_subject->getPrimaryKey(), __CA_DATA_IMPORT_ITEM_FAILURE__, _t('Failed to import %1', $vs_idno));
                         if ($o_trans) {
                             $o_trans->rollback();
                         }
                         return false;
                     }
                     continue;
                 }
             }
         }
         $va_elements_set_for_this_record = array();
         foreach ($va_content_tree as $vs_table_name => $va_content) {
             if ($vs_table_name == $vs_subject_table) {
                 foreach ($va_content as $vn_i => $va_element_data) {
                     foreach ($va_element_data as $vs_element => $va_element_content) {
                         if (is_array($va_element_content)) {
                             $vs_item_error_policy = $va_element_content['_errorPolicy'];
                             unset($va_element_content['_errorPolicy']);
                         } else {
                             $vs_item_error_policy = null;
                         }
                         $t_subject->clearErrors();
                         $t_subject->setMode(ACCESS_WRITE);
                         switch ($vs_element) {
                             case 'preferred_labels':
                                 $t_subject->addLabel($va_element_content, $vn_locale_id, isset($va_element_content['type_id']) ? $va_element_content['type_id'] : null, true);
                                 if ($t_subject->numErrors() == 0) {
                                     $vb_output_subject_preferred_label = true;
                                 }
                                 if ($vs_error = DataMigrationUtils::postError($t_subject, _t("[%1] Could not add preferred label to %2. Record was deleted because no preferred label could be applied: ", $vs_idno, $t_subject->tableName()), __CA_DATA_IMPORT_ERROR__, array('dontOutputLevel' => true, 'dontPrint' => true))) {
                                     ca_data_importers::logImportError($vs_error, $va_log_import_error_opts);
                                     $t_subject->delete(true, array('hard' => false));
                                     if ($vs_import_error_policy == 'stop') {
                                         $o_log->logAlert(_t('Import stopped due to import error policy %1', $vs_import_error_policy));
                                         if ($vb_use_ncurses) {
                                             ncurses_end();
                                         }
                                         $o_event->endItem($t_subject->getPrimaryKey(), __CA_DATA_IMPORT_ITEM_FAILURE__, _t('Failed to import %1', $vs_idno));
                                         if ($o_trans) {
                                             $o_trans->rollback();
                                         }
                                         return false;
                                     }
                                     if ($vs_item_error_policy == 'stop') {
                                         $o_log->logAlert(_t('Import stopped due to mapping error policy'));
                                         if ($vb_use_ncurses) {
                                             ncurses_end();
                                         }
                                         if ($o_trans) {
                                             $o_trans->rollback();
                                         }
                                         return false;
                                     }
                                     continue 5;
                                 }
                                 break;
                             case 'nonpreferred_labels':
                                 $t_subject->addLabel($va_element_content, $vn_locale_id, isset($va_element_content['type_id']) ? $va_element_content['type_id'] : null, false);
                                 if ($vs_error = DataMigrationUtils::postError($t_subject, _t("[%1] Could not add non-preferred label to %2:", $vs_idno, $t_subject->tableName()), __CA_DATA_IMPORT_ERROR__, array('dontOutputLevel' => true, 'dontPrint' => true))) {
                                     ca_data_importers::logImportError($vs_error, $va_log_import_error_opts);
                                     if ($vs_item_error_policy == 'stop') {
                                         $o_log->logAlert(_t('Import stopped due to mapping error policy'));
                                         if ($vb_use_ncurses) {
                                             ncurses_end();
                                         }
                                         $o_event->endItem($t_subject->getPrimaryKey(), __CA_DATA_IMPORT_ITEM_FAILURE__, _t('Failed to import %1', $vs_idno));
                                         if ($o_trans) {
                                             $o_trans->rollback();
                                         }
                                         return false;
                                     }
                                 }
                                 break;
                             default:
                                 if ($t_subject->hasField($vs_element)) {
                                     $t_subject->set($vs_element, $va_element_content[$vs_element], array('assumeIdnoStubForLotID' => true));
                                     $t_subject->update();
                                     break;
                                 }
                                 if ($vs_subject_table == 'ca_representation_annotations' && $vs_element == 'properties') {
                                     foreach ($va_element_content as $vs_prop => $vs_prop_val) {
                                         $t_subject->setPropertyValue($vs_prop, $vs_prop_val);
                                     }
                                     break;
                                 }
                                 if (is_array($va_element_content)) {
                                     $va_element_content['locale_id'] = $vn_locale_id;
                                 }
                                 if (!isset($va_elements_set_for_this_record[$vs_element]) && !$va_elements_set_for_this_record[$vs_element] && in_array($vs_existing_record_policy, array('merge_on_idno_with_replace', 'merge_on_preferred_labels_with_replace', 'merge_on_idno_and_preferred_labels_with_replace'))) {
                                     $t_subject->removeAttributes($vs_element, array('force' => true));
                                 }
                                 $va_elements_set_for_this_record[$vs_element] = true;
                                 $t_subject->addAttribute($va_element_content, $vs_element, null, array('showRepeatCountErrors' => true, 'alwaysTreatValueAsIdno' => true));
                                 if ($vs_error = DataMigrationUtils::postError($t_subject, _t("[%1] Failed to add value for %2; values were %3: ", $vs_idno, $vs_element, ca_data_importers::formatValuesForLog($va_element_content)), __CA_DATA_IMPORT_ERROR__, array('dontOutputLevel' => true, 'dontPrint' => true))) {
                                     ca_data_importers::logImportError($vs_error, $va_log_import_error_opts);
                                     if ($vs_item_error_policy == 'stop') {
                                         $o_log->logAlert(_t('Import stopped due to mapping error policy'));
                                         if ($vb_use_ncurses) {
                                             ncurses_end();
                                         }
                                         $o_event->endItem($t_subject->getPrimaryKey(), __CA_DATA_IMPORT_ITEM_FAILURE__, _t('Failed to import %1', $vs_idno));
                                         if ($o_trans) {
                                             $o_trans->rollback();
                                         }
                                         return false;
                                     }
                                 }
                                 $t_subject->update();
                                 if ($vs_error = DataMigrationUtils::postError($t_subject, _t("[%1] Invalid %2; values were %3: ", $vs_idno, $vs_element, ca_data_importers::formatValuesForLog($va_element_content)), __CA_DATA_IMPORT_ERROR__, array('dontOutputLevel' => true, 'dontPrint' => true))) {
                                     ca_data_importers::logImportError($vs_error, $va_log_import_error_opts);
                                     if ($vs_item_error_policy == 'stop') {
                                         $o_log->logAlert(_t('Import stopped due to mapping error policy'));
                                         if ($vb_use_ncurses) {
                                             ncurses_end();
                                         }
                                         $o_event->endItem($t_subject->getPrimaryKey(), __CA_DATA_IMPORT_ITEM_FAILURE__, _t('Failed to import %1', $vs_idno));
                                         if ($o_trans) {
                                             $o_trans->rollback();
                                         }
                                         return false;
                                     }
                                 }
                                 break;
                         }
                     }
                 }
             } else {
                 // related
                 $vs_table_name = preg_replace('!^related\\.!', '', $vs_table_name);
                 foreach ($va_content as $vn_i => $va_element_data) {
                     $va_match_on = caGetOption('_matchOn', $va_element_data, null);
                     $vb_dont_create = caGetOption('_dontCreate', $va_element_data, null);
                     $va_data_for_rel_table = $va_element_data;
                     $va_nonpreferred_labels = isset($va_data_for_rel_table['nonpreferred_labels']) ? $va_data_for_rel_table['nonpreferred_labels'] : null;
                     unset($va_data_for_rel_table['preferred_labels']);
                     unset($va_data_for_rel_table['_relationship_type']);
                     unset($va_data_for_rel_table['_type']);
                     unset($va_data_for_rel_table['_parent_id']);
                     unset($va_data_for_rel_table['_errorPolicy']);
                     unset($va_data_for_rel_table['_matchOn']);
                     $va_data_for_rel_table = array_merge($va_data_for_rel_table, ca_data_importers::_extractIntrinsicValues($va_data_for_rel_table, $vs_table_name));
                     $t_subject->clearErrors();
                     switch ($vs_table_name) {
                         case 'ca_objects':
                             if ($vn_rel_id = DataMigrationUtils::getObjectID($va_element_data['preferred_labels']['name'], $va_element_data['_parent_id'], $va_element_data['_type'], $vn_locale_id, $va_data_for_rel_table, array('dontCreate' => $vb_dont_create, 'matchOn' => $va_match_on, 'log' => $o_log, 'transaction' => $o_trans, 'importEvent' => $o_event, 'importEventSource' => $vn_row, 'nonPreferredLabels' => $va_nonpreferred_labels))) {
                                 if (!($vs_rel_type = $va_element_data['_relationship_type']) && !($vs_rel_type = $va_element_data['idno']['_relationship_type'])) {
                                     break;
                                 }
                                 $t_subject->addRelationship($vs_table_name, $vn_rel_id, trim($va_element_data['_relationship_type']), null, null, null, null, array('interstitialValues' => $va_element_data['_interstitial']));
                                 if ($vs_error = DataMigrationUtils::postError($t_subject, _t("[%1] Could not add related object with relationship %2:", $vs_idno, trim($va_element_data['_relationship_type'])), __CA_DATA_IMPORT_ERROR__, array('dontOutputLevel' => true, 'dontPrint' => true))) {
                                     ca_data_importers::logImportError($vs_error, $va_log_import_error_opts);
                                     if ($vs_item_error_policy == 'stop') {
                                         $o_log->logAlert(_t('Import stopped due to mapping error policy'));
                                         if ($vb_use_ncurses) {
                                             ncurses_end();
                                         }
                                         if ($o_trans) {
                                             $o_trans->rollback();
                                         }
                                         return false;
                                     }
                                 }
                             }
                             break;
                         case 'ca_object_lots':
                             $vs_idno_stub = null;
                             if (is_array($va_element_data['idno_stub'])) {
                                 $vs_idno_stub = isset($va_element_data['idno_stub']['idno_stub']) ? $va_element_data['idno_stub']['idno_stub'] : '';
                             } else {
                                 $vs_idno_stub = isset($va_element_data['idno_stub']) ? $va_element_data['idno_stub'] : '';
                             }
                             if ($vn_rel_id = DataMigrationUtils::getObjectLotID($vs_idno_stub, $va_element_data['preferred_labels']['name'], $va_element_data['_type'], $vn_locale_id, $va_data_for_rel_table, array('dontCreate' => $vb_dont_create, 'matchOn' => $va_match_on, 'log' => $o_log, 'transaction' => $o_trans, 'importEvent' => $o_event, 'importEventSource' => $vn_row, 'nonPreferredLabels' => $va_nonpreferred_labels))) {
                                 if (!($vs_rel_type = $va_element_data['_relationship_type']) && !($vs_rel_type = $va_element_data['idno_stub']['_relationship_type'])) {
                                     break;
                                 }
                                 $t_subject->addRelationship($vs_table_name, $vn_rel_id, trim($va_element_data['_relationship_type']), null, null, null, null, array('interstitialValues' => $va_element_data['_interstitial']));
                                 if ($vs_error = DataMigrationUtils::postError($t_subject, _t("[%1] Could not add related object lot with relationship %2:", $vs_idno, trim($va_element_data['_relationship_type'])), __CA_DATA_IMPORT_ERROR__, array('dontOutputLevel' => true, 'dontPrint' => true))) {
                                     ca_data_importers::logImportError($vs_error, $va_log_import_error_opts);
                                     if ($vs_item_error_policy == 'stop') {
                                         $o_log->logAlert(_t('Import stopped due to mapping error policy'));
                                         if ($vb_use_ncurses) {
                                             ncurses_end();
                                         }
                                         if ($o_trans) {
                                             $o_trans->rollback();
                                         }
                                         return false;
                                     }
                                 }
                             }
                             break;
                         case 'ca_entities':
                             if ($vn_rel_id = DataMigrationUtils::getEntityID($va_element_data['preferred_labels'], $va_element_data['_type'], $vn_locale_id, $va_data_for_rel_table, array('dontCreate' => $vb_dont_create, 'matchOn' => $va_match_on, 'log' => $o_log, 'transaction' => $o_trans, 'importEvent' => $o_event, 'importEventSource' => $vn_row, 'nonPreferredLabels' => $va_nonpreferred_labels))) {
                                 if (!($vs_rel_type = $va_element_data['_relationship_type']) && !($vs_rel_type = $va_element_data['idno']['_relationship_type'])) {
                                     break;
                                 }
                                 $t_subject->addRelationship($vs_table_name, $vn_rel_id, trim($va_element_data['_relationship_type']), null, null, null, null, array('interstitialValues' => $va_element_data['_interstitial']));
                                 if ($vs_error = DataMigrationUtils::postError($t_subject, _t("[%1] Could not add related entity with relationship %2:", $vs_idno, trim($va_element_data['_relationship_type'])), __CA_DATA_IMPORT_ERROR__, array('dontOutputLevel' => true, 'dontPrint' => true))) {
                                     ca_data_importers::logImportError($vs_error, $va_log_import_error_opts);
                                     if ($vs_item_error_policy == 'stop') {
                                         $o_log->logAlert(_t('Import stopped due to mapping error policy'));
                                         if ($vb_use_ncurses) {
                                             ncurses_end();
                                         }
                                         if ($o_trans) {
                                             $o_trans->rollback();
                                         }
                                         return false;
                                     }
                                 }
                             }
                             break;
                         case 'ca_places':
                             if ($vn_rel_id = DataMigrationUtils::getPlaceID($va_element_data['preferred_labels']['name'], $va_element_data['_parent_id'], $va_element_data['_type'], $vn_locale_id, $va_data_for_rel_table, array('dontCreate' => $vb_dont_create, 'matchOn' => $va_match_on, 'log' => $o_log, 'transaction' => $o_trans, 'importEvent' => $o_event, 'importEventSource' => $vn_row, 'nonPreferredLabels' => $va_nonpreferred_labels))) {
                                 if (!($vs_rel_type = $va_element_data['_relationship_type']) && !($vs_rel_type = $va_element_data['idno']['_relationship_type'])) {
                                     break;
                                 }
                                 $t_subject->addRelationship($vs_table_name, $vn_rel_id, trim($va_element_data['_relationship_type']), null, null, null, null, array('interstitialValues' => $va_element_data['_interstitial']));
                                 if ($vs_error = DataMigrationUtils::postError($t_subject, _t("[%1] Could not add related place with relationship %2:", $vs_idno, trim($va_element_data['_relationship_type'])), __CA_DATA_IMPORT_ERROR__, array('dontOutputLevel' => true, 'dontPrint' => true))) {
                                     ca_data_importers::logImportError($vs_error, $va_log_import_error_opts);
                                     if ($vs_item_error_policy == 'stop') {
                                         $o_log->logAlert(_t('Import stopped due to mapping error policy'));
                                         if ($vb_use_ncurses) {
                                             ncurses_end();
                                         }
                                         if ($o_trans) {
                                             $o_trans->rollback();
                                         }
                                         return false;
                                     }
                                 }
                             }
                             break;
                         case 'ca_collections':
                             if ($vn_rel_id = DataMigrationUtils::getCollectionID($va_element_data['preferred_labels']['name'], $va_element_data['_type'], $vn_locale_id, $va_data_for_rel_table, array('dontCreate' => $vb_dont_create, 'matchOn' => $va_match_on, 'log' => $o_log, 'transaction' => $o_trans, 'importEvent' => $o_event, 'importEventSource' => $vn_row, 'nonPreferredLabels' => $va_nonpreferred_labels))) {
                                 if (!($vs_rel_type = $va_element_data['_relationship_type']) && !($vs_rel_type = $va_element_data['idno']['_relationship_type'])) {
                                     break;
                                 }
                                 $t_subject->addRelationship($vs_table_name, $vn_rel_id, $vs_rel_type, null, null, null, null, array('interstitialValues' => $va_element_data['_interstitial']));
                                 if ($vs_error = DataMigrationUtils::postError($t_subject, _t("[%1] Could not add related collection with relationship %2:", $vs_idno, $vs_rel_type), __CA_DATA_IMPORT_ERROR__, array('dontOutputLevel' => true, 'dontPrint' => true))) {
                                     ca_data_importers::logImportError($vs_error, $va_log_import_error_opts);
                                     if ($vs_item_error_policy == 'stop') {
                                         $o_log->logAlert(_t('Import stopped due to mapping error policy'));
                                         if ($vb_use_ncurses) {
                                             ncurses_end();
                                         }
                                         if ($o_trans) {
                                             $o_trans->rollback();
                                         }
                                         return false;
                                     }
                                 }
                             }
                             break;
                         case 'ca_occurrences':
                             if ($vn_rel_id = DataMigrationUtils::getOccurrenceID($va_element_data['preferred_labels']['name'], $va_element_data['_parent_id'], $va_element_data['_type'], $vn_locale_id, $va_data_for_rel_table, array('dontCreate' => $vb_dont_create, 'matchOn' => $va_match_on, 'log' => $o_log, 'transaction' => $o_trans, 'importEvent' => $o_event, 'importEventSource' => $vn_row, 'nonPreferredLabels' => $va_nonpreferred_labels))) {
                                 if (!($vs_rel_type = $va_element_data['_relationship_type']) && !($vs_rel_type = $va_element_data['idno']['_relationship_type'])) {
                                     break;
                                 }
                                 $t_subject->addRelationship($vs_table_name, $vn_rel_id, $vs_rel_type, null, null, null, null, array('interstitialValues' => $va_element_data['_interstitial']));
                                 if ($vs_error = DataMigrationUtils::postError($t_subject, _t("[%1] Could not add related occurrence with relationship %2:", $vs_idno, $vs_rel_type), __CA_DATA_IMPORT_ERROR__, array('dontOutputLevel' => true, 'dontPrint' => true))) {
                                     ca_data_importers::logImportError($vs_error, $va_log_import_error_opts);
                                     if ($vs_item_error_policy == 'stop') {
                                         $o_log->logAlert(_t('Import stopped due to mapping error policy'));
                                         if ($vb_use_ncurses) {
                                             ncurses_end();
                                         }
                                         if ($o_trans) {
                                             $o_trans->rollback();
                                         }
                                         return false;
                                     }
                                 }
                             }
                             break;
                         case 'ca_storage_locations':
                             if ($vn_rel_id = DataMigrationUtils::getStorageLocationID($va_element_data['preferred_labels']['name'], $va_element_data['_parent_id'], $va_element_data['_type'], $vn_locale_id, $va_data_for_rel_table, array('dontCreate' => $vb_dont_create, 'matchOn' => $va_match_on, 'log' => $o_log, 'transaction' => $o_trans, 'importEvent' => $o_event, 'importEventSource' => $vn_row, 'nonPreferredLabels' => $va_nonpreferred_labels))) {
                                 if (!($vs_rel_type = $va_element_data['_relationship_type']) && !($vs_rel_type = $va_element_data['idno']['_relationship_type'])) {
                                     break;
                                 }
                                 $t_subject->addRelationship($vs_table_name, $vn_rel_id, trim($va_element_data['_relationship_type']), null, null, null, null, array('interstitialValues' => $va_element_data['_interstitial']));
                                 if ($vs_error = DataMigrationUtils::postError($t_subject, _t("[%1] Could not add related storage location with relationship %2:", $vs_idno, trim($va_element_data['_relationship_type'])), __CA_DATA_IMPORT_ERROR__, array('dontOutputLevel' => true, 'dontPrint' => true))) {
                                     ca_data_importers::logImportError($vs_error, $va_log_import_error_opts);
                                     if ($vs_item_error_policy == 'stop') {
                                         $o_log->logAlert(_t('Import stopped due to mapping error policy'));
                                         if ($vb_use_ncurses) {
                                             ncurses_end();
                                         }
                                         if ($o_trans) {
                                             $o_trans->rollback();
                                         }
                                         return false;
                                     }
                                 }
                             }
                             break;
                         case 'ca_list_items':
                             $va_data_for_rel_table['is_enabled'] = 1;
                             $va_data_for_rel_table['preferred_labels'] = $va_element_data['preferred_labels'];
                             if ($vn_rel_id = DataMigrationUtils::getListItemID($va_element_data['_list'], $va_element_data['idno'] ? $va_element_data['idno'] : null, $va_element_data['_type'], $vn_locale_id, $va_data_for_rel_table, array('dontCreate' => $vb_dont_create, 'matchOn' => $va_match_on, 'log' => $o_log, 'transaction' => $o_trans, 'importEvent' => $o_event, 'importEventSource' => $vn_row, 'nonPreferredLabels' => $va_nonpreferred_labels))) {
                                 if (!($vs_rel_type = $va_element_data['_relationship_type']) && !($vs_rel_type = $va_element_data['idno']['_relationship_type'])) {
                                     break;
                                 }
                                 $t_subject->addRelationship($vs_table_name, $vn_rel_id, trim($va_element_data['_relationship_type']), null, null, null, null, array('interstitialValues' => $va_element_data['_interstitial']));
                                 if ($vs_error = DataMigrationUtils::postError($t_subject, _t("[%1] Could not add related list item with relationship %2:", $vs_idno, trim($va_element_data['_relationship_type'])), __CA_DATA_IMPORT_ERROR__, array('dontOutputLevel' => true, 'dontPrint' => true))) {
                                     ca_data_importers::logImportError($vs_error, $va_log_import_error_opts);
                                     if ($vs_item_error_policy == 'stop') {
                                         $o_log->logAlert(_t('Import stopped due to mapping error policy'));
                                         if ($vb_use_ncurses) {
                                             ncurses_end();
                                         }
                                         if ($o_trans) {
                                             $o_trans->rollback();
                                         }
                                         return false;
                                     }
                                 }
                             }
                             break;
                         case 'ca_object_representations':
                             if ($vn_rel_id = DataMigrationUtils::getObjectRepresentationID($va_element_data['preferred_labels']['name'], $va_element_data['_type'], $vn_locale_id, $va_data_for_rel_table, array('dontCreate' => $vb_dont_create, 'matchOn' => $va_match_on, 'log' => $o_log, 'transaction' => $o_trans, 'importEvent' => $o_event, 'importEventSource' => $vn_row, 'nonPreferredLabels' => $va_nonpreferred_labels, 'matchMediaFilesWithoutExtension' => true))) {
                                 $t_subject->linkRepresentation($vn_rel_id, null, null, null, null, array('type_id' => trim($va_element_data['_relationship_type']), 'is_primary' => true));
                                 if ($vs_error = DataMigrationUtils::postError($t_subject, _t("[%1] Could not add related object representation with:", $vs_idno), __CA_DATA_IMPORT_ERROR__, array('dontOutputLevel' => true, 'dontPrint' => true))) {
                                     ca_data_importers::logImportError($vs_error, $va_log_import_error_opts);
                                     if ($vs_item_error_policy == 'stop') {
                                         $o_log->logAlert(_t('Import stopped due to mapping error policy'));
                                         if ($vb_use_ncurses) {
                                             ncurses_end();
                                         }
                                         if ($o_trans) {
                                             $o_trans->rollback();
                                         }
                                         return false;
                                     }
                                 }
                             }
                             //
                             // 									if (($vs_subject_table_name == 'ca_objects') && $va_element_data['media']['media']) {
                             // 										unset($va_data_for_rel_table['media']);
                             //
                             // 										foreach($va_data_for_rel_table as $vs_key => $vm_val) {
                             // 											// Attributes, including intrinsics are in two-level format, eg. idno is $va_attributes['idno']['idno']
                             // 											// but addRepresentations() expects intrinsics to be single level (eg. $va_attributes['idno']) so
                             // 											// we do some rewriting here
                             // 											if (is_array($vm_val) && isset($vm_val[$vs_key])) {
                             // 												$va_data_for_rel_table[$vs_key] = $vm_val[$vs_key];
                             // 											}
                             // 										}
                             //
                             // 										if (!($t_subject->addRepresentation($va_element_data['media']['media'], isset($va_element_data['_type']) ? $va_element_data['_type'] : caGetDefaultItemID('object_representation_types'), $vn_locale_id, 0, 0, true, $va_data_for_rel_table, array('dontCreate' => $vb_dont_create, 'matchOn' => $va_match_on)))) {
                             // 											$vs_error = join("; ", $t_subject->getErrors());
                             // 											ca_data_importers::logImportError($vs_error, $va_log_import_error_opts);
                             // 											if ($vs_item_error_policy == 'stop') {
                             // 												$o_log->logAlert(_t('Import stopped due to mapping error policy'));
                             // 												if($vb_use_ncurses) { ncurses_end(); }
                             // 												if ($o_trans) { $o_trans->rollback(); }
                             // 												return false;
                             // 											}
                             // 										}
                             // 									}
                             break;
                         case 'ca_loans':
                             if ($vn_rel_id = DataMigrationUtils::getLoanID($va_element_data['preferred_labels']['name'], $va_element_data['_type'], $vn_locale_id, $va_data_for_rel_table, array('dontCreate' => $vb_dont_create, 'matchOn' => $va_match_on, 'log' => $o_log, 'transaction' => $o_trans, 'importEvent' => $o_event, 'importEventSource' => $vn_row, 'nonPreferredLabels' => $va_nonpreferred_labels))) {
                                 if (!($vs_rel_type = $va_element_data['_relationship_type']) && !($vs_rel_type = $va_element_data['idno']['_relationship_type'])) {
                                     break;
                                 }
                                 $t_subject->addRelationship($vs_table_name, $vn_rel_id, trim($va_element_data['_relationship_type']), null, null, null, null, array('interstitialValues' => $va_element_data['_interstitial']));
                                 if ($vs_error = DataMigrationUtils::postError($t_subject, _t("[%1] Could not add related loan with relationship %2:", $vs_idno, trim($va_element_data['_relationship_type'])), __CA_DATA_IMPORT_ERROR__, array('dontOutputLevel' => true, 'dontPrint' => true))) {
                                     ca_data_importers::logImportError($vs_error, $va_log_import_error_opts);
                                     if ($vs_item_error_policy == 'stop') {
                                         $o_log->logAlert(_t('Import stopped due to mapping error policy'));
                                         if ($vb_use_ncurses) {
                                             ncurses_end();
                                         }
                                         if ($o_trans) {
                                             $o_trans->rollback();
                                         }
                                         return false;
                                     }
                                 }
                             }
                             break;
                         case 'ca_movements':
                             if ($vn_rel_id = DataMigrationUtils::getMovementID($va_element_data['preferred_labels']['name'], $va_element_data['_type'], $vn_locale_id, $va_data_for_rel_table, array('dontCreate' => $vb_dont_create, 'matchOn' => $va_match_on, 'log' => $o_log, 'transaction' => $o_trans, 'importEvent' => $o_event, 'importEventSource' => $vn_row, 'nonPreferredLabels' => $va_nonpreferred_labels))) {
                                 if (!($vs_rel_type = $va_element_data['_relationship_type']) && !($vs_rel_type = $va_element_data['idno']['_relationship_type'])) {
                                     break;
                                 }
                                 $t_subject->addRelationship($vs_table_name, $vn_rel_id, trim($va_element_data['_relationship_type']), null, null, null, null, array('interstitialValues' => $va_element_data['_interstitial']));
                                 if ($vs_error = DataMigrationUtils::postError($t_subject, _t("[%1] Could not add related movement with relationship %2:", $vs_idno, trim($va_element_data['_relationship_type'])), __CA_DATA_IMPORT_ERROR__, array('dontOutputLevel' => true, 'dontPrint' => true))) {
                                     ca_data_importers::logImportError($vs_error, $va_log_import_error_opts);
                                     if ($vs_item_error_policy == 'stop') {
                                         $o_log->logAlert(_t('Import stopped due to mapping error policy'));
                                         if ($vb_use_ncurses) {
                                             ncurses_end();
                                         }
                                         if ($o_trans) {
                                             $o_trans->rollback();
                                         }
                                         return false;
                                     }
                                 }
                             }
                             break;
                     }
                     if (is_array($va_element_data['_related_related']) && sizeof($va_element_data['_related_related'])) {
                         foreach ($va_element_data['_related_related'] as $vs_rel_rel_table => $va_rel_rels) {
                             foreach ($va_rel_rels as $vn_i => $va_rel_rel) {
                                 if (!($t_rel_instance = $o_dm->getInstanceByTableName($vs_table_name))) {
                                     $o_log->logWarn(_t("[%1] Could not instantiate related table %2", $vs_idno, $vs_table_name));
                                     continue;
                                 }
                                 if ($o_trans) {
                                     $t_rel_instance->setTransaction($o_trans);
                                 }
                                 if ($t_rel_instance->load($vn_rel_id)) {
                                     if ($t_rel_rel = $t_rel_instance->addRelationship($vs_rel_rel_table, $va_rel_rel['id'], $va_rel_rel['_relationship_type'])) {
                                         $o_log->logInfo(_t('[%1] Related %2 (%3) to related %4 with relationship %5', $vs_idno, $o_dm->getTableProperty($vs_rel_rel_table, 'NAME_SINGULAR'), $va_rel_rel['id'], $t_rel_instance->getProperty('NAME_SINGULAR'), trim($va_rel_rel['_relationship_type'])));
                                     } else {
                                         if ($vs_error = DataMigrationUtils::postError($t_subject, _t("[%1] Could not add related %2 (%3) to related %4 with relationship %5:", $vs_idno, $o_dm->getTableProperty($vs_rel_rel_table, 'NAME_SINGULAR'), $va_rel_rel['id'], $t_rel_instance->getProperty('NAME_SINGULAR'), trim($va_rel_rel['_relationship_type'])), __CA_DATA_IMPORT_ERROR__, array('dontOutputLevel' => true, 'dontPrint' => true))) {
                                             ca_data_importers::logImportError($vs_error, $va_log_import_error_opts);
                                         }
                                     }
                                 }
                             }
                         }
                     }
                 }
             }
         }
         // $t_subject->update();
         //
         // 			if ($vs_error = DataMigrationUtils::postError($t_subject, _t("[%1] Invalid %2; values were %3: ", $vs_idno, 'attributes', ca_data_importers::formatValuesForLog($va_element_content)), __CA_DATA_IMPORT_ERROR__, array('dontOutputLevel' => true, 'dontPrint' => true))) {
         // 				ca_data_importers::logImportError($vs_error, $va_log_import_error_opts);
         // 				if ($vs_item_error_policy == 'stop') {
         // 					$o_log->logAlert(_t('Import stopped due to mapping error policy'));
         // 					if($vb_use_ncurses) { ncurses_end(); }
         //
         // 					$o_event->endItem($t_subject->getPrimaryKey(), __CA_DATA_IMPORT_ITEM_FAILURE__, _t('Failed to import %1', $vs_idno));
         //
         // 					if ($o_trans) { $o_trans->rollback(); }
         // 					return false;
         // 				}
         // 			}
         //
         $o_log->logDebug(_t('Finished inserting content tree for %1 at %2 seconds into database', $vs_idno, $t->getTime(4)));
         if (!$vb_output_subject_preferred_label && $t_subject->getPreferredLabelCount() == 0) {
             $t_subject->addLabel(array($vs_label_display_fld => '???'), $vn_locale_id, null, true);
             if ($vs_error = DataMigrationUtils::postError($t_subject, _t("[%1] Could not add default label", $vs_idno), __CA_DATA_IMPORT_ERROR__, array('dontOutputLevel' => true, 'dontPrint' => true))) {
                 ca_data_importers::logImportError($vs_error, $va_log_import_error_opts);
                 if ($vs_import_error_policy == 'stop') {
                     $o_log->logAlert(_t('Import stopped due to import error policy'));
                     if ($vb_use_ncurses) {
                         ncurses_end();
                     }
                     $o_event->endItem($t_subject->getPrimaryKey(), __CA_DATA_IMPORT_ITEM_FAILURE__, _t('Failed to import %1', $vs_idno));
                     if ($o_trans) {
                         $o_trans->rollback();
                     }
                     return false;
                 }
             }
         }
         $o_log->logInfo(_t('[%1] Imported %2 as %3 ', $vs_idno, $vs_preferred_label_for_log, $vs_subject_table_name));
         $o_event->endItem($t_subject->getPrimaryKey(), __CA_DATA_IMPORT_ITEM_SUCCESS__, _t('Imported %1', $vs_idno));
         ca_data_importers::$s_num_records_processed++;
     }
     $o_log->logInfo(_t('Import of %1 completed using mapping %2: %3 imported/%4 skipped/%5 errors', $ps_source, $t_mapping->get('importer_code'), ca_data_importers::$s_num_records_processed, ca_data_importers::$s_num_records_skipped, ca_data_importers::$s_num_import_errors));
     //if ($vb_show_cli_progress_bar) {
     $o_progress->finish();
     //}
     if ($po_request && isset($pa_options['progressCallback']) && ($ps_callback = $pa_options['progressCallback'])) {
         $ps_callback($po_request, $pn_file_number, $pn_number_of_files, $ps_source, $vn_num_items, $vn_num_items, _t('Import completed'), time() - $vn_start_time, memory_get_usage(true), ca_data_importers::$s_num_records_processed, ca_data_importers::$s_num_import_errors);
     }
     if (isset($pa_options['reportCallback']) && ($ps_callback = $pa_options['reportCallback'])) {
         $va_general = array('elapsedTime' => time() - $vn_start_time, 'numErrors' => ca_data_importers::$s_num_import_errors, 'numProcessed' => ca_data_importers::$s_num_records_processed);
         $ps_callback($po_request, $va_general, ca_data_importers::$s_import_error_list, true);
     }
     if ($vb_use_ncurses) {
         ncurses_end();
     }
     if ($pb_dry_run) {
         if ($o_trans) {
             $o_trans->rollback();
         }
         $o_log->logInfo(_t('Rollback successful import run in "dry run" mode'));
     } else {
         if ($o_trans) {
             $o_trans->commit();
         }
     }
     return true;
 }
 /**
  * Prepopulate record fields according to rules in prepopulate.conf
  *
  * @param array $pa_options Options array. Available options are:
  * 		prepopulateConfig = override path to prepopulate.conf, e.g. for testing purposes
  * @return bool success or not
  */
 public function prepopulateFields($pa_options = null)
 {
     if (!$this->getPrimaryKey()) {
         return false;
     }
     if (!($vs_prepopulate_cfg = caGetOption('prepopulateConfig', $pa_options, null))) {
         $vs_prepopulate_cfg = $this->getAppConfig()->get('prepopulate_config');
     }
     $o_prepopulate_conf = Configuration::load($vs_prepopulate_cfg);
     if (!($o_prepopulate_conf->get('prepopulate_fields_on_save') || $o_prepopulate_conf->get('prepopulate_fields_on_load'))) {
         return false;
     }
     $va_rules = $o_prepopulate_conf->get('prepopulate_rules');
     if (!$va_rules || !is_array($va_rules) || sizeof($va_rules) < 1) {
         return false;
     }
     global $g_ui_locale_id;
     // we need to unset the form timestamp to disable the 'Changes have been made since you loaded this data' warning when we update() $this
     // the warning makes sense because an update()/insert() is called before we arrive here but after the form_timestamp ... but we chose to ignore it
     $vn_timestamp = $_REQUEST['form_timestamp'];
     unset($_REQUEST['form_timestamp']);
     $vb_we_set_transaction = true;
     if (!$this->inTransaction()) {
         $this->setTransaction(new Transaction($this->getDb()));
         $vb_we_set_transaction = true;
     }
     // process rules
     $va_expression_vars = array();
     // we only process those if and when we need them
     foreach ($va_rules as $vs_rule_key => $va_rule) {
         if ($this->tableName() != $va_rule['table']) {
             continue;
         }
         // check target
         $vs_target = $va_rule['target'];
         if (strlen($vs_target) < 1) {
             Debug::msg("[prepopulateFields()] skipping rule {$vs_rule_key} because target is not set");
             continue;
         }
         // check template
         $vs_template = $va_rule['template'];
         if (strlen($vs_template) < 1) {
             Debug::msg("[prepopulateFields()] skipping rule {$vs_rule_key} because template is not set");
             continue;
         }
         $vs_mode = caGetOption('mode', $va_rule, 'merge');
         // respect restrictToTypes option
         if ($va_rule['restrictToTypes'] && is_array($va_rule['restrictToTypes']) && sizeof($va_rule['restrictToTypes']) > 0) {
             if (!in_array($this->getTypeCode(), $va_rule['restrictToTypes'])) {
                 Debug::msg("[prepopulateFields()] skipping rule {$vs_rule_key} because current record type " . $this->getTypeCode() . " is not in restrictToTypes");
                 continue;
             }
         }
         // skip this rule if expression is true
         if ($va_rule['skipIfExpression'] && strlen($va_rule['skipIfExpression']) > 0) {
             $va_tags = caGetTemplateTags($va_rule['skipIfExpression']);
             foreach ($va_tags as $vs_tag) {
                 if (!isset($va_expression_vars[$vs_tag])) {
                     $va_expression_vars[$vs_tag] = $this->get($vs_tag, array('returnIdno' => true, 'delimiter' => ';'));
                 }
             }
             if (ExpressionParser::evaluate($va_rule['skipIfExpression'], $va_expression_vars)) {
                 Debug::msg("[prepopulateFields()] skipping rule {$vs_rule_key} because skipIfExpression evaluated true");
                 continue;
             }
         }
         // evaluate template
         $vs_value = caProcessTemplateForIDs($vs_template, $this->tableNum(), array($this->getPrimaryKey()), array('path' => true));
         Debug::msg("[prepopulateFields()] processed template for rule {$vs_rule_key} value is: " . $vs_value);
         // inject into target
         $va_parts = explode('.', $vs_target);
         // intrinsic or simple (non-container) attribute
         if (sizeof($va_parts) == 2) {
             // intrinsic
             if ($this->hasField($va_parts[1])) {
                 switch (strtolower($vs_mode)) {
                     case 'overwrite':
                         // always set
                         $this->set($va_parts[1], $vs_value);
                         break;
                     case 'addifempty':
                     default:
                         if (!$this->get($va_parts[1])) {
                             $this->set($va_parts[1], $vs_value);
                         } else {
                             Debug::msg("[prepopulateFields()] rule {$vs_rule_key}: intrinsic skipped because it already has value and mode is addIfEmpty or merge");
                         }
                         break;
                 }
                 // attribute/element
             } elseif ($this->hasElement($va_parts[1])) {
                 $va_attributes = $this->getAttributesByElement($va_parts[1]);
                 if (sizeof($va_attributes) > 1) {
                     Debug::msg("[prepopulateFields()] containers with multiple values are not supported");
                     continue;
                 }
                 switch (strtolower($vs_mode)) {
                     case 'overwrite':
                         // always replace first value we find
                         $this->replaceAttribute(array($va_parts[1] => $vs_value, 'locale_id' => $g_ui_locale_id), $va_parts[1]);
                         break;
                     default:
                     case 'addifempty':
                         // only add value if none exists
                         if (!$this->get($vs_target)) {
                             $this->replaceAttribute(array($va_parts[1] => $vs_value, 'locale_id' => $g_ui_locale_id), $va_parts[1]);
                         }
                         break;
                 }
             }
             // "container"
         } elseif (sizeof($va_parts) == 3) {
             // actual container
             if ($this->hasElement($va_parts[1])) {
                 $va_attr = $this->getAttributesByElement($va_parts[1]);
                 switch (sizeof($va_attr)) {
                     case 1:
                         switch (strtolower($vs_mode)) {
                             case 'overwrite':
                                 $vo_attr = array_pop($va_attr);
                                 $va_value = array($va_parts[2] => $vs_value);
                                 foreach ($vo_attr->getValues() as $o_val) {
                                     if ($o_val->getElementCode() != $va_parts[2]) {
                                         $va_value[$o_val->getElementCode()] = $o_val->getDisplayValue();
                                     }
                                 }
                                 $this->_editAttribute($vo_attr->getAttributeID(), $va_value);
                                 break;
                             case 'addifempty':
                                 $vo_attr = array_pop($va_attr);
                                 $va_value = array($va_parts[2] => $vs_value);
                                 $vb_update = false;
                                 foreach ($vo_attr->getValues() as $o_val) {
                                     if ($o_val->getElementCode() != $va_parts[2]) {
                                         $va_value[$o_val->getElementCode()] = $o_val->getDisplayValue();
                                     } else {
                                         if (!$o_val->getDisplayValue()) {
                                             $vb_update = true;
                                         }
                                     }
                                 }
                                 if ($vb_update) {
                                     $this->editAttribute($vo_attr->getAttributeID(), $va_parts[1], $va_value);
                                 }
                                 break;
                             default:
                                 Debug::msg("[prepopulateFields()] unsupported mode {$vs_mode} for target bundle");
                                 break;
                         }
                         break;
                     case 0:
                         // if no container value exists, always add it (ignoring mode)
                         $this->addAttribute(array($va_parts[2] => $vs_value, 'locale_id' => $g_ui_locale_id), $va_parts[1]);
                         break;
                     default:
                         Debug::msg("[prepopulateFields()] containers with multiple values are not supported");
                         break;
                 }
                 // labels
             } elseif ($va_parts[1] == 'preferred_labels' || $va_parts[1] == 'nonpreferred_labels') {
                 $vb_preferred = $va_parts[1] == 'preferred_labels';
                 if (!($t_label = $this->getAppDatamodel()->getInstanceByTableName($this->getLabelTableName(), true))) {
                     continue;
                 }
                 if (!$t_label->hasField($va_parts[2])) {
                     continue;
                 }
                 switch ($this->getLabelCount($vb_preferred)) {
                     case 0:
                         // if no value exists, always add it (ignoring mode)
                         $this->addLabel(array($va_parts[2] => $vs_value), $g_ui_locale_id, null, $vb_preferred);
                         break;
                     case 1:
                         switch (strtolower($vs_mode)) {
                             case 'overwrite':
                             case 'addifempty':
                                 $va_labels = $this->getLabels(null, $vb_preferred ? __CA_LABEL_TYPE_PREFERRED__ : __CA_LABEL_TYPE_NONPREFERRED__);
                                 if (sizeof($va_labels)) {
                                     $va_labels = caExtractValuesByUserLocale($va_labels);
                                     $va_label = array_shift($va_labels);
                                     $va_label = $va_label[0];
                                     $va_label[$va_parts[2]] = $vs_value;
                                     $vb_update = false;
                                     if (strtolower($vs_mode) == 'overwrite') {
                                         $va_label[$va_parts[2]] = $vs_value;
                                         $vb_update = true;
                                     } else {
                                         if (strlen(trim($va_label[$va_parts[2]])) == 0) {
                                             // in addifempty mode only edit label when field is not set
                                             $va_label[$va_parts[2]] = $vs_value;
                                             $vb_update = true;
                                         }
                                     }
                                     if ($vb_update) {
                                         $this->editLabel($va_label['label_id'], $va_label, $g_ui_locale_id, null, $vb_preferred);
                                     }
                                 } else {
                                     $this->addLabel(array($va_parts[2] => $vs_value), $g_ui_locale_id, null, $vb_preferred);
                                 }
                                 break;
                             default:
                                 Debug::msg("[prepopulateFields()] unsupported mode {$vs_mode} for target bundle");
                                 break;
                         }
                         break;
                     default:
                         Debug::msg("[prepopulateFields()] records with multiple labels are not supported");
                         break;
                 }
             }
         }
     }
     $vn_old_mode = $this->getMode();
     $this->setMode(ACCESS_WRITE);
     $this->update();
     $this->setMode($vn_old_mode);
     $_REQUEST['form_timestamp'] = $vn_timestamp;
     if ($this->numErrors() > 0) {
         foreach ($this->getErrors() as $vs_error) {
             Debug::msg("[prepopulateFields()] there was an error while updating the record: " . $vs_error);
         }
         if ($vb_we_set_transaction) {
             $this->removeTransaction(false);
         }
         return false;
     }
     if ($vb_we_set_transaction) {
         $this->removeTransaction(true);
     }
     return true;
 }
Example #21
0
 /**
  * get() value for attribute
  *
  * @param array $pa_value_list
  * @param BaseModel $pt_instance
  * @param array Options
  *
  * @return array|string
  */
 private function _getAttributeValue($pa_value_list, $pt_instance, $pa_options)
 {
     $va_path_components =& $pa_options['pathComponents'];
     $vs_delimiter = isset($pa_options['delimiter']) ? $pa_options['delimiter'] : ';';
     $va_return_values = array();
     $vn_id = $this->get($pt_instance->primaryKey(true));
     $vs_table_name = $pt_instance->tableName();
     if (is_array($pa_value_list) && sizeof($pa_value_list)) {
         $va_val_proc = array();
         foreach ($pa_value_list as $o_attribute) {
             $t_attr_element = $pt_instance->_getElementInstance($o_attribute->getElementID());
             $vn_attr_type = $t_attr_element->get('datatype');
             $va_acc = array();
             $va_values = $o_attribute->getValues();
             if ($pa_options['useLocaleCodes']) {
                 if (!$o_attribute->getLocaleID() || !($vm_locale_id = SearchResult::$opo_locales->localeIDToCode($o_attribute->getLocaleID()))) {
                     $vm_locale_id = __CA_DEFAULT_LOCALE__;
                 }
             } else {
                 if (!($vm_locale_id = $o_attribute->getLocaleID())) {
                     $vm_locale_id = SearchResult::$opo_locales->localeCodeToID(__CA_DEFAULT_LOCALE__);
                 }
             }
             $vb_did_return_value = false;
             foreach ($va_values as $o_value) {
                 $vs_val_proc = null;
                 $vb_dont_return_value = false;
                 $vs_element_code = $o_value->getElementCode();
                 $va_auth_spec = null;
                 if (is_a($o_value, "AuthorityAttributeValue")) {
                     $va_auth_spec = $va_path_components['components'];
                     if ($pt_instance->hasElement($va_path_components['subfield_name'], null, true, array('dontCache' => false))) {
                         array_shift($va_auth_spec);
                         array_shift($va_auth_spec);
                         array_shift($va_auth_spec);
                     } elseif ($pt_instance->hasElement($va_path_components['field_name'], null, true, array('dontCache' => false))) {
                         array_shift($va_auth_spec);
                         array_shift($va_auth_spec);
                         $va_path_components['subfield_name'] = null;
                     }
                 }
                 if ($va_path_components['subfield_name'] && $va_path_components['subfield_name'] !== $vs_element_code && !$o_value instanceof InformationServiceAttributeValue) {
                     $vb_dont_return_value = true;
                     if (!$pa_options['filter']) {
                         continue;
                     }
                 }
                 if (is_a($o_value, "AuthorityAttributeValue") && sizeof($va_auth_spec) > 0) {
                     array_unshift($va_auth_spec, $vs_auth_table_name = $o_value->tableName());
                     if ($qr_res = caMakeSearchResult($vs_auth_table_name, array($o_value->getID()))) {
                         if ($qr_res->nextHit()) {
                             unset($pa_options['returnWithStructure']);
                             $va_options['returnAsArray'] = true;
                             $va_val_proc = $qr_res->get(join(".", $va_auth_spec), $pa_options);
                             if (is_array($va_val_proc)) {
                                 foreach ($va_val_proc as $vn_i => $vs_v) {
                                     $va_return_values[(int) $vn_id][$vm_locale_id][(int) $o_attribute->getAttributeID() . "_{$vn_i}"][$vs_element_code] = $vs_v;
                                 }
                             }
                         }
                     }
                     continue;
                 }
                 if (is_null($vs_val_proc)) {
                     switch ($o_value->getType()) {
                         case __CA_ATTRIBUTE_VALUE_LIST__:
                             $t_element = $pt_instance->_getElementInstance($o_value->getElementID());
                             $vn_list_id = $t_element->get('list_id');
                             $vs_val_proc = $o_value->getDisplayValue(array_merge($pa_options, array('output' => $pa_options['output'], 'list_id' => $vn_list_id)));
                             break;
                         case __CA_ATTRIBUTE_VALUE_INFORMATIONSERVICE__:
                             //ca_objects.informationservice.ulan_container
                             // support subfield notations like ca_objects.wikipedia.abstract, but only if we're not already at subfield-level, e.g. ca_objects.container.wikipedia
                             if ($va_path_components['subfield_name'] && $vs_element_code != $va_path_components['subfield_name'] && $vs_element_code == $va_path_components['field_name']) {
                                 $vs_val_proc = $o_value->getExtraInfo($va_path_components['subfield_name']);
                                 $vb_dont_return_value = false;
                                 break;
                             }
                             // support ca_objects.container.wikipedia.abstract
                             if ($vs_element_code == $va_path_components['subfield_name'] && $va_path_components['num_components'] == 4) {
                                 $vs_val_proc = $o_value->getExtraInfo($va_path_components['components'][3]);
                                 $vb_dont_return_value = false;
                                 break;
                             }
                             // support ca_objects.wikipedia or ca_objects.container.wikipedia (Eg. no "extra" value specified)
                             if ($vs_element_code == $va_path_components['field_name'] || $vs_element_code == $va_path_components['subfield_name']) {
                                 $vs_val_proc = $o_value->getDisplayValue(array_merge($pa_options, array('output' => $pa_options['output'])));
                                 $vb_dont_return_value = false;
                                 break;
                             }
                             continue 2;
                         default:
                             $vs_val_proc = $o_value->getDisplayValue(array_merge($pa_options, array('output' => $pa_options['output'])));
                             break;
                     }
                 }
                 if ($vn_attr_type == __CA_ATTRIBUTE_VALUE_CONTAINER__ && !$va_path_components['subfield_name'] && !$pa_options['returnWithStructure']) {
                     if (strlen($vs_val_proc) > 0) {
                         $va_val_proc[] = $vs_val_proc;
                     }
                     $vs_val_proc = join($vs_delimiter, $va_val_proc);
                 }
                 $va_spec = $va_path_components['components'];
                 array_pop($va_spec);
                 $va_acc[join('.', $va_spec) . '.' . $vs_element_code] = $o_value->getDisplayValue(array_merge($pa_options, array('output' => 'idno')));
                 if (!$vb_dont_return_value) {
                     $vb_did_return_value = true;
                     if ($pa_options['makeLink']) {
                         $vs_val_proc = array_shift(caCreateLinksFromText(array($vs_val_proc), $vs_table_name, array($vn_id)));
                     }
                     if ($pa_options['returnWithStructure']) {
                         $va_return_values[(int) $vn_id][$vm_locale_id][(int) $o_attribute->getAttributeID()][$vs_element_code] = $vs_val_proc;
                     } else {
                         $va_return_values[(int) $vn_id][$vm_locale_id][(int) $o_attribute->getAttributeID()] = $vs_val_proc;
                     }
                 }
             }
             if ($va_path_components['subfield_name'] && $pa_options['returnBlankValues'] && !$vb_did_return_value) {
                 // value is missing so insert blank
                 if ($pa_options['returnWithStructure']) {
                     $va_return_values[(int) $vn_id][$vm_locale_id][(int) $o_attribute->getAttributeID()][$va_path_components['subfield_name']] = '';
                 } else {
                     $va_return_values[(int) $vn_id][$vm_locale_id][(int) $o_attribute->getAttributeID()] = '';
                 }
             }
             if ($pa_options['filter']) {
                 $va_tags = caGetTemplateTags($pa_options['filter']);
                 $va_vars = array();
                 foreach ($va_tags as $vs_tag) {
                     if (isset($va_acc[$vs_tag])) {
                         $va_vars[$vs_tag] = $va_acc[$vs_tag];
                     } else {
                         $va_vars[$vs_tag] = $this->get($vs_tag, array('convertCodesToIdno' => true));
                     }
                 }
                 if (ExpressionParser::evaluate($pa_options['filter'], $va_vars)) {
                     unset($va_return_values[(int) $vn_id][$vm_locale_id][(int) $o_attribute->getAttributeID()]);
                     continue;
                 }
             }
         }
     } else {
         // is blank
         if ($pa_options['returnWithStructure'] && $pa_options['returnBlankValues']) {
             $va_return_values[(int) $vn_id][null][null][$va_path_components['subfield_name'] ? $va_path_components['subfield_name'] : $va_path_components['field_name']] = '';
         }
     }
     if (!$pa_options['returnAllLocales']) {
         $va_return_values = caExtractValuesByUserLocale($va_return_values);
     }
     if ($pa_options['returnWithStructure']) {
         return is_array($va_return_values) ? $va_return_values : array();
     }
     //
     // Flatten array for return as string or simple array value
     //
     $va_flattened_values = $this->_flattenArray($va_return_values, $pa_options);
     if ($pa_options['returnAsArray']) {
         return $va_flattened_values;
     } else {
         return sizeof($va_flattened_values) > 0 ? join($pa_options['delimiter'], $va_flattened_values) : null;
     }
 }
Example #22
0
/**
 * Replace "^" prefixed tags (eg. ^forename) in a template with values from an array
 *
 * @param string $ps_template String with embedded tags. Tags are just alphanumeric strings prefixed with a caret ("^")
 * @param string $pm_tablename_or_num Table name or number of table from which values are being formatted
 * @param string $pa_row_ids An array of primary key values in the specified table to be pulled into the template
 * @param array $pa_options Supported options are:
 *		returnAsArray = if true an array of processed template values is returned, otherwise the template values are returned as a string joined together with a delimiter. Default is false.
 *		delimiter = value to string together template values with when returnAsArray is false. Default is ';' (semicolon)
 *		relatedValues = array of field values to return in template when directly referenced. Array should be indexed numerically in parallel with $pa_row_ids
 *		relationshipValues = array of field values to return in template for relationship when directly referenced. Should be indexed by row_id and then by relation_id
 *		placeholderPrefix = attribute container to implicitly place primary record fields into. Ex. if the table is "ca_entities" and the placeholder is "address" then tags like ^city will resolve to ca_entities.address.city
 *		requireLinkTags = if set then links are only added when explicitly defined with <l> tags. Default is to make the entire text a link in the absence of <l> tags.
 *		resolveLinksUsing = 
 *		primaryIDs = row_ids for primary rows in related table, keyed by table name; when resolving ambiguous relationships the row_ids will be excluded from consideration. This option is rarely used and exists primarily to take care of a single
 *						edge case: you are processing a template relative to a self-relationship such as ca_entities_x_entities that includes references to the subject table (ca_entities, in the case of ca_entities_x_entities). There are
 *						two possible paths to take in this situations; primaryIDs lets you specify which ones you *don't* want to take by row_id. For interstitial editors, the ids will be set to a single id: that of the subject (Eg. ca_entities) row
 *						from which the interstitial was launched.
 *		sort = optional list of tag values to sort repeating values within a row template on. The tag must appear in the template. You can specify more than one tag by separating the tags with semicolons.
 *		sortDirection = The direction of the sort of repeating values within a row template. May be either ASC (ascending) or DESC (descending). [Default is ASC]
 *		linkTarget = Optional target to use when generating <l> tag-based links. By default links point to standard detail pages, but plugins may define linkTargets that point elsewhere.
 * 		skipIfExpression = skip the elements in $pa_row_ids for which the given expression does not evaluate true
 *		includeBlankValuesInArray = include blank template values in returned array when returnAsArray is set. If you need the returned array of values to line up with the row_ids in $pa_row_ids this should be set. [Default is false]
 *
 * @return mixed Output of processed templates
 */
function caProcessTemplateForIDs($ps_template, $pm_tablename_or_num, $pa_row_ids, $pa_options = null)
{
    foreach (array('request', 'template', 'restrictToTypes', 'restrict_to_types', 'restrict_to_relationship_types', 'restrictToRelationshipTypes', 'useLocaleCodes') as $vs_k) {
        unset($pa_options[$vs_k]);
    }
    if (!isset($pa_options['convertCodesToDisplayText'])) {
        $pa_options['convertCodesToDisplayText'] = true;
    }
    $pb_return_as_array = (bool) caGetOption('returnAsArray', $pa_options, false);
    if (($pa_sort = caGetOption('sort', $pa_options, null)) && !is_array($pa_sort)) {
        $pa_sort = explode(";", $pa_sort);
    }
    $ps_sort_direction = caGetOption('sortDirection', $pa_options, null, array('forceUppercase' => true));
    if (!in_array($ps_sort_direction, array('ASC', 'DESC'))) {
        $ps_sort_direction = 'ASC';
    }
    $pa_check_access = caGetOption('checkAccess', $pa_options, null);
    if (!is_array($pa_row_ids) || !sizeof($pa_row_ids) || !$ps_template) {
        return $pb_return_as_array ? array() : "";
    }
    unset($pa_options['returnAsArray']);
    if (!isset($pa_options['requireLinkTags'])) {
        $pa_options['requireLinkTags'] = true;
    }
    $ps_skip_if_expression = caGetOption('skipIfExpression', $pa_options, false);
    $va_primary_ids = caGetOption("primaryIDs", $pa_options, null);
    $o_dm = Datamodel::load();
    $ps_tablename = is_numeric($pm_tablename_or_num) ? $o_dm->getTableName($pm_tablename_or_num) : $pm_tablename_or_num;
    $ps_resolve_links_using = caGetOption('resolveLinksUsing', $pa_options, $ps_tablename);
    $t_instance = $o_dm->getInstanceByTableName($ps_tablename, true);
    if ($ps_resolve_links_using != $ps_tablename) {
        $t_resolve_links_instance = $o_dm->getInstanceByTableName($ps_resolve_links_using, true);
        $vs_resolve_links_using_pk = $t_resolve_links_instance->primaryKey();
    }
    $vs_pk = $t_instance->primaryKey();
    $vs_delimiter = isset($pa_options['delimiter']) ? $pa_options['delimiter'] : '; ';
    $ps_template = str_replace("^_parent", "^{$ps_resolve_links_using}.parent.preferred_labels", $ps_template);
    $ps_template = str_replace("^_hierarchy", "^{$ps_resolve_links_using}._hierarchyName", $ps_template);
    $va_related_values = isset($pa_options['relatedValues']) && is_array($pa_options['relatedValues']) ? $pa_options['relatedValues'] : array();
    $va_relationship_values = isset($pa_options['relationshipValues']) && is_array($pa_options['relationshipValues']) ? $pa_options['relationshipValues'] : array();
    $o_doc = str_get_dom($ps_template);
    // parse template
    $ps_template = str_replace("<~root~>", "", str_replace("</~root~>", "", $o_doc->html()));
    // replace template with parsed version; this allows us to do text find/replace later
    // Parse units from template
    $o_units = $o_doc('unit');
    // only process non-nested <unit> tags
    $va_units = array();
    $vn_unit_id = 1;
    foreach ($o_units as $o_unit) {
        if (!$o_unit) {
            continue;
        }
        $vs_html = str_replace("<~root~>", "", str_replace("</~root~>", "", $o_unit->html()));
        $vs_content = $o_unit->getInnerText();
        // is this nested in another unit? We skip these
        foreach ($va_units as $va_tmp) {
            if (strpos($va_tmp['directive'], $vs_html) !== false) {
                continue 2;
            }
        }
        $va_units[] = $va_unit = array('tag' => $vs_unit_tag = "[[#{$vn_unit_id}]]", 'directive' => $vs_html, 'content' => $vs_content, 'relativeTo' => (string) $o_unit->getAttribute("relativeto"), 'delimiter' => ($vs_d = (string) $o_unit->getAttribute("delimiter")) ? $vs_d : null, 'restrictToTypes' => (string) $o_unit->getAttribute("restricttotypes"), 'restrictToRelationshipTypes' => (string) $o_unit->getAttribute("restricttorelationshiptypes"), 'sort' => explode(";", $o_unit->getAttribute("sort")), 'sortDirection' => (string) $o_unit->getAttribute("sortDirection"), 'skipIfExpression' => (string) $o_unit->getAttribute("skipIfExpression"));
        $ps_template = str_ireplace($va_unit['directive'], $vs_unit_tag, $ps_template);
        $vn_unit_id++;
    }
    $o_doc = str_get_dom($ps_template);
    // parse template again with units replaced by unit tags in the format [[#X]]
    $ps_template = str_replace("<~root~>", "", str_replace("</~root~>", "", $o_doc->html()));
    // replace template with parsed version; this allows us to do text find/replace later
    $va_tags = array();
    if (preg_match_all(__CA_BUNDLE_DISPLAY_TEMPLATE_TAG_REGEX__, $ps_template, $va_matches)) {
        $va_tags = $va_matches[1];
    }
    $va_directive_tags = array();
    $va_directive_tag_vals = array();
    $qr_res = caMakeSearchResult($ps_tablename, $pa_row_ids);
    if (!$qr_res) {
        return '';
    }
    $va_proc_templates = array();
    $vn_i = 0;
    $o_ifs = $o_doc("if");
    // if
    $o_ifdefs = $o_doc("ifdef");
    // if defined
    $o_ifnotdefs = $o_doc("ifnotdef");
    // if not defined
    $o_mores = $o_doc("more");
    // more tags - content suppressed if there are no defined values following the tag pair
    $o_betweens = $o_doc("between");
    // between tags - content suppressed if there are not defined values on both sides of the tag pair
    $va_if = array();
    foreach ($o_ifs as $o_if) {
        if (!$o_if) {
            continue;
        }
        $vs_html = $o_if->html();
        $vs_content = $o_if->getInnerText();
        $va_if[] = array('directive' => $vs_html, 'content' => $vs_content, 'rule' => $vs_rule = (string) $o_if->getAttribute('rule'));
    }
    foreach ($o_ifdefs as $o_ifdef) {
        if (!$o_ifdef) {
            continue;
        }
        $vs_code = (string) $o_ifdef->getAttribute('code');
        $vs_code_proc = preg_replace("!%(.*)\$!", '', $vs_code);
        $va_directive_tags = array_merge($va_directive_tags, preg_split('![,\\|]{1}!', $vs_code_proc));
    }
    foreach ($o_ifnotdefs as $o_ifnotdef) {
        if (!$o_ifnotdef) {
            continue;
        }
        $vs_code = (string) $o_ifnotdef->getAttribute('code');
        $vs_code_proc = preg_replace("!%(.*)\$!", '', $vs_code);
        $va_directive_tags = array_merge($va_directive_tags, preg_split('![,\\|]{1}!', $vs_code_proc));
    }
    $va_mores = array();
    foreach ($o_mores as $o_more) {
        if (!$o_more) {
            continue;
        }
        $vs_html = str_replace("<~root~>", "", str_replace("</~root~>", "", $o_more->html()));
        $vs_content = $o_more->getInnerText();
        $va_mores[] = array('directive' => $vs_html, 'content' => $vs_content);
    }
    $va_betweens = array();
    foreach ($o_betweens as $o_between) {
        if (!$o_between) {
            continue;
        }
        $vs_html = str_replace("<~root~>", "", str_replace("</~root~>", "", $o_between->html()));
        $vs_content = $o_between->getInnerText();
        $va_betweens[] = array('directive' => $vs_html, 'content' => $vs_content);
    }
    $va_resolve_links_using_row_ids = array();
    $va_tag_val_list = $va_defined_tag_list = array();
    $va_expression_vars = array();
    /** @var $qr_res SearchResult */
    while ($qr_res->nextHit()) {
        $vs_pk_val = $qr_res->get($vs_pk, array('checkAccess' => $pa_check_access));
        if (is_array($pa_check_access) && sizeof($pa_check_access) && !in_array($qr_res->get("{$ps_tablename}.access"), $pa_check_access)) {
            continue;
        }
        $vs_template = $ps_template;
        // check if we skip this row because of skipIfExpression
        if (strlen($ps_skip_if_expression) > 0) {
            $va_expression_tags = caGetTemplateTags($ps_skip_if_expression);
            foreach ($va_expression_tags as $vs_expression_tag) {
                if (!isset($va_expression_vars[$vs_expression_tag])) {
                    $va_expression_vars[$vs_expression_tag] = $qr_res->get($vs_expression_tag, array('assumeDisplayField' => true, 'returnIdno' => true, 'delimiter' => ';'));
                }
            }
            if (ExpressionParser::evaluate($ps_skip_if_expression, $va_expression_vars)) {
                continue;
            }
        }
        // Grab values for codes used in ifdef and ifnotdef directives
        $va_directive_tag_vals = array();
        foreach ($va_directive_tags as $vs_directive_tag) {
            $va_directive_tag_vals[$vs_directive_tag] = $qr_res->get($vs_directive_tag, array('assumeDisplayField' => true, 'convertCodesToDisplayText' => true, 'dontUseElementTemplate' => true));
        }
        $o_parsed_template = str_get_dom($vs_template);
        while (sizeof($vo_templates = $o_parsed_template('ifcount:not(:has(ifdef,ifndef,ifcount)),ifdef:not(:has(ifdef,ifndef,ifcount)),ifndef:not(:has(ifdef,ifndef,ifcount))')) > 0) {
            foreach ($vo_templates as $vn_index => $vo_element) {
                $vs_code = $vo_element->code;
                switch ($vo_element->tag) {
                    case 'ifdef':
                        if (strpos($vs_code, "|") !== false) {
                            $vs_bool = 'OR';
                            $va_tag_list = explode("|", $vs_code);
                            $vb_output = false;
                        } else {
                            $vs_bool = 'AND';
                            $va_tag_list = explode(",", $vs_code);
                            $vb_output = true;
                        }
                        foreach ($va_tag_list as $vs_tag_to_test) {
                            $vs_tag_to_test = preg_replace("!%.*\$!", "", $vs_tag_to_test);
                            $vb_value_is_set = isset($va_directive_tag_vals[$vs_tag_to_test]) && strlen($va_directive_tag_vals[$vs_tag_to_test]) > 1;
                            switch ($vs_bool) {
                                case 'OR':
                                    if ($vb_value_is_set) {
                                        $vb_output = true;
                                        break 2;
                                    }
                                    // any must be defined; if any is defined output
                                    break;
                                case 'AND':
                                default:
                                    if (!$vb_value_is_set) {
                                        $vb_output = false;
                                        break 2;
                                    }
                                    // all must be defined; if any is not defined don't output
                                    break;
                            }
                        }
                        if ($vb_output) {
                            $vs_template = str_replace($vo_element->html(), $vo_element->getInnerText(), $vs_template);
                        } else {
                            $vs_template = str_replace($vo_element->html(), '', $vs_template);
                        }
                        break;
                    case 'ifndef':
                        if (strpos($vs_code, "|") !== false) {
                            $vs_bool = 'OR';
                            $va_tag_list = explode("|", $vs_code);
                            $vb_output = false;
                        } else {
                            $vs_bool = 'AND';
                            $va_tag_list = explode(",", $vs_code);
                            $vb_output = true;
                        }
                        $vb_output = true;
                        foreach ($va_tag_list as $vs_tag_to_test) {
                            $vb_value_is_set = (bool) (isset($va_directive_tag_vals[$vs_tag_to_test]) && strlen($va_directive_tag_vals[$vs_tag_to_test]) > 0);
                            switch ($vs_bool) {
                                case 'OR':
                                    if (!$vb_value_is_set) {
                                        $vb_output = true;
                                        break 2;
                                    }
                                    // any must be not defined; if anything is not set output
                                    break;
                                case 'AND':
                                default:
                                    if ($vb_value_is_set) {
                                        $vb_output = false;
                                        break 2;
                                    }
                                    // all must be not defined; if anything is set don't output
                                    break;
                            }
                        }
                        if ($vb_output) {
                            $vs_template = str_replace($vo_element->html(), $vo_element->getInnerText(), $vs_template);
                        } else {
                            $vs_template = str_replace($vo_element->html(), '', $vs_template);
                        }
                        break;
                    case 'ifcount':
                        if (is_array($va_if_codes = preg_split("![\\|,;]+!", $vs_code))) {
                            $vn_min = (int) $vo_element->min;
                            $vn_max = (int) $vo_element->max;
                            $va_restrict_to_types = preg_split("![,; ]+!", $vo_element->restrictToTypes);
                            $va_restrict_to_relationship_types = preg_split("![,; ]+!", $vo_element->restrictToRelationshipTypes);
                            $vn_count = 0;
                            foreach ($va_if_codes as $vs_if_code) {
                                if ($t_table = $o_dm->getInstanceByTableName($vs_if_code, true)) {
                                    $va_count_vals = $qr_res->get($vs_if_code . "." . $t_table->primaryKey(), array('restrictToTypes' => $va_restrict_to_types, 'restrictToRelationshipTypes' => $va_restrict_to_relationship_types, 'returnAsArray' => true, 'checkAccess' => $pa_check_access));
                                } else {
                                    $va_count_vals = $qr_res->get($vs_if_code, array('returnAsArray' => true, 'restrictToTypes' => $va_restrict_to_types, 'restrictToRelationshipTypes' => $va_restrict_to_relationship_types, 'checkAccess' => $pa_check_access));
                                }
                                if (is_array($va_count_vals)) {
                                    $va_bits = explode(".", $vs_if_code);
                                    $vs_fld = array_pop($va_bits);
                                    foreach ($va_count_vals as $vs_count_val) {
                                        if (is_array($vs_count_val)) {
                                            if (isset($vs_count_val[$vs_fld]) && !trim($vs_count_val[$vs_fld])) {
                                                continue;
                                            }
                                            $vb_is_set = false;
                                            foreach ($vs_count_val as $vs_f => $vs_v) {
                                                if (trim($vs_v)) {
                                                    $vb_is_set = true;
                                                    break;
                                                }
                                            }
                                            if (!$vb_is_set) {
                                                continue;
                                            }
                                        } else {
                                            if (!trim($vs_count_val)) {
                                                continue;
                                            }
                                        }
                                        $vn_count++;
                                    }
                                }
                            }
                            if ($vn_min <= $vn_count && ($vn_max >= $vn_count || !$vn_max)) {
                                $vs_template = str_replace($vo_element->html(), $vo_element->getInnerText(), $vs_template);
                            } else {
                                $vs_template = str_replace($vo_element->html(), '', $vs_template);
                            }
                        }
                        break;
                }
            }
            $o_parsed_template = str_get_dom($vs_template);
            // reparse
        }
        $va_proc_templates[$vn_i] = $vs_template;
        foreach ($va_units as $k => $va_unit) {
            if (!$va_unit['content']) {
                continue;
            }
            $va_relative_to_tmp = $va_unit['relativeTo'] ? explode(".", $va_unit['relativeTo']) : array($ps_tablename);
            if (!($t_rel_instance = $o_dm->getInstanceByTableName($va_relative_to_tmp[0], true))) {
                continue;
            }
            $vs_unit_delimiter = caGetOption('delimiter', $va_unit, $vs_delimiter);
            $vs_unit_skip_if_expression = caGetOption('skipIfExpression', $va_unit, false);
            // additional get options for pulling related records
            $va_get_options = array('returnAsArray' => true, 'checkAccess' => $pa_check_access);
            if ($va_unit['restrictToTypes'] && strlen($va_unit['restrictToTypes']) > 0) {
                $va_get_options['restrictToTypes'] = preg_split('![\\|,;]+!', $va_unit['restrictToTypes']);
            }
            if ($va_unit['restrictToRelationshipTypes'] && strlen($va_unit['restrictToRelationshipTypes']) > 0) {
                $va_get_options['restrictToRelationshipTypes'] = preg_split('![\\|,;]+!', $va_unit['restrictToRelationshipTypes']);
            }
            if ($va_unit['sort'] && is_array($va_unit['sort'])) {
                $va_get_options['sort'] = $va_unit['sort'];
                $va_get_options['sortDirection'] = $va_unit['sortDirection'];
            }
            if (sizeof($va_relative_to_tmp) == 1 && $va_relative_to_tmp[0] == $ps_tablename || sizeof($va_relative_to_tmp) >= 1 && $va_relative_to_tmp[0] == $ps_tablename && $va_relative_to_tmp[1] != 'related') {
                switch (strtolower($va_relative_to_tmp[1])) {
                    case 'hierarchy':
                        $va_relative_ids = $qr_res->get($t_rel_instance->tableName() . ".hierarchy." . $t_rel_instance->primaryKey(), $va_get_options);
                        $va_relative_ids = array_values($va_relative_ids);
                        break;
                    case 'parent':
                        $va_relative_ids = $qr_res->get($t_rel_instance->tableName() . ".parent." . $t_rel_instance->primaryKey(), $va_get_options);
                        $va_relative_ids = array_values($va_relative_ids);
                        break;
                    case 'children':
                        $va_relative_ids = $qr_res->get($t_rel_instance->tableName() . ".children." . $t_rel_instance->primaryKey(), $va_get_options);
                        $va_relative_ids = array_values($va_relative_ids);
                        break;
                    default:
                        $va_relative_ids = array($vs_pk_val);
                        break;
                }
                // process template for all records selected by unit tag
                $va_tmpl_val = caProcessTemplateForIDs($va_unit['content'], $va_relative_to_tmp[0], $va_relative_ids, array_merge($pa_options, array('sort' => $va_get_options['sort'], 'sortDirection' => $va_get_options['sortDirection'], 'returnAsArray' => true, 'delimiter' => $vs_unit_delimiter, 'resolveLinksUsing' => null, 'skipIfExpression' => $vs_unit_skip_if_expression)));
                $va_proc_templates[$vn_i] = str_ireplace($va_unit['tag'], join($vs_unit_delimiter, $va_tmpl_val), $va_proc_templates[$vn_i]);
            } else {
                switch (strtolower($va_relative_to_tmp[1])) {
                    case 'hierarchy':
                        $va_relative_ids = $qr_res->get($t_rel_instance->tableName() . ".hierarchy." . $t_rel_instance->primaryKey(), $va_get_options);
                        $va_relative_ids = array_values($va_relative_ids);
                        break;
                    case 'parent':
                        $va_relative_ids = $qr_res->get($t_rel_instance->tableName() . ".parent." . $t_rel_instance->primaryKey(), $va_get_options);
                        $va_relative_ids = array_values($va_relative_ids);
                        break;
                    case 'children':
                        $va_relative_ids = $qr_res->get($t_rel_instance->tableName() . ".children." . $t_rel_instance->primaryKey(), $va_get_options);
                        $va_relative_ids = array_values($va_relative_ids);
                        break;
                    case 'related':
                        $va_relative_ids = $qr_res->get($t_rel_instance->tableName() . ".related." . $t_rel_instance->primaryKey(), $va_get_options);
                        $va_relative_ids = array_values($va_relative_ids);
                        break;
                    default:
                        if (method_exists($t_instance, 'isSelfRelationship') && $t_instance->isSelfRelationship()) {
                            $va_relative_ids = array_values($t_instance->getRelatedIDsForSelfRelationship($va_primary_ids[$t_rel_instance->tableName()], array($vs_pk_val)));
                        } else {
                            $va_relative_ids = array_values($qr_res->get($t_rel_instance->tableName() . "." . $t_rel_instance->primaryKey(), $va_get_options));
                        }
                        break;
                }
                $vs_tmpl_val = caProcessTemplateForIDs($va_unit['content'], $va_relative_to_tmp[0], $va_relative_ids, array_merge($pa_options, array('sort' => $va_unit['sort'], 'sortDirection' => $va_unit['sortDirection'], 'delimiter' => $vs_unit_delimiter, 'resolveLinksUsing' => null, 'skipIfExpression' => $vs_unit_skip_if_expression)));
                $va_proc_templates[$vn_i] = str_ireplace($va_unit['tag'], $vs_tmpl_val, $va_proc_templates[$vn_i]);
            }
        }
        if (!strlen(trim($va_proc_templates[$vn_i]))) {
            $va_proc_templates[$vn_i] = null;
        }
        if (!sizeof($va_tags)) {
            $vn_i++;
            continue;
        }
        // if there are no tags in the template then we don't need to process further
        if ($ps_resolve_links_using !== $ps_tablename) {
            $va_resolve_links_using_row_ids += $qr_res->get("{$ps_resolve_links_using}.{$vs_resolve_links_using_pk}", array('returnAsArray' => true, 'checkAccess' => $pa_check_access));
            // we need to remove "primary_ids" from the list, since for self-relations these will be the side(s) of the relations we're viewing *from*
            if (is_array($va_primary_ids[$ps_resolve_links_using]) && sizeof($va_primary_ids[$ps_resolve_links_using])) {
                $va_resolve_links_using_row_ids = array_values(array_diff($va_resolve_links_using_row_ids, $va_primary_ids[$ps_resolve_links_using]));
            }
        }
        $va_tag_val_list[$vn_i] = array();
        $va_defined_tag_list[$vn_i] = array();
        $va_tag_opts = $va_tag_filters = array();
        foreach ($va_tags as $vs_tag) {
            $va_tmp = explode('.', $vs_tag);
            $vs_last_element = $va_tmp[sizeof($va_tmp) - 1];
            $va_tag_opt_tmp = explode("%", $vs_last_element);
            if (sizeof($va_tag_opt_tmp) > 1) {
                $vs_tag_bit = array_shift($va_tag_opt_tmp);
                // get rid of getspec
                foreach ($va_tag_opt_tmp as $vs_tag_opt_raw) {
                    if (preg_match("!^\\[([^\\]]+)\\]\$!", $vs_tag_opt_raw, $va_matches)) {
                        if (sizeof($va_filter = explode("=", $va_matches[1])) == 2) {
                            $va_tag_filters[$va_filter[0]] = $va_filter[1];
                        }
                        continue;
                    }
                    $va_tag_tmp = explode("=", $vs_tag_opt_raw);
                    $va_tag_tmp[0] = trim($va_tag_tmp[0]);
                    $va_tag_tmp[1] = trim($va_tag_tmp[1]);
                    if (in_array($va_tag_tmp[0], array('delimiter', 'hierarchicalDelimiter'))) {
                        $va_tag_tmp[1] = str_replace("_", " ", $va_tag_tmp[1]);
                    }
                    if (sizeof($va_tag_line_tmp = explode("|", $va_tag_tmp[1])) > 1) {
                        $va_tag_opts[trim($va_tag_tmp[0])] = $va_tag_line_tmp;
                    } else {
                        $va_tag_opts[trim($va_tag_tmp[0])] = $va_tag_tmp[1];
                    }
                }
                $va_tmp[sizeof($va_tmp) - 1] = $vs_tag_bit;
                // remove option from tag-part array
                $vs_tag_proc = join(".", $va_tmp);
                $va_proc_templates[$vn_i] = str_replace($vs_tag, $vs_tag_proc, $va_proc_templates[$vn_i]);
                $vs_tag = $vs_tag_proc;
            }
            switch ($vs_tag) {
                case 'DATE':
                    $vs_format = urldecode(caGetOption('format', $va_tag_opts, 'd M Y'));
                    $va_proc_templates[$vn_i] = str_replace("^{$vs_tag}", date($vs_format), $va_proc_templates[$vn_i]);
                    continue 2;
                    break;
            }
            $pa_options = array_merge($pa_options, $va_tag_opts);
            // Default label tag to hierarchies
            if (isset($pa_options['showHierarchicalLabels']) && $pa_options['showHierarchicalLabels'] && $vs_tag == 'label') {
                unset($va_related_values[$vs_pk_val][$vs_tag]);
                unset($va_relationship_values[$vs_pk_val][$vs_tag]);
                $va_tmp = array($ps_tablename, 'hierarchy', 'preferred_labels');
            }
            if (!isset($va_relationship_values[$vs_pk_val])) {
                $va_relationship_values[$vs_pk_val] = array(0 => null);
            }
            foreach ($va_relationship_values[$vs_pk_val] as $vn_relation_id => $va_relationship_value_array) {
                $vb_is_related = false;
                $va_val = null;
                if (isset($va_relationship_value_array[$vs_tag]) && !(isset($pa_options['showHierarchicalLabels']) && $pa_options['showHierarchicalLabels'] && $vs_tag == 'label')) {
                    $va_val = array($vs_val = $va_relationship_value_array[$vs_tag]);
                } elseif (isset($va_relationship_value_array[$vs_tag])) {
                    $va_val = array($vs_val = $va_relationship_value_array[$vs_tag]);
                } else {
                    if (isset($va_related_values[$vs_pk_val][$vs_tag])) {
                        $va_val = array($vs_val = $va_related_values[$vs_pk_val][$vs_tag]);
                    } else {
                        //
                        // see if this is a reference to a related table
                        //
                        if (in_array($vs_tag, array("relationship_typename", "relationship_type_id", "relationship_typecode", "relationship_type_code"))) {
                            $vb_is_related = true;
                            switch ($vs_tag) {
                                case 'relationship_typename':
                                    $vs_spec = 'preferred_labels.' . (caGetOption('orientation', $pa_options, 'LTOR') == 'LTOR' ? 'typename' : 'typename_reverse');
                                    break;
                                case 'relationship_type_id':
                                    $vs_spec = 'type_id';
                                    break;
                                case 'relationship_typecode':
                                case 'relationship_type_code':
                                default:
                                    $vs_spec = 'type_code';
                                    break;
                            }
                            $vs_rel = $qr_res->get("ca_relationship_types.{$vs_spec}", array_merge($pa_options, $va_tag_opts, array('returnAsArray' => false)));
                            $va_val = array($vs_rel);
                        } elseif ($ps_tablename != $va_tmp[0] && ($t_tmp = $o_dm->getInstanceByTableName($va_tmp[0], true))) {
                            // if the part of the tag before a "." (or the tag itself if there are no periods) is a related table then try to fetch it as related to the current record
                            if (isset($pa_options['placeholderPrefix']) && $pa_options['placeholderPrefix'] && $va_tmp[0] != $pa_options['placeholderPrefix'] && sizeof($va_tmp) == 1) {
                                $vs_get_spec = array_shift($va_tmp) . "." . $pa_options['placeholderPrefix'];
                                if (sizeof($va_tmp) > 0) {
                                    $vs_get_spec .= "." . join(".", $va_tmp);
                                }
                            } else {
                                $vs_get_spec = $vs_tag;
                            }
                            $va_spec_bits = explode(".", $vs_get_spec);
                            if (sizeof($va_spec_bits) == 1 && $o_dm->getTableNum($va_spec_bits[0])) {
                                $vs_get_spec .= ".preferred_labels";
                            }
                            $va_additional_options = array('returnAsArray' => true, 'checkAccess' => $pa_check_access);
                            $vs_hierarchy_name = null;
                            $vb_is_hierarchy = false;
                            if (in_array($va_spec_bits[1], array('hierarchy', '_hierarchyName'))) {
                                $t_rel = $o_dm->getInstanceByTableName($va_spec_bits[0], true);
                                switch ($t_rel->getProperty('HIERARCHY_TYPE')) {
                                    case __CA_HIER_TYPE_SIMPLE_MONO__:
                                        $va_additional_options['removeFirstItems'] = 1;
                                        break;
                                    case __CA_HIER_TYPE_MULTI_MONO__:
                                        $vs_hierarchy_name = $t_rel->getHierarchyName($qr_res->get($t_rel->tableName() . "." . $t_rel->primaryKey(), array('checkAccess' => $pa_check_access)));
                                        $va_additional_options['removeFirstItems'] = 1;
                                        break;
                                }
                            }
                            if ($va_spec_bits[1] != '_hierarchyName') {
                                $va_val = $qr_res->get($vs_get_spec, array_merge($pa_options, $va_additional_options, array('returnWithStructure' => true, 'returnBlankValues' => true, 'returnAllLocales' => true, 'useLocaleCodes' => false, 'filters' => $va_tag_filters, 'primaryIDs' => $va_primary_ids)));
                            } else {
                                $va_val = array();
                            }
                            if (is_array($va_primary_ids) && isset($va_primary_ids[$va_spec_bits[0]]) && is_array($va_primary_ids[$va_spec_bits[0]])) {
                                foreach ($va_primary_ids[$va_spec_bits[0]] as $vn_primary_id) {
                                    unset($va_val[$vn_primary_id]);
                                }
                            }
                            if ($va_spec_bits[1] !== 'hierarchy') {
                                $va_val = caExtractValuesByUserLocale($va_val);
                                $va_val_tmp = array();
                                foreach ($va_val as $vn_d => $va_vals) {
                                    if (is_array($va_vals)) {
                                        $va_val_tmp = array_merge($va_val_tmp, array_values($va_vals));
                                    } else {
                                        $va_val_tmp[] = $va_vals;
                                    }
                                }
                                $va_val = $va_val_tmp;
                            }
                            $va_val_proc = array();
                            switch ($va_spec_bits[1]) {
                                case '_hierarchyName':
                                    if ($vs_hierarchy_name) {
                                        $va_val_proc[] = $vs_hierarchy_name;
                                    }
                                    break;
                                case 'hierarchy':
                                    if (is_array($va_val) && sizeof($va_val) > 0) {
                                        $va_hier_list = array();
                                        if ($vs_hierarchy_name) {
                                            array_unshift($va_hier_list, $vs_hierarchy_name);
                                        }
                                        $vs_name = end($va_spec_bits);
                                        foreach ($va_val as $va_hier) {
                                            $va_hier = caExtractValuesByUserLocale($va_hier);
                                            foreach ($va_hier as $va_hier_item) {
                                                foreach ($va_hier_item as $va_hier_value) {
                                                    $va_hier_list[] = $va_hier_value[$vs_name] ? $va_hier_value[$vs_name] : array_shift($va_hier_value);
                                                }
                                            }
                                        }
                                        $va_val_proc[] = join(caGetOption("delimiter", $va_tag_opts, $vs_delimiter), $va_hier_list);
                                    }
                                    break;
                                case 'parent':
                                    if (is_array($va_val)) {
                                        foreach ($va_val as $vm_label) {
                                            if (is_array($vm_label)) {
                                                $t_rel = $o_dm->getInstanceByTableName($va_spec_bits[0], true);
                                                if (!$t_rel || !method_exists($t_rel, "getLabelDisplayField")) {
                                                    $va_val_proc[] = join("; ", $vm_label);
                                                } else {
                                                    $va_val_proc[] = $vm_label[$t_rel->getLabelDisplayField()];
                                                }
                                            } else {
                                                $va_val_proc[] = $vm_label;
                                            }
                                        }
                                    }
                                    break;
                                default:
                                    $vs_terminal = end($va_spec_bits);
                                    foreach ($va_val as $va_val_container) {
                                        if (!is_array($va_val_container)) {
                                            if ($va_val_container) {
                                                $va_val_proc[] = $va_val_container;
                                            }
                                            continue;
                                        }
                                        // Add display field to *_labels terminals
                                        if (in_array($vs_terminal, array('preferred_labels', 'nonpreferred_labels')) && !$va_val_container[$vs_terminal]) {
                                            $t_rel = $o_dm->getInstanceByTableName($va_spec_bits[0], true);
                                            $vs_terminal = $t_rel->getLabelDisplayField();
                                        }
                                        $va_val_proc[] = $va_val_container[$vs_terminal];
                                    }
                                    break;
                            }
                            $va_val = $va_val_proc;
                            $vb_is_related = true;
                        } else {
                            //
                            // Handle non-related gets
                            //
                            // Default specifiers that end with a modifier to preferred labels
                            if (sizeof($va_tmp) == 2 && in_array($va_tmp[1], array('hierarchy', 'children', 'parent', 'related'))) {
                                array_push($va_tmp, 'preferred_labels');
                            }
                            $vs_hierarchy_name = null;
                            if (in_array($va_tmp[1], array('hierarchy', '_hierarchyName'))) {
                                switch ($t_instance->getProperty('HIERARCHY_TYPE')) {
                                    case __CA_HIER_TYPE_SIMPLE_MONO__:
                                        $va_additional_options['removeFirstItems'] = 1;
                                        break;
                                    case __CA_HIER_TYPE_MULTI_MONO__:
                                        $vs_hierarchy_name = $t_instance->getHierarchyName($qr_res->get($t_instance->tableName() . "." . $t_instance->primaryKey(), array('checkAccess' => $pa_check_access)));
                                        $va_additional_options['removeFirstItems'] = 1;
                                        break;
                                }
                            }
                            if ($va_tmp[0] == $ps_tablename) {
                                array_shift($va_tmp);
                            }
                            // get rid of primary table if it's in the field spec
                            if (!sizeof($va_tmp) && $t_instance->getProperty('LABEL_TABLE_NAME')) {
                                $va_tmp[] = "preferred_labels";
                            }
                            if (isset($pa_options['showHierarchicalLabels']) && $pa_options['showHierarchicalLabels']) {
                                if (!in_array($va_tmp[0], array('hierarchy', 'children', 'parent', 'related')) && $va_tmp[1] == 'preferred_labels') {
                                    array_unshift($va_tmp, 'hierarchy');
                                }
                            }
                            if (isset($pa_options['placeholderPrefix']) && $pa_options['placeholderPrefix'] && $va_tmp[0] != $pa_options['placeholderPrefix']) {
                                array_splice($va_tmp, -1, 0, $pa_options['placeholderPrefix']);
                            }
                            $vs_get_spec = "{$ps_tablename}." . join(".", $va_tmp);
                            if (in_array($va_tmp[0], array('parent'))) {
                                $va_val[] = $qr_res->get($vs_get_spec, array_merge($pa_options, $va_tag_opts, array('returnAsArray' => false)));
                            } elseif ($va_tmp[0] == '_hierarchyName') {
                                $va_val[] = $vs_hierarchy_name;
                            } else {
                                $va_val_tmp = $qr_res->get($vs_get_spec, array_merge($pa_options, $va_tag_opts, array('returnAsArray' => true, 'returnBlankValues' => true, 'assumeDisplayField' => true, 'filters' => $va_tag_filters, 'checkAccess' => $pa_check_access)));
                                $va_val = array();
                                if (is_array($va_val_tmp)) {
                                    //$va_val_tmp = array_reverse($va_val_tmp);
                                    if ($va_tmp[0] == 'hierarchy') {
                                        if ($vs_hierarchy_name) {
                                            array_shift($va_val_tmp);
                                            // remove root
                                            array_unshift($va_val_tmp, $vs_hierarchy_name);
                                            // replace with hierarchy name
                                        }
                                        if ($vs_delimiter_tmp = caGetOption('hierarchicalDelimiter', $va_tag_opts)) {
                                            $vs_tag_val_delimiter = $vs_delimiter_tmp;
                                        } elseif ($vs_delimiter_tmp = caGetOption('hierarchicalDelimiter', $pa_options)) {
                                            $vs_tag_val_delimiter = $vs_delimiter_tmp;
                                        } elseif ($vs_delimiter_tmp = caGetOption('delimiter', $va_tag_opts, $vs_delimiter)) {
                                            $vs_tag_val_delimiter = $vs_delimiter_tmp;
                                        } else {
                                            $vs_tag_val_delimiter = $vs_delimiter;
                                        }
                                    } else {
                                        $vs_tag_val_delimiter = caGetOption('delimiter', $va_tag_opts, $vs_delimiter);
                                    }
                                    foreach ($va_val_tmp as $vn_attr_id => $vm_attr_val) {
                                        if (is_array($vm_attr_val)) {
                                            $va_val[] = join($vs_tag_val_delimiter, $vm_attr_val);
                                        } else {
                                            $va_val[] = $vm_attr_val;
                                        }
                                    }
                                }
                                if (sizeof($va_val) > 1 && $va_tmp[0] == 'hierarchy') {
                                    $vs_tag_val_delimiter = caGetOption('delimiter', $va_tag_opts, $vs_delimiter);
                                    $va_val = array(join($vs_tag_val_delimiter, $va_val));
                                }
                            }
                        }
                    }
                }
                if (is_array($va_val)) {
                    if (sizeof($va_val) > 0) {
                        foreach ($va_val as $vn_j => $vs_val) {
                            if (!is_array($va_tag_val_list[$vn_i][$vn_j][$vs_tag]) || !in_array($vs_val, $va_tag_val_list[$vn_i][$vn_j][$vs_tag])) {
                                $va_tag_val_list[$vn_i][$vn_j][$vs_tag][] = $vs_val;
                                if (is_array($vs_val) && sizeof($vs_val) || strlen($vs_val) > 0) {
                                    $va_defined_tag_list[$vn_i][$vn_j][$vs_tag] = true;
                                }
                            }
                        }
                    } else {
                        $va_tag_val_list[$vn_i][0][$vs_tag] = null;
                        $va_defined_tag_list[$vn_i][0][$vs_tag] = false;
                    }
                }
            }
        }
        $vn_i++;
    }
    foreach ($va_tag_val_list as $vn_i => $va_tags_list) {
        // do sorting?
        if (is_array($pa_sort)) {
            $va_sorted_values = $va_sorted_values_tmp = array();
            foreach ($va_tags_list as $vn_j => $va_values_by_field) {
                $vs_key = '';
                foreach ($pa_sort as $vn_k => $vs_sort) {
                    if (!isset($va_values_by_field[$vs_sort])) {
                        continue;
                    }
                    $vs_subkey = null;
                    foreach ($va_values_by_field[$vs_sort] as $vn_x => $vs_sort_subval) {
                        if ($va_date = caDateToHistoricTimestamps($vs_sort_subval)) {
                            // try to treat it as a date
                            if ($ps_sort_direction == 'DESC' && ($va_date[0] < $vs_subkey || is_null($vs_subkey))) {
                                $vs_subkey = $va_date[0];
                            } elseif ($va_date[0] > $vs_subkey || is_null($vs_subkey)) {
                                $vs_subkey = $va_date[0];
                            }
                        } else {
                            $vs_sort_subval = str_pad($vs_sort_subval, 20, ' ', STR_PAD_LEFT);
                            if ($ps_sort_direction == 'DESC' && ($vs_sort_subval < $vs_subkey || is_null($vs_subkey))) {
                                $vs_subkey = $vs_sort_subval;
                            } elseif ($vs_sort_subval > $vs_subkey || is_null($vs_subkey)) {
                                $vs_subkey = $vs_sort_subval;
                            }
                        }
                    }
                    $vs_key .= $vs_subkey;
                    $va_sorted_values_tmp[$vs_key][] = $va_values_by_field;
                }
            }
            ksort($va_sorted_values_tmp);
            foreach ($va_sorted_values_tmp as $vs_key => $va_value_list) {
                foreach ($va_value_list as $vn_x => $va_val) {
                    $va_sorted_values[$vs_key . $vn_x] = $va_val;
                }
            }
            if ($ps_sort_direction == 'DESC') {
                $va_sorted_values = array_reverse($va_sorted_values);
            }
            if (sizeof($va_sorted_values) > 0) {
                $va_tag_val_list[$vn_i] = $va_tags_list = $va_sorted_values;
            }
        }
        $va_acc = array();
        foreach ($va_tags_list as $vn_j => $va_tags) {
            $va_tag_list = array();
            $va_pt_vals = array();
            $vs_template = $va_proc_templates[$vn_i];
            // Process <if>
            foreach ($va_if as $va_def_con) {
                if (ExpressionParser::evaluate($va_def_con['rule'], $va_tags)) {
                    $vs_template = str_replace($va_def_con['directive'], $va_def_con['content'], $vs_template);
                } else {
                    $vs_template = str_replace($va_def_con['directive'], '', $vs_template);
                }
            }
            // Process <more> tags
            foreach ($va_mores as $vn_more_index => $va_more) {
                if (($vn_pos = strpos($vs_template, $va_more['directive'])) !== false) {
                    if (isset($va_mores[$vn_more_index + 1]) && ($vn_next_more_pos = strpos(substr($vs_template, $vn_pos + strlen($va_more['directive'])), $va_mores[$vn_more_index + 1]['directive'])) !== false) {
                        $vn_next_more_pos += $vn_pos;
                        $vs_partial_template = substr($vs_template, $vn_pos + strlen($va_more['directive']), $vn_next_more_pos - $vn_pos);
                    } else {
                        $vs_partial_template = substr($vs_template, $vn_pos + strlen($va_more['directive']));
                    }
                    $vb_output = false;
                    foreach (array_keys($va_defined_tag_list[$vn_i][$vn_j]) as $vs_defined_tag) {
                        if (strpos($vs_partial_template, $vs_defined_tag) !== false) {
                            // content is defined
                            $vb_output = true;
                            break;
                        }
                    }
                    if ($vb_output) {
                        $vs_template = preg_replace('!' . $va_more['directive'] . '!', $va_more['content'], $vs_template, 1);
                    } else {
                        $vs_template = preg_replace('!' . $va_more['directive'] . '!', '', $vs_template, 1);
                    }
                }
            }
            // Process <between> tags - text to be output if it is between two defined values
            $va_between_positions = array();
            foreach ($va_betweens as $vn_between_index => $va_between) {
                $vb_output_before = $vb_output_after = false;
                if (($vn_cur_pos = strpos($vs_template, $va_between['directive'])) !== false) {
                    $va_between_positions[$vn_between_index] = $vn_cur_pos;
                    // Get parts of template before tag and after tag
                    $vs_partial_template_before = substr($vs_template, 0, $vn_cur_pos);
                    $vs_partial_template_after = substr($vs_template, $vn_cur_pos + strlen($va_between['directive']));
                    // Only get the template between our current position and the next <between> tag
                    if (isset($va_betweens[$vn_between_index + 1]) && ($vn_after_pos_relative = strpos($vs_partial_template_after, $va_betweens[$vn_between_index + 1]['directive'])) !== false) {
                        $vs_partial_template_after = substr($vs_partial_template_after, 0, $vn_after_pos_relative);
                    }
                    // Check for defined value before and after tag
                    foreach (array_keys($va_defined_tag_list[$vn_i][$vn_j]) as $vs_defined_tag) {
                        if (strpos($vs_partial_template_before, $vs_defined_tag) !== false) {
                            // content is defined
                            $vb_output_after = true;
                        }
                        if (strpos($vs_partial_template_after, $vs_defined_tag) !== false) {
                            // content is defined
                            $vb_output_before = true;
                            break;
                        }
                        if ($vb_output_before && $vb_output_after) {
                            break;
                        }
                    }
                }
                if ($vb_output_before && $vb_output_after) {
                    $vs_template = preg_replace('!' . $va_between['directive'] . '!', $va_between['content'], $vs_template, 1);
                } else {
                    $vs_template = preg_replace('!' . $va_between['directive'] . '!', '', $vs_template, 1);
                }
            }
            //
            // Need to sort tags by length descending (longest first)
            // so that when we go to substitute and you have a tag followed by itself with a suffix
            // (ex. ^measurements and ^measurements2) we don't substitute both for the stub (ex. ^measurements)
            //
            $va_tags_tmp = array_keys($va_tags);
            usort($va_tags_tmp, function ($a, $b) {
                return strlen($b) - strlen($a);
            });
            $vs_pt = $vs_template;
            foreach ($va_tags_tmp as $vs_tag) {
                $vs_pt = str_replace('^' . $vs_tag, is_array($va_tags[$vs_tag]) ? join(" | ", $va_tags[$vs_tag]) : $va_tags[$vs_tag], $vs_pt);
            }
            if ($vs_pt) {
                $va_pt_vals[] = $vs_pt;
            }
            if ($vs_acc_val = join(isset($pa_options['delimiter']) ? $pa_options['delimiter'] : $vs_delimiter, $va_pt_vals)) {
                $va_acc[] = $vs_acc_val;
            }
        }
        $va_proc_templates[$vn_i] = join($vs_delimiter, $va_acc);
    }
    if ($pb_return_as_array && !caGetOption('includeBlankValuesInArray', $pa_options, false)) {
        foreach ($va_proc_templates as $vn_i => $vs_template) {
            if (!strlen(trim($vs_template))) {
                unset($va_proc_templates[$vn_i]);
            }
        }
    }
    // Transform links
    $va_proc_templates = caCreateLinksFromText($va_proc_templates, $ps_resolve_links_using, $ps_resolve_links_using != $ps_tablename ? $va_resolve_links_using_row_ids : $pa_row_ids, null, caGetOption('linkTarget', $pa_options, null), array_merge(array('addRelParameter' => true), $pa_options));
    // Kill any lingering tags (just in case)
    foreach ($va_proc_templates as $vn_i => $vs_proc_template) {
        $va_proc_templates[$vn_i] = preg_replace("!\\^([A-Za-z0-9_\\.]+[%]{1}[^ \\^\t\r\n\"\\'<>\\(\\)\\{\\}\\/\\[\\]]*|[A-Za-z0-9_\\.]+)!", "", $vs_proc_template);
        $va_proc_templates[$vn_i] = str_replace("<![CDATA[", "", $va_proc_templates[$vn_i]);
        $va_proc_templates[$vn_i] = str_replace("]]>", "", $va_proc_templates[$vn_i]);
    }
    if ($pb_return_as_array) {
        return $va_proc_templates;
    }
    return join($vs_delimiter, $va_proc_templates);
}
 /**
  * Processes single exporter item for a given record
  *
  * @param int $pn_item_id Primary of exporter item
  * @param int $pn_table_num Table num of item to export
  * @param int $pn_record_id Primary key value of item to export
  * @param array $pa_options
  *		ignoreContext = don't switch context even though context may be set for current item
  *		relationship_type_id, relationship_type_code, relationship_typename =
  *			if this export is a sub-export (context-switch), we have no way of knowing the relationship
  *			to the 'parent' element in the export, so there has to be a means to pass it down to make it accessible
  * 		attribute_id = signals that this is an export relative to a specific attribute instance
  * 			this triggers special behavior that allows getting container values in a kind of sub-export
  *			it's really only useful for Containers but in theory can be any attribute
  *		logger = KLogger instance to use for logging. This option is mandatory!
  * @return array Item info
  */
 public function processExporterItem($pn_item_id, $pn_table_num, $pn_record_id, $pa_options = array())
 {
     $o_log = caGetOption('logger', $pa_options);
     // always set by exportRecord()
     $vb_ignore_context = caGetOption('ignoreContext', $pa_options);
     $vn_attribute_id = caGetOption('attribute_id', $pa_options);
     $o_log->logInfo(_t("Export mapping processor called with parameters [exporter_item_id:%1 table_num:%2 record_id:%3]", $pn_item_id, $pn_table_num, $pn_record_id));
     $t_exporter_item = ca_data_exporters::loadExporterItemByID($pn_item_id);
     $t_instance = ca_data_exporters::loadInstanceByID($pn_record_id, $pn_table_num);
     // switch context to a different set of records if necessary and repeat current exporter item for all those selected records
     // (e.g. hierarchy children or related items in another table, restricted by types or relationship types)
     if (!$vb_ignore_context && ($vs_context = $t_exporter_item->getSetting('context'))) {
         $va_restrict_to_types = $t_exporter_item->getSetting('restrictToTypes');
         $va_restrict_to_rel_types = $t_exporter_item->getSetting('restrictToRelationshipTypes');
         $va_restrict_to_bundle_vals = $t_exporter_item->getSetting('restrictToBundleValues');
         $va_check_access = $t_exporter_item->getSetting('checkAccess');
         $va_sort = $t_exporter_item->getSetting('sort');
         $vn_new_table_num = $this->getAppDatamodel()->getTableNum($vs_context);
         $vb_context_is_related_table = false;
         $va_related = null;
         if ($vn_new_table_num) {
             // switch to new table
             $vs_key = $this->getAppDatamodel()->getTablePrimaryKeyName($vs_context);
         } else {
             // this table, i.e. hierarchy context switch
             $vs_key = $t_instance->primaryKey();
         }
         $o_log->logInfo(_t("Initiating context switch to '%1' for mapping ID %2 and record ID %3. The processor now tries to find matching records for the switch and calls itself for each of those items.", $vs_context, $pn_item_id, $pn_record_id));
         switch ($vs_context) {
             case 'children':
                 $va_related = $t_instance->getHierarchyChildren();
                 break;
             case 'parent':
                 $va_related = array();
                 if ($vs_parent_id_fld = $t_instance->getProperty("HIERARCHY_PARENT_ID_FLD")) {
                     $va_related[] = array($vs_key => $t_instance->get($vs_parent_id_fld));
                 }
                 break;
             case 'ancestors':
                 $va_parents = $t_instance->getHierarchyAncestors(null, array('idsOnly' => true));
                 $va_related = array();
                 foreach (array_unique($va_parents) as $vn_pk) {
                     $va_related[] = array($vs_key => intval($vn_pk));
                 }
                 break;
             case 'ca_sets':
                 $t_set = new ca_sets();
                 $va_set_options = array();
                 if (isset($va_restrict_to_types[0])) {
                     // the utility used below doesn't support passing multiple types so we just pass the first.
                     // this should be enough for 99.99% of the actual use cases anyway
                     $va_set_options['setType'] = $va_restrict_to_types[0];
                 }
                 $va_set_options['checkAccess'] = $va_check_access;
                 $va_set_options['setIDsOnly'] = true;
                 $va_set_ids = $t_set->getSetsForItem($pn_table_num, $t_instance->getPrimaryKey(), $va_set_options);
                 $va_related = array();
                 foreach (array_unique($va_set_ids) as $vn_pk) {
                     $va_related[] = array($vs_key => intval($vn_pk));
                 }
                 break;
             case 'ca_list_items.firstLevel':
                 if ($t_instance->tableName() == 'ca_lists') {
                     $o_dm = Datamodel::load();
                     $va_related = array();
                     $va_items_legacy_format = $t_instance->getListItemsAsHierarchy(null, array('maxLevels' => 1, 'dontIncludeRoot' => true));
                     $vn_new_table_num = $o_dm->getTableNum('ca_list_items');
                     $vs_key = 'item_id';
                     foreach ($va_items_legacy_format as $va_item_legacy_format) {
                         $va_related[$va_item_legacy_format['NODE']['item_id']] = $va_item_legacy_format['NODE'];
                     }
                     break;
                 } else {
                     return array();
                 }
                 break;
             default:
                 if ($vn_new_table_num) {
                     $va_options = array('restrictToTypes' => $va_restrict_to_types, 'restrictToRelationshipTypes' => $va_restrict_to_rel_types, 'restrictToBundleValues' => $va_restrict_to_bundle_vals, 'checkAccess' => $va_check_access, 'sort' => $va_sort);
                     $o_log->logDebug(_t("Calling getRelatedItems with options: %1.", print_r($va_options, true)));
                     $va_related = $t_instance->getRelatedItems($vs_context, $va_options);
                     $vb_context_is_related_table = true;
                 } else {
                     // container or invalid context
                     $va_context_tmp = explode('.', $vs_context);
                     if (sizeof($va_context_tmp) != 2) {
                         $o_log->logError(_t("Invalid context %1. Ignoring this mapping.", $vs_context));
                         return array();
                     }
                     $va_attrs = $t_instance->getAttributesByElement($va_context_tmp[1]);
                     $va_info = array();
                     if (is_array($va_attrs) && sizeof($va_attrs) > 0) {
                         $o_log->logInfo(_t("Switching context for element code: %1.", $va_context_tmp[1]));
                         $o_log->logDebug(_t("Raw attribute value array is as follows. The mapping will now be repeated for each (outer) attribute. %1", print_r($va_attrs, true)));
                         foreach ($va_attrs as $vo_attr) {
                             $va_attribute_export = $this->processExporterItem($pn_item_id, $pn_table_num, $pn_record_id, array_merge(array('ignoreContext' => true, 'attribute_id' => $vo_attr->getAttributeID()), $pa_options));
                             $va_info = array_merge($va_info, $va_attribute_export);
                         }
                     } else {
                         $o_log->logInfo(_t("Switching context for element code %1 failed. Either there is no attribute with that code attached to the current row or the code is invalid. Mapping is ignored for current row.", $va_context_tmp[1]));
                     }
                     return $va_info;
                 }
                 break;
         }
         $va_info = array();
         if (is_array($va_related)) {
             $o_log->logDebug(_t("The current mapping will now be repreated for these items: %1", print_r($va_related, true)));
             if (!$vn_new_table_num) {
                 $vn_new_table_num = $pn_table_num;
             }
             foreach ($va_related as $va_rel) {
                 // if we're dealing with a related table, pass on some info the relationship type to the context-switched invocation of processExporterItem(),
                 // because we can't access that information from the related item simply because we don't exactly know where the call originated
                 if ($vb_context_is_related_table) {
                     $pa_options['relationship_typename'] = $va_rel['relationship_typename'];
                     $pa_options['relationship_type_code'] = $va_rel['relationship_type_code'];
                     $pa_options['relationship_type_id'] = $va_rel['relationship_type_id'];
                 }
                 $va_rel_export = $this->processExporterItem($pn_item_id, $vn_new_table_num, $va_rel[$vs_key], array_merge(array('ignoreContext' => true), $pa_options));
                 $va_info = array_merge($va_info, $va_rel_export);
             }
         } else {
             $o_log->logDebug(_t("No matching related items found for last context switch"));
         }
         return $va_info;
     }
     // end switch context
     // Don't prevent context switches for children of context-switched exporter items. This way you can
     // build cascades for jobs like exporting objects related to the creator of the record in question.
     unset($pa_options['ignoreContext']);
     $va_item_info = array();
     $vs_source = $t_exporter_item->get('source');
     $vs_element = $t_exporter_item->get('element');
     $vb_repeat = $t_exporter_item->getSetting('repeat_element_for_multiple_values');
     // if omitIfEmpty is set and get() returns nothing, we ignore this exporter item and all children
     if ($vs_omit_if_empty = $t_exporter_item->getSetting('omitIfEmpty')) {
         if (!(strlen($t_instance->get($vs_omit_if_empty)) > 0)) {
             return array();
         }
     }
     // if omitIfNotEmpty is set and get() returns a value, we ignore this exporter item and all children
     if ($vs_omit_if_not_empty = $t_exporter_item->getSetting('omitIfNotEmpty')) {
         if (strlen($t_instance->get($vs_omit_if_not_empty)) > 0) {
             return array();
         }
     }
     // always return URL for export, not an HTML tag
     $va_get_options = array('returnURL' => true);
     if ($vs_delimiter = $t_exporter_item->getSetting("delimiter")) {
         $va_get_options['delimiter'] = $vs_delimiter;
     }
     if ($vs_template = $t_exporter_item->getSetting('template')) {
         $va_get_options['template'] = $vs_template;
     }
     if ($vs_locale = $t_exporter_item->getSetting('locale')) {
         // the global UI locale for some reason has a higher priority
         // than the locale setting in BaseModelWithAttributes::get
         // which is why we unset it here and restore it later
         global $g_ui_locale;
         $vs_old_ui_locale = $g_ui_locale;
         $g_ui_locale = null;
         $va_get_options['locale'] = $vs_locale;
     }
     // AttributeValue settings that are simply passed through by the exporter
     if ($t_exporter_item->getSetting('convertCodesToDisplayText')) {
         $va_get_options['convertCodesToDisplayText'] = true;
         // try to return text suitable for display for system lists stored in intrinsics (ex. ca_objects.access, ca_objects.status, ca_objects.source_id)
         // this does not affect list attributes
     } else {
         $va_get_options['convertCodesToIdno'] = true;
         // if display text is not requested try to return list item idno's... since underlying integer ca_list_items.item_id values are unlikely to be useful in an export context
     }
     if ($t_exporter_item->getSetting('returnIdno')) {
         $va_get_options['returnIdno'] = true;
     }
     if ($t_exporter_item->getSetting('start_as_iso8601')) {
         $va_get_options['start_as_iso8601'] = true;
     }
     if ($t_exporter_item->getSetting('end_as_iso8601')) {
         $va_get_options['end_as_iso8601'] = true;
     }
     if ($t_exporter_item->getSetting('dontReturnValueIfOnSameDayAsStart')) {
         $va_get_options['dontReturnValueIfOnSameDayAsStart'] = true;
     }
     if ($vs_date_format = $t_exporter_item->getSetting('dateFormat')) {
         $va_get_options['dateFormat'] = $vs_date_format;
     }
     // context was switched to attribute
     if ($vn_attribute_id) {
         $o_log->logInfo(_t("Processing mapping in attribute mode for attribute_id = %1.", $vn_attribute_id));
         if ($vs_source) {
             // trying to find the source only makes sense if the source is set
             $t_attr = new ca_attributes($vn_attribute_id);
             $va_values = $t_attr->getAttributeValues();
             $va_src_tmp = explode('.', $vs_source);
             if (sizeof($va_src_tmp) == 2) {
                 $o_dm = Datamodel::load();
                 if ($t_attr->get('table_num') == $o_dm->getTableNum($va_src_tmp[0])) {
                     $vs_source = $va_src_tmp[1];
                 }
             }
             $o_log->logDebug(_t("Trying to find code %1 in value array for the current attribute.", $vs_source));
             $o_log->logDebug(_t("Value array is %1.", print_r($va_values, true)));
             foreach ($va_values as $vo_val) {
                 $va_display_val_options = array();
                 if ($vo_val instanceof ListAttributeValue) {
                     // figure out list_id -- without it we can't pull display values
                     $t_element = new ca_metadata_elements($vo_val->getElementID());
                     $va_display_val_options = array('list_id' => $t_element->get('list_id'));
                     if ($t_exporter_item->getSetting('returnIdno') || $t_exporter_item->getSetting('convertCodesToIdno')) {
                         $va_display_val_options['output'] = 'idno';
                     } elseif ($t_exporter_item->getSetting('convertCodesToDisplayText')) {
                         $va_display_val_options['output'] = 'text';
                     }
                 }
                 $o_log->logDebug(_t("Trying to match code from array %1 and the code we're looking for %2.", $vo_val->getElementCode(), $vs_source));
                 if ($vo_val->getElementCode() == $vs_source) {
                     $vs_display_value = $vo_val->getDisplayValue($va_display_val_options);
                     $o_log->logDebug(_t("Found value %1.", $vs_display_value));
                     $va_item_info[] = array('text' => $vs_display_value, 'element' => $vs_element);
                 }
             }
         } else {
             // no source in attribute context probably means this is some form of wrapper, e.g. a MARC field
             $va_item_info[] = array('element' => $vs_element);
         }
     } else {
         if ($vs_source) {
             $o_log->logDebug(_t("Source for current mapping is %1", $vs_source));
             $va_matches = array();
             // CONSTANT value
             if (preg_match("/^_CONSTANT_:(.*)\$/", $vs_source, $va_matches)) {
                 $o_log->logDebug(_t("This is a constant. Value for this mapping is '%1'", trim($va_matches[1])));
                 $va_item_info[] = array('text' => trim($va_matches[1]), 'element' => $vs_element);
             } else {
                 if (in_array($vs_source, array("relationship_type_id", "relationship_type_code", "relationship_typename"))) {
                     if (isset($pa_options[$vs_source]) && strlen($pa_options[$vs_source]) > 0) {
                         $o_log->logDebug(_t("Source refers to releationship type information. Value for this mapping is '%1'", $pa_options[$vs_source]));
                         $va_item_info[] = array('text' => $pa_options[$vs_source], 'element' => $vs_element);
                     }
                 } else {
                     if (!$vb_repeat) {
                         $vs_get = $t_instance->get($vs_source, $va_get_options);
                         $o_log->logDebug(_t("Source is a simple get() for some bundle. Value for this mapping is '%1'", $vs_get));
                         $o_log->logDebug(_t("get() options are: %1", print_r($va_get_options, true)));
                         $va_item_info[] = array('text' => $vs_get, 'element' => $vs_element);
                     } else {
                         // user wants current element repeated in case of multiple returned values
                         $va_get_options['delimiter'] = ';#;';
                         $vs_values = $t_instance->get($vs_source, $va_get_options);
                         $o_log->logDebug(_t("Source is a get() that should be repeated for multiple values. Value for this mapping is '%1'. It includes the custom delimiter ';#;' that is later used to split the value into multiple values.", $vs_values));
                         $o_log->logDebug(_t("get() options are: %1", print_r($va_get_options, true)));
                         $va_tmp = explode(";#;", $vs_values);
                         foreach ($va_tmp as $vs_text) {
                             $va_item_info[] = array('element' => $vs_element, 'text' => $vs_text);
                         }
                     }
                 }
             }
         } else {
             if ($vs_template) {
                 // templates without source are probably just static text, but you never know
                 // -> run them through processor anyways
                 $vs_proc_template = caProcessTemplateForIDs($vs_template, $pn_table_num, array($pn_record_id), array());
                 $o_log->logDebug(_t("Current mapping has no source but a template '%1'. Value from extracted via template processor is '%2'", $vs_template, $vs_proc_template));
                 $va_item_info[] = array('element' => $vs_element, 'text' => $vs_proc_template);
             } else {
                 // no source, no template -> probably wrapper
                 $o_log->logDebug(_t("Current mapping has no source and no template and is probably an XML/MARC wrapper element"));
                 $va_item_info[] = array('element' => $vs_element);
             }
         }
     }
     // reset UI locale if we unset it
     if ($vs_locale) {
         $g_ui_locale = $vs_old_ui_locale;
     }
     $o_log->logDebug(_t("We're now processing other settings like default, prefix, suffix, skipIfExpression, filterByRegExp, maxLength, plugins and replacements for this mapping"));
     $o_log->logDebug(_t("Local data before processing is: %1", print_r($va_item_info, true)));
     // handle other settings and plugin hooks
     $vs_default = $t_exporter_item->getSetting('default');
     $vs_prefix = $t_exporter_item->getSetting('prefix');
     $vs_suffix = $t_exporter_item->getSetting('suffix');
     //$vs_regexp = $t_exporter_item->getSetting('filterByRegExp');		// Deprecated -- remove?
     $vn_max_length = $t_exporter_item->getSetting('maxLength');
     $vs_skip_if_expr = $t_exporter_item->getSetting('skipIfExpression');
     $vs_original_values = $t_exporter_item->getSetting('original_values');
     $vs_replacement_values = $t_exporter_item->getSetting('replacement_values');
     $va_replacements = ca_data_exporter_items::getReplacementArray($vs_original_values, $vs_replacement_values);
     foreach ($va_item_info as $vn_key => &$va_item) {
         $this->opo_app_plugin_manager->hookExportItemBeforeSettings(array('instance' => $t_instance, 'exporter_item_instance' => $t_exporter_item, 'export_item' => &$va_item));
         // handle dontReturnValueIfOnSameDayAsStart
         if (caGetOption('dontReturnValueIfOnSameDayAsStart', $va_get_options, false)) {
             if (strlen($va_item['text']) < 1) {
                 unset($va_item_info[$vn_key]);
             }
         }
         // handle skipIfExpression setting
         if ($vs_skip_if_expr) {
             // Add current value as variable "value", accessible in expressions as ^value
             $va_vars = array_merge(array('value' => $va_item['text']), ca_data_exporters::$s_variables);
             if (ExpressionParser::evaluate($vs_skip_if_expr, $va_vars)) {
                 unset($va_item_info[$vn_key]);
                 continue;
             }
         }
         // filter by regex (deprecated since you can do the same thing and more with skipIfExpression) -- remove?
         //if((strlen($va_item['text'])>0) && $vs_regexp) {
         //	if(!preg_match("!".$vs_regexp."!i", $va_item['text'])) {
         //		unset($va_item_info[$vn_key]);
         //		continue;
         //	}
         //}
         // do replacements
         $va_item['text'] = ca_data_exporter_items::replaceText($va_item['text'], $va_replacements);
         // if text is empty, fill in default
         // if text isn't empty, respect prefix and suffix
         if (strlen($va_item['text']) == 0) {
             if ($vs_default) {
                 $va_item['text'] = $vs_default;
             }
         } else {
             if (strlen($vs_prefix) > 0 || strlen($vs_suffix) > 0) {
                 $va_item['text'] = $vs_prefix . $va_item['text'] . $vs_suffix;
             }
         }
         if ($vn_max_length && strlen($va_item['text']) > $vn_max_length) {
             $va_item['text'] = substr($va_item['text'], 0, $vn_max_length) . " ...";
         }
         // if this is a variable, set the value and delete it from the export tree
         $va_matches = array();
         if (preg_match("/^_VARIABLE_:(.*)\$/", $va_item['element'], $va_matches)) {
             ca_data_exporters::$s_variables[$va_matches[1]] = $va_item['text'];
             unset($va_item_info[$vn_key]);
             continue;
         }
         // if returned value is null then we skip the item
         $this->opo_app_plugin_manager->hookExportItem(array('instance' => $t_instance, 'exporter_item_instance' => $t_exporter_item, 'export_item' => &$va_item));
     }
     $o_log->logInfo(_t("Extracted data for this mapping is as follows:"));
     foreach ($va_item_info as $va_tmp) {
         $o_log->logInfo(sprintf("    element:%-20s value: %-10s", $va_tmp['element'], $va_tmp['text']));
     }
     $va_children = $t_exporter_item->getHierarchyChildren();
     if (is_array($va_children) && sizeof($va_children) > 0) {
         $o_log->logInfo(_t("Now proceeding to process %1 direct children in the mapping hierarchy", sizeof($va_children)));
         foreach ($va_children as $va_child) {
             foreach ($va_item_info as &$va_info) {
                 $va_child_export = $this->processExporterItem($va_child['item_id'], $pn_table_num, $pn_record_id, $pa_options);
                 $va_info['children'] = array_merge((array) $va_info['children'], $va_child_export);
             }
         }
     }
     return $va_item_info;
 }