Skip to content

Commit

Permalink
Fix xpath-expressions (#39)
Browse files Browse the repository at this point in the history
* Fix xpath-expressions b2c4c7a

This change aims to implement support for ALL valid xpath-expressions
inside the „expression“-field.

See #35 for
details.

* Fix evaluation of root name as requested 17dd7a7

* Fix Root Node Name 8961139

See
#39 (review)
-125301233

* Fix Root Node Name a9971a1

* Modify xpath-expressions (#35) f499626

+ Better solution for modifying path-expressions (#35)
+ Fixing some indenting…

Fixes #35
  • Loading branch information
twiro authored and nitriques committed Jul 24, 2018
1 parent bc46e4c commit 3cbe0aa
Showing 1 changed file with 103 additions and 63 deletions.
166 changes: 103 additions & 63 deletions fields/field.reflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,18 @@ public function createTable()
$field_id = $this->get('id');

return Symphony::Database()->query("
CREATE TABLE IF NOT EXISTS `tbl_entries_data_{$field_id}` (
`id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`entry_id` INT(11) UNSIGNED NOT NULL,
`handle` VARCHAR(255) DEFAULT NULL,
`value` TEXT DEFAULT NULL,
`value_formatted` TEXT DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `entry_id` (`entry_id`),
FULLTEXT KEY `value` (`value`),
FULLTEXT KEY `value_formatted` (`value_formatted`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
");
CREATE TABLE IF NOT EXISTS `tbl_entries_data_{$field_id}` (
`id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`entry_id` INT(11) UNSIGNED NOT NULL,
`handle` VARCHAR(255) DEFAULT NULL,
`value` TEXT DEFAULT NULL,
`value_formatted` TEXT DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `entry_id` (`entry_id`),
FULLTEXT KEY `value` (`value`),
FULLTEXT KEY `value_formatted` (`value_formatted`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
");
}

public function allowDatasourceOutputGrouping()
Expand Down Expand Up @@ -141,9 +141,7 @@ public function displaySettingsPanel(XMLElement &$wrapper, $errors = null)
$help = new XMLElement('p');
$help->setAttribute('class', 'help');

$help->setValue(__('
To access the other fields, use XPath: <code>{//entry/field-one} static text {//entry/field-two}</code>.
'));
$help->setValue(__('Use XPath to access other fields: <code>{//entry/field-one} static text {//entry/field-two}</code>.'));

$label->appendChild($help);
$wrapper->appendChild($label);
Expand Down Expand Up @@ -384,12 +382,54 @@ public function compile(&$entry)
$expression = $this->get('expression');
$replacements = array();

// Find queries:
// Extract xpath queries from the expression
preg_match_all('/\{[^\}]+\}/', $expression, $matches);

// Find replacements:
// Get root node name (will be 'data' if not otherwise defined in a
// xslt utility)
$root_node_name = (string)$xpath->evaluate('name(/*)');

// Find replacements
// Including modifications for edge-cases (#35)
foreach ($matches[0] as $match) {
$result = @$xpath->evaluate('string(' . trim($match, '{}') . ')');

// Add leading slashes if the expression starts with 'entry'
// or '/entry'. While these are not valid xpath expressions in
// the given context, they make the expressions in 2.X backwards
// compatible with 1.X without causing any problems.
if (strpos($match, '{entry/' ) === 0) {
$path = '//' . trim($match, '{}');
} else if (strpos($match, '{/entry/' ) === 0) {
$path = '/' . trim($match, '{}');
}

// Add a leading '/' if the expression starts directly with the
// root node name.
else if (strpos($match, '{'.$root_node_name ) === 0) {
$path = '/' . trim($match, '{}');
}

// Insert a '/' if the expression contains a '(' directly followed
// by the root node name
else if (strpos($match,'('.$root_node_name) !== false) {
$search = '(';
$index = strpos($match, $search);
$string = substr_replace($match, $search.'/', $index, 1);
$path = trim($string, '{}');
}

// Add the name of the root node if the expression only consists of '/'
else if ($match === '{/}') {
$path = '/' . $root_node_name;
}

// Use the given expression without modifications
else {
$path = trim($match, '{}');
}

// Evaluate the (modified) xpath:
$result = @$xpath->evaluate('string(' . $path . ')');

if (!empty($result)) {
$replacements[$match] = trim($result);
Expand Down Expand Up @@ -461,13 +501,13 @@ public function buildDSRetrievalSQL($data, &$joins, &$where, $andOperation = fal
$data = $this->cleanValue($data);
++$this->_key;
$joins .= "
LEFT JOIN
`tbl_entries_data_{$field_id}` AS t{$field_id}_{$this->_key}
ON (e.id = t{$field_id}_{$this->_key}.entry_id)
";
LEFT JOIN
`tbl_entries_data_{$field_id}` AS t{$field_id}_{$this->_key}
ON (e.id = t{$field_id}_{$this->_key}.entry_id)
";
$where .= "
AND {$negate}(MATCH (t{$field_id}_{$this->_key}.value) AGAINST ('{$data}' IN BOOLEAN MODE))
";
AND {$negate}(MATCH (t{$field_id}_{$this->_key}.value) AGAINST ('{$data}' IN BOOLEAN MODE))
";
} elseif (preg_match('/^(not-)?((starts|ends)-with|contains):\s*/', $data[0], $matches)) {
$data = trim(array_pop(explode(':', $data[0], 2)));

Expand All @@ -490,16 +530,16 @@ public function buildDSRetrievalSQL($data, &$joins, &$where, $andOperation = fal

++$this->_key;
$joins .= "
LEFT JOIN
`tbl_entries_data_{$field_id}` AS t{$field_id}_{$this->_key}
ON (e.id = t{$field_id}_{$this->_key}.entry_id)
";
LEFT JOIN
`tbl_entries_data_{$field_id}` AS t{$field_id}_{$this->_key}
ON (e.id = t{$field_id}_{$this->_key}.entry_id)
";
$where .= "
AND {$negate}(
t{$field_id}_{$this->_key}.handle LIKE '{$data}'
OR t{$field_id}_{$this->_key}.value LIKE '{$data}'
)
";
AND {$negate}(
t{$field_id}_{$this->_key}.handle LIKE '{$data}'
OR t{$field_id}_{$this->_key}.value LIKE '{$data}'
)
";
} elseif (preg_match('/^(?:equal to or )?(?:less than|more than|equal to) -?\d+(?:\.\d+)?$/i', $data[0])) {
$comparisons = array();
foreach ($data as $string) {
Expand Down Expand Up @@ -528,35 +568,35 @@ public function buildDSRetrievalSQL($data, &$joins, &$where, $andOperation = fal
if (!empty($comparisons)) {
++$this->_key;
$joins .= "
LEFT JOIN
`tbl_entries_data_{$field_id}` AS t{$field_id}_{$this->_key}
ON (e.id = t{$field_id}_{$this->_key}.entry_id)
";
LEFT JOIN
`tbl_entries_data_{$field_id}` AS t{$field_id}_{$this->_key}
ON (e.id = t{$field_id}_{$this->_key}.entry_id)
";

$value = " t{$field_id}_{$this->_key}.value ";
$comparisons = $value.implode(' '.($andOperation ? 'AND' : 'OR').$value, $comparisons);

$where .= "
AND (
{$comparisons}
)
";
AND (
{$comparisons}
)
";
}
} elseif ($andOperation) {
foreach ($data as $value) {
++$this->_key;
$value = $this->cleanValue($value);
$joins .= "
LEFT JOIN
`tbl_entries_data_{$field_id}` AS t{$field_id}_{$this->_key}
ON (e.id = t{$field_id}_{$this->_key}.entry_id)
";
LEFT JOIN
`tbl_entries_data_{$field_id}` AS t{$field_id}_{$this->_key}
ON (e.id = t{$field_id}_{$this->_key}.entry_id)
";
$where .= "
AND (
t{$field_id}_{$this->_key}.handle = '{$value}'
OR t{$field_id}_{$this->_key}.value = '{$value}'
)
";
AND (
t{$field_id}_{$this->_key}.handle = '{$value}'
OR t{$field_id}_{$this->_key}.value = '{$value}'
)
";
}
} else {
if (!is_array($data)) {
Expand All @@ -570,16 +610,16 @@ public function buildDSRetrievalSQL($data, &$joins, &$where, $andOperation = fal
++$this->_key;
$data = implode("', '", $data);
$joins .= "
LEFT JOIN
`tbl_entries_data_{$field_id}` AS t{$field_id}_{$this->_key}
ON (e.id = t{$field_id}_{$this->_key}.entry_id)
";
LEFT JOIN
`tbl_entries_data_{$field_id}` AS t{$field_id}_{$this->_key}
ON (e.id = t{$field_id}_{$this->_key}.entry_id)
";
$where .= "
AND (
t{$field_id}_{$this->_key}.handle IN ('{$data}')
OR t{$field_id}_{$this->_key}.value IN ('{$data}')
)
";
AND (
t{$field_id}_{$this->_key}.handle IN ('{$data}')
OR t{$field_id}_{$this->_key}.value IN ('{$data}')
)
";
}

return true;
Expand All @@ -596,10 +636,10 @@ public function buildSortingSQL(&$joins, &$where, &$sort, $order = 'ASC')
} else {
$sort = sprintf(
'ORDER BY (
SELECT %s
FROM tbl_entries_data_%d AS `ed`
WHERE entry_id = e.id
) %s',
SELECT %s
FROM tbl_entries_data_%d AS `ed`
WHERE entry_id = e.id
) %s',
'`ed`.value',
$this->get('id'),
$order
Expand Down

0 comments on commit 3cbe0aa

Please sign in to comment.