1
0
mirror of https://github.com/processwire/processwire.git synced 2025-08-16 03:34:33 +02:00

Improvements to FieldtypeOptions plus some refactoring in SelectableOptionsManager. This also removes the existing combined fulltext index 'value_title' and replaces it with separate fulltext indexes for value and title, since the previous one did not appear to ever be used. These table schema changes are applied at Modules > Refresh.

This commit is contained in:
Ryan Cramer
2021-07-23 09:55:19 -04:00
parent 6368910434
commit e9754b1177
2 changed files with 235 additions and 72 deletions

View File

@@ -3,7 +3,7 @@
/**
* ProcessWire Select Options Fieldtype
*
* ProcessWire 3.x, Copyright 2020 by Ryan Cramer
* ProcessWire 3.x, Copyright 2021 by Ryan Cramer
* https://processwire.com
*
* @property SelectableOptionManager $manager
@@ -16,7 +16,7 @@ class FieldtypeOptions extends FieldtypeMulti implements Module {
return array(
'title' => __('Select Options', __FILE__),
'summary' => __('Field that stores single and multi select options.', __FILE__),
'version' => 1,
'version' => 2,
);
}
@@ -338,14 +338,17 @@ class FieldtypeOptions extends FieldtypeMulti implements Module {
if(!count($options)) {
if(!$subfield || !SelectableOption::isProperty($subfield)) {
// if empty subfield or not a subfield we recognize, just assume title
$subfield = 'title';
$s = rtrim($subfield, '0123456789');
if($subfield && $this->manager->useLanguages() && SelectableOption::isProperty($s)) {
// property and language id, i.e. title1234 or value1235
} else {
// if empty subfield or not a subfield we recognize, just assume title
$subfield = 'title';
}
}
$options = $this->manager->findOptionsByProperty($query->field, $subfield, $operator, $value);
}
$option = $options->first();
if($operator != '=' && $operator != '!=') {
// for fulltext operations...
// since we are now just matching IDs of already found options
@@ -353,7 +356,16 @@ class FieldtypeOptions extends FieldtypeMulti implements Module {
}
$subfield = 'data';
$value = $option ? $option->id : null;
if(count($options) < 2) {
$option = $options->first();
$value = $option ? $option->id : '';
} else {
$value = array();
foreach($options as $option) {
$value[] = $option->id;
}
}
}
if($operator == '!=') {
@@ -423,11 +435,13 @@ class FieldtypeOptions extends FieldtypeMulti implements Module {
'name' => 'title',
'input' => 'text',
'operators' => array('%=', '=', '!=', '^=', '$=', '*=', '~='),
'label' => $this->_('Title'),
),
'value' => array(
'name' => 'value',
'input' => 'text',
'operators' => array('%=', '=', '!=', '^=', '$=', '*=', '~='),
'label' => $this->_('Value text'),
),
'id' => array(
'name' => 'id',
@@ -436,6 +450,25 @@ class FieldtypeOptions extends FieldtypeMulti implements Module {
),
);
$languages = $this->wire()->languages;
if($languages) {
foreach($languages as $language) {
if($language->isDefault()) continue;
$subfield = $subfields['title'];
$subfield['name'] .= $language->id;
$subfield['label'] .= " ($language->name)";
$subfields["title$language"] = $subfield;
$subfield = $subfields['value'];
$subfield['name'] .= $language->id;
$subfield['label'] .= " ($language->name)";
$subfields["value$language"] = $subfield;
}
ksort($subfields);
}
$info['subfields'] = array_merge($info['subfields'], $subfields);
return $info;
@@ -621,4 +654,15 @@ class FieldtypeOptions extends FieldtypeMulti implements Module {
return $this->manager->deleteOptions($this->_getField($field), $options);
}
/**
* Upgrade module version
*
* @param string $fromVersion
* @param string $toVersion
*
*/
public function upgrade($fromVersion, $toVersion) {
$this->manager->upgrade($fromVersion, $toVersion);
}
}

View File

