1
0
mirror of https://github.com/processwire/processwire.git synced 2025-08-17 04:04:13 +02:00
This commit is contained in:
Ryan Cramer
2023-06-08 16:42:34 -04:00
parent 16d0d77d4c
commit e59a44a83d
2 changed files with 109 additions and 23 deletions

View File

@@ -9,7 +9,7 @@
* /wire/core/Fieldtype.php * /wire/core/Fieldtype.php
* /wire/core/FieldtypeMulti.php * /wire/core/FieldtypeMulti.php
* *
* ProcessWire 3.x, Copyright 2021 by Ryan Cramer * ProcessWire 3.x, Copyright 2023 by Ryan Cramer
* https://processwire.com * https://processwire.com
* *
*/ */
@@ -19,7 +19,7 @@ class FieldtypePage extends FieldtypeMulti implements Module, ConfigurableModule
public static function getModuleInfo() { public static function getModuleInfo() {
return array( return array(
'title' => 'Page Reference', 'title' => 'Page Reference',
'version' => 106, 'version' => 107,
'summary' => 'Field that stores one or more references to ProcessWire pages', 'summary' => 'Field that stores one or more references to ProcessWire pages',
'permanent' => true, 'permanent' => true,
); );
@@ -45,6 +45,13 @@ class FieldtypePage extends FieldtypeMulti implements Module, ConfigurableModule
'published', 'published',
); );
/**
* Runtime/temp caches indexed by function name
*
* @var array
*
*/
protected $caches = array();
/** /**
* Setup a hook to Pages::delete so that we can remove references when pages are deleted * Setup a hook to Pages::delete so that we can remove references when pages are deleted
@@ -356,19 +363,20 @@ class FieldtypePage extends FieldtypeMulti implements Module, ConfigurableModule
if($field->get('derefAsPage') > 0) { if($field->get('derefAsPage') > 0) {
// if the $value isn't specifically a Page, make it a blank array for storage // if the $value isn't specifically a Page, make it a blank array for storage
if(!$value instanceof Page || !$value->id) return $sleepValue; if(!$value instanceof Page || !$value->id) return $sleepValue;
// if $value is a Page (not a NullPage) then place it's ID in an array for storage // if $value is a Page (not a NullPage) then place its ID in an array for storage
$this->isValidPage($value, $field, $page, true); if($this->isValidPage($value, $field, $page)) {
$sleepValue[] = $value->id; $sleepValue[] = $value->id;
}
} else { } else {
// if $value isn't a PageArray then we'll store a blank array // if $value isn't a PageArray then we'll store a blank array
if(!$value instanceof PageArray) return $sleepValue; if(!$value instanceof PageArray) return $sleepValue;
// iterate through the array and place each Page ID // iterate through the array and place each Page ID
foreach($value as $pg) { foreach($value as $pg) {
if(!$pg->id) continue; if($pg->id && $this->isValidPage($pg, $field, $page)) {
$this->isValidPage($pg, $field, $page, true);
$sleepValue[] = $pg->id; $sleepValue[] = $pg->id;
} }
} }
}
return $sleepValue; return $sleepValue;
} }
@@ -1076,10 +1084,14 @@ class FieldtypePage extends FieldtypeMulti implements Module, ConfigurableModule
$database = $this->wire()->database; $database = $this->wire()->database;
if(!in_array($subfield, $this->nativeNames)) return false; $key = array_search($subfield, $this->nativeNames);
if($key === false) return false;
$subfield = $this->nativeNames[$key];
// we let the custom field query matcher handle the '!=' scenario // we let the custom field query matcher handle the '!=' scenario
if(!$database->isOperator($operator)) return $this->getMatchQueryCustom($query, $table, $subfield, $operator, $value); if(!$database->isOperator($operator)) {
return $this->getMatchQueryCustom($query, $table, $subfield, $operator, $value);
}
if($subfield == 'created' || $subfield == 'modified' || $subfield == 'published') { if($subfield == 'created' || $subfield == 'modified' || $subfield == 'published') {
if(!ctype_digit($value)) $value = strtotime($value); if(!ctype_digit($value)) $value = strtotime($value);
@@ -1106,7 +1118,27 @@ class FieldtypePage extends FieldtypeMulti implements Module, ConfigurableModule
} else if($subfield == 'name') { } else if($subfield == 'name') {
$value = $this->wire()->sanitizer->pageName($value, Sanitizer::toAscii); $value = $this->wire()->sanitizer->pageName($value, Sanitizer::toAscii);
} else $value = (int) $value; } else {
$value = (int) $value;
}
$values = $query->selector->values();
if(count($values) > 1 && $operator === '=') {
// we are in an OR match among multiple values
// make sure they all exist before sending to match query
// which performs regular joins (not left joins)
$valids = $this->getMatchQueryNativeValidValues($subfield, $values);
if(!count($valids)) {
// none of the values in the OR are possible matches
// let it pass through to query, as query will fail as it should
} else if(count($valids) === count($values)) {
// all values are valid
} else if(!in_array($value, $valids)) {
// value not valid and should be ignored since others are valid
return true;
}
}
static $n = 0; static $n = 0;
$table = $database->escapeTable($table); $table = $database->escapeTable($table);
@@ -1119,6 +1151,56 @@ class FieldtypePage extends FieldtypeMulti implements Module, ConfigurableModule
return true; return true;
} }
/**
* Get values for $subfield that match those of given $values
*
* @param string $subfield
* @param array $values
* @return array
* @since 3.0.220
*
*/
protected function getMatchQueryNativeValidValues($subfield, array $values) {
$cacheKey = implode('|', $values);
if(isset($this->caches[__FUNCTION__])) {
// cache is used to prevent back-to-back duplicate queries
$a = $this->caches[__FUNCTION__];
if($a[0] === $cacheKey) return $a[1];
}
$wheres = array();
$binds = array();
foreach(array_values($values) as $i => $v) {
$wheres[] = "$subfield=:value$i";
$binds[":value$i"] = $v;
}
$where = implode(' OR ', $wheres);
$sql = "SELECT id, $subfield FROM pages WHERE $where GROUP BY $subfield";
$query = $this->wire()->database->prepare($sql);
foreach($binds as $bindKey => $bindValue) {
$query->bindValue($bindKey, $bindValue);
}
$query->execute();
$valids = array();
while($row = $query->fetch(\PDO::FETCH_NUM)) {
$id = (int) $row[0];
$valids[$id] = $row[1];
}
$query->closeCursor();
$this->caches[__FUNCTION__] = array($cacheKey, $valids);
return $valids;
}
/** /**
* Update a DatabaseSelectQuery object to match a Page containing a matching custom subfield * Update a DatabaseSelectQuery object to match a Page containing a matching custom subfield
* *
@@ -1168,7 +1250,7 @@ class FieldtypePage extends FieldtypeMulti implements Module, ConfigurableModule
if(in_array($subfield, $this->nativeNames)) { if(in_array($subfield, $this->nativeNames)) {
// fine then, we can handle that here when needed (like !=) // fine then, we can handle that here when needed (like !=)
} else { } else {
$subfield = $this->wire('fields')->get($subfield); $subfield = $this->wire()->fields->get($subfield);
if(!$subfield) return false; // not a custom field if(!$subfield) return false; // not a custom field
$subfield = $subfield->name; $subfield = $subfield->name;
} }
@@ -1202,7 +1284,7 @@ class FieldtypePage extends FieldtypeMulti implements Module, ConfigurableModule
/** @noinspection PhpUnusedLocalVariableInspection */ /** @noinspection PhpUnusedLocalVariableInspection */
list($itemField, $itemSubfield) = explode('.', $itemField); list($itemField, $itemSubfield) = explode('.', $itemField);
if($itemField != $field->name) continue; // only group the same fields together in one selector query if($itemField != $field->name) continue; // only group the same fields together in one selector query
if(!preg_match('/^' . $group . '@' . $field->name . '\.(([_a-zA-z0-9]+).*)$/', (string) $item, $matches)) continue; if(!preg_match('/^' . $group . '@' . $field->name . '\.(([_a-zA-Z0-9]+).*)$/', (string) $item, $matches)) continue;
// extract the field name portion so we just get the subfield and rest of the selector // extract the field name portion so we just get the subfield and rest of the selector
$selector .= "$matches[1], "; $selector .= "$matches[1], ";
} }
@@ -1523,20 +1605,21 @@ class FieldtypePage extends FieldtypeMulti implements Module, ConfigurableModule
$data = parent::___exportConfigData($field, $data); $data = parent::___exportConfigData($field, $data);
if(!empty($data['parent_id']) && ctype_digit("$data[parent_id]")) { if(!empty($data['parent_id']) && ctype_digit("$data[parent_id]")) {
// convert parent ID to parent path // convert parent ID to parent path
$data['parent_id'] = $this->wire('pages')->get((int) $data['parent_id'])->path; $data['parent_id'] = $this->wire()->pages->get((int) $data['parent_id'])->path;
} }
$templates = $this->wire()->templates;
foreach(array('template_id', 'template_ids') as $key) { foreach(array('template_id', 'template_ids') as $key) {
if(empty($data[$key])) continue; if(empty($data[$key])) continue;
if(is_array($data[$key])) { if(is_array($data[$key])) {
// convert array of template ids to template names // convert array of template ids to template names
foreach($data[$key] as $k => $id) { foreach($data[$key] as $k => $id) {
if(ctype_digit("$id")) continue; if(ctype_digit("$id")) continue;
$template = $this->wire('templates')->get((int) $id); $template = $templates->get((int) $id);
if($template) $data[$key][$k] = $template->name; if($template) $data[$key][$k] = $template->name;
} }
} else if(ctype_digit((string) $data[$key])) { } else if(ctype_digit((string) $data[$key])) {
// convert template id to template name // convert template id to template name
$template = $this->wire('templates')->get((int) $data[$key]); $template = $templates->get((int) $data[$key]);
if($template) $data[$key] = $template->name; if($template) $data[$key] = $template->name;
} }
} }

