/** * */ 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; }
public function testAttributesWithHTML() { $vm_ret = DisplayTemplateParser::evaluate("<unit relativeTo='ca_entities' delimiter='<br/>'>^ca_entities.preferred_labels.displayname</unit>", "ca_objects", array($this->opn_object_id), array('returnAsArray' => true)); $this->assertInternalType('array', $vm_ret); $this->assertCount(1, $vm_ret); $this->assertEquals("Homer J. Simpson<br/>Bart Simpson", $vm_ret[0]); $vm_ret = DisplayTemplateParser::evaluate("<unit relativeTo='ca_entities' delimiter=' <br/> '>^ca_entities.preferred_labels.displayname</unit>", "ca_objects", array($this->opn_object_id), array('returnAsArray' => true)); $this->assertInternalType('array', $vm_ret); $this->assertCount(1, $vm_ret); $this->assertEquals("Homer J. Simpson <br/> Bart Simpson", $vm_ret[0]); }
public function testSiblingRestrictToTypesDisplayTemplates() { $vm_ret = DisplayTemplateParser::evaluate("<unit delimiter='; ' restrictToTypes='image' relativeTo='ca_objects.siblings'>^ca_objects.preferred_labels.name</unit>", "ca_objects", array($this->opt_another_child_object->getPrimaryKey()), array('returnAsArray' => true)); $this->assertInternalType('array', $vm_ret); $this->assertCount(1, $vm_ret); $this->assertEquals('My test still; Another test still', $vm_ret[0]); $vm_ret = DisplayTemplateParser::evaluate("<unit delimiter='; ' restrictToTypes='moving_image' relativeTo='ca_objects.siblings'>^ca_objects.preferred_labels.name</unit>", "ca_objects", array($this->opt_another_child_object->getPrimaryKey()), array('returnAsArray' => true)); $this->assertInternalType('array', $vm_ret); $this->assertCount(1, $vm_ret); $this->assertEquals('A child movie', $vm_ret[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) * 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. * 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 primary template and all <unit>s 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] * includeBlankValuesInTopLevelForPrefetch = include blank template values in *primary template* (not <unit>s) in returned array when returnAsArray is set. Used by template prefetcher to ensure returned values align with id indices. [Default is false] * * @return mixed Output of processed templates */ function caProcessTemplateForIDs($ps_template, $pm_tablename_or_num, $pa_row_ids, $pa_options = null) { return DisplayTemplateParser::evaluate($ps_template, $pm_tablename_or_num, $pa_row_ids, $pa_options); }