@@ -6,7 +6,7 @@
* Handles management of the fieldtype_options table and related field_[name] table
* to assist FieldtypeOptions module.
*
* ProcessWire 3.x, Copyright 2016 by Ryan Cramer
* ProcessWire 3.x, Copyright 2021 by Ryan Cramer
* https://processwire.com
*
*/
@@ -215,7 +215,7 @@ class SelectableOptionManager extends Wire {
/** @var DatabaseQuerySelect $query */
$query = $this->wire(new DatabaseQuerySelect());
$query->select('*');
$query->select(self::optionsTable . '.*');
$query->from(self::optionsTable);
$query->where("fields_id=:fields_id");
$query->bindValue(':fields_id', $field->id);
@@ -755,77 +755,187 @@ class SelectableOptionManager extends Wire {
*
*/
public function updateLanguages(HookEvent $event = null) {
if($event) {} // ignore
if(!$this->useLanguages) return;
$database = $this->wire('database');
$table = self::optionsTable;
$languages = $this->wire('languages');
$maxLen = $database->getMaxIndexLength();
if(strtolower($this->wire('config')->dbCharset) == 'utf8mb4') $maxLen -= 20;
if(!$this->useLanguages || !$this->wire()->languages) return;
// check for added languages
foreach($languages as $language) {
if($language->isDefault()) continue;
$titleCol = "title" . (int) $language->id;
$valueCol = "value" . (int) $language->id;
$query = $database->prepare("SHOW COLUMNS FROM $table LIKE '$valueCol'");
$query->execute();
if($query->rowCount() > 0) continue;
$this->message("FieldtypeOptions: Add language $language->name (id=$language)", Notice::debug);
try {
$database->exec("ALTER TABLE $table ADD $titleCol TEXT");
$database->exec("ALTER TABLE $table ADD UNIQUE $titleCol ($titleCol($maxLen), fields_id)");
} catch(\Exception $e) {
$this->error($e->getMessage());
}
try {
$database->exec("ALTER TABLE $table ADD $valueCol VARCHAR($maxLen)");
$database->exec("ALTER TABLE $table ADD INDEX $valueCol ($valueCol($maxLen), fields_id)");
$database->exec("ALTER TABLE $table ADD FULLTEXT {$titleCol}_$valueCol ($titleCol, $valueCol)");
} catch(\Exception $e) {
$this->error($e->getMessage());
}
if($event) {
$language = $event->arguments(0); /** @var Language $language */
$updateType = $event->arguments(1); /** @var string $updateType one of 'added' or 'deleted' */
} else {
$language = null;
$updateType = '';
}
// check for deleted languages
$query = $database->prepare("SHOW COLUMNS FROM $table LIKE 'title%'");
$query->execute();
while($row = $query->fetch(\PDO::FETCH_ASSOC)) {
$name = $row['Field'];
if($name === 'title') continue;
$id = (int) str_replace('title', '', $name);
$language = $languages->get($id);
if($language && $language->id) continue;
$titleCol = "title$id";
$valueCol = "value$id";
$this->message("FieldtypeOptions: Delete language $id", Notice::debug);
if($updateType === 'deleted') {
$sqls = $this->checkLanguagesDeleted($language);
} else if($updateType === 'added') {
$sqls = $this->checkLanguagesAdded($language);
} else {
$sqls = array_merge($this->checkLanguagesAdded(), $this->checkLanguagesDeleted());
}
$database = $this->wire()->database;
foreach($sqls as $sql) {
try {
$database->exec("ALTER TABLE $table DROP INDEX $titleCol");
$database->exec("ALTER TABLE $table DROP INDEX $valueCol");
$database->exec("ALTER TABLE $table DROP INDEX {$titleCol}_$valueCol");
$database->exec("ALTER TABLE $table DROP $titleCol");
$database->exec("ALTER TABLE $table DROP $valueCol");
$database->exec($sql);
} catch(\Exception $e) {
$this->error($e->getMessage());
$this->error("$sql -- " . $e->getMessage());
}
}
}
/**
* Check for added languages
*
* @param Language|null $languageAdded
* @return array SQL statements to add language when appropriate
*
*/
protected function checkLanguagesAdded($languageAdded = null) {
$database = $this->wire()->database;
$table = self::optionsTable;
$maxLen = $database->getMaxIndexLength();
$sqls = array();
$languages = $languageAdded ? array($languageAdded) : $this->wire()->languages;
if(strtolower($this->wire()->config->dbCharset) == 'utf8mb4') $maxLen -= 20;
foreach($languages as $language) {
/** @var Language $language */
if($language->isDefault()) continue;
$titleCol = "title" . (int) $language->id;
$valueCol = "value" . (int) $language->id;
if($database->columnExists($table, $valueCol)) continue;
$this->message("FieldtypeOptions: Add language $language->name (id=$language)", Notice::debug);
$sqls[] = "ALTER TABLE $table ADD $titleCol TEXT";
$sqls[] = "ALTER TABLE $table ADD UNIQUE $titleCol ($titleCol($maxLen), fields_id)";
$sqls[] = "ALTER TABLE $table ADD $valueCol VARCHAR($maxLen)";
$sqls[] = "ALTER TABLE $table ADD INDEX $valueCol ($valueCol($maxLen), fields_id)";
$sqls[] = "CREATE FULLTEXT INDEX {$titleCol}_ft ON $table($titleCol)";
$sqls[] = "CREATE FULLTEXT INDEX {$valueCol}_ft ON $table($valueCol)";
}
return $sqls;
}
/**
* Check for deleted languages
*
* @param Language|null $languageDeleted
* @return array SQL statements to delete language when appropriate
*
*/
protected function checkLanguagesDeleted($languageDeleted = null) {
$database = $this->wire()->database;
$table = self::optionsTable;
$languages = $this->wire()->languages;
$indexes = $database->getIndexes($table, true);
$query = $database->prepare("SHOW COLUMNS FROM $table LIKE 'title%'");
$query->execute();
$rows = array();
$sqls = array();
while($row = $query->fetch(\PDO::FETCH_ASSOC)) $rows[] = $row;
$query->closeCursor();
foreach($rows as $row) {
$name = $row['Field'];
if($name === 'title') continue;
$id = (int) str_replace('title', '', $name);
if($languageDeleted) {
// language specified and if it matches column name then allow it
if($languageDeleted->id !== $id) continue;
} else {
// check if language exists and if yes then skip it
$language = $languages->get($id);
if($language && $language->id) continue;
}
$this->message("FieldtypeOptions: Delete language $id", Notice::debug);
$titleCol = "title$id";
$valueCol = "value$id";
// Drop unique index: title+fields_id
if(isset($indexes[$titleCol])) $sqls[] = "ALTER TABLE $table DROP INDEX $titleCol";
if(isset($indexes[$valueCol])) $sqls[] = "ALTER TABLE $table DROP INDEX $valueCol";
// Drop fulltext index
if(isset($indexes[$titleCol . '_ft'])) $sqls[] = "ALTER TABLE $table DROP INDEX {$titleCol}_ft";
if(isset($indexes[$valueCol . '_ft'])) $sqls[] = "ALTER TABLE $table DROP INDEX {$valueCol}_ft";
// Drop older style combined index if present
if(isset($indexes["{$titleCol}_$valueCol"])) $sqls[] = "ALTER TABLE $table DROP INDEX {$titleCol}_$valueCol";
// drop column
$sqls[] = "ALTER TABLE $table DROP $titleCol";
$sqls[] = "ALTER TABLE $table DROP $valueCol";
}
return $sqls;
}
/**
* Upgrade fieldtype_options table
*
* @param string $fromVersion
* @param string $toVersion
* @throws WireException
*
*/
public function upgrade($fromVersion, $toVersion) {
if($fromVersion && $toVersion) {} // ignore
$database = $this->wire()->database;
$table = self::optionsTable;
if(!$database->tableExists($table)) return;
$indexes = $database->getIndexes($table, true);
if(isset($indexes['title_value'])) {
// removed combined title+value indexes created prior to 3.0.182
// and replace with separate fulltext indexes for title and value
foreach($indexes as $name => $info) {
if(strpos($name, 'title') !== 0) continue;
if(!strpos($name, '_value')) continue;
// i.e. title_value or title123_value123
$database->exec("ALTER TABLE $table DROP INDEX `$name`");
$this->message("Dropped index $table.$name", Notice::debug);
foreach($info['columns'] as $col) {
try {
$sql = "CREATE FULLTEXT INDEX {$col}_ft ON $table($col)";
$database->exec($sql);
$this->message("Added fulltext index for $table.$col", Notice::debug);
} catch(\Exception $e) {
$this->error("$sql -- " . $e->getMessage());
}
}
}
}
}
/**
* Install
*
*/
public function install() {
$database = $this->wire('database');
$database = $this->wire()->database;
$maxLen = $database->getMaxIndexLength();
$query = $database->prepare("SHOW TABLES LIKE '" . self::optionsTable . "'");
$query->execute();
if($query->rowCount() == 0) {
$engine = $this->wire('config')->dbEngine;
$charset = $this->wire('config')->dbCharset;
if(!$database->tableExists(self::optionsTable)) {
$config = $this->wire()->config;
$engine = $config->dbEngine;
$charset = $config->dbCharset;
$table = self::optionsTable;
if(strtolower($charset) == 'utf8mb4') $maxLen -= 20;
$sql =
"CREATE TABLE " . self::optionsTable . " (" .
"CREATE TABLE $table (" .
"fields_id INT UNSIGNED NOT NULL, " .
"option_id INT UNSIGNED NOT NULL, " .
"`title` TEXT, " .
@@ -834,18 +944,27 @@ class SelectableOptionManager extends Wire {
"PRIMARY KEY (fields_id, option_id), " .
"UNIQUE title (title($maxLen), fields_id), " .
"INDEX `value` (`value`($maxLen), fields_id), " .
"INDEX sort (sort, fields_id), " .
"FULLTEXT title_value (`title`, `value`)" .
"INDEX sort (sort, fields_id) " .
") ENGINE=$engine DEFAULT CHARSET=$charset";
$database->exec($sql);
try {
$database->exec("CREATE FULLTEXT INDEX title_ft ON $table(`title`)");
$database->exec("CREATE FULLTEXT INDEX value_ft ON $table(`value`)");
} catch(\Exception $e) {
$this->error($e->getMessage());
}
}
if($this->useLanguages) $this->updateLanguages();
}
/**
* Uninstall
*
*/
public function uninstall() {
try {
$this->wire('database')->exec("DROP TABLE " . self::optionsTable);
$this->wire()->database->exec("DROP TABLE " . self::optionsTable);
} catch(\Exception $e) {
$this->warning($e->getMessage());
}