View File

@@ -38,12 +38,15 @@ class PageField extends Field {
*/ */
public function getTemplateAndParentIds() { public function getTemplateAndParentIds() {
$pages = $this->wire()->pages;
$templates = $this->wire()->templates;
$parentId = $this->get('parent_id'); $parentId = $this->get('parent_id');
$parentIds = array(); $parentIds = array();
$templateIds = array(); $templateIds = array();
if(empty($parentId)) { if(empty($parentId)) {
$parentIds = array(); // $parentIds = array();
} else if(is_string($parentId)) { } else if(is_string($parentId)) {
if(ctype_digit($parentId)) { if(ctype_digit($parentId)) {
$parentIds = array((int) $parentId); $parentIds = array((int) $parentId);
@@ -80,7 +83,7 @@ class PageField extends Field {
if(ctype_digit("$v")) { if(ctype_digit("$v")) {
$parentIds[] = (int) $v; $parentIds[] = (int) $v;
} else if(strpos($v, '/')) { } else if(strpos($v, '/')) {
$p = $this->wire()->pages->get($v); $p = $pages->get($v);
if($p->id) $parentIds[] = $p->id; if($p->id) $parentIds[] = $p->id;
} }
} }
@@ -93,7 +96,7 @@ class PageField extends Field {
if(ctype_digit("$v")) { if(ctype_digit("$v")) {
$templateIds[] = (int) $v; $templateIds[] = (int) $v;
} else if($v) { } else if($v) {
$template = $this->wire()->templates->get($v); $template = $templates->get($v);
if($template instanceof Template) $templateIds[] = $template->id; if($template instanceof Template) $templateIds[] = $template->id;
} }
} }