mirror of
https://github.com/processwire/processwire.git
synced 2025-08-16 11:44:42 +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:
@@ -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)) {
|
||||
$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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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');
|
||||
if(!$this->useLanguages || !$this->wire()->languages) return;
|
||||
|
||||
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 = '';
|
||||
}
|
||||
|
||||
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($sql);
|
||||
} catch(\Exception $e) {
|
||||
$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;
|
||||
$languages = $this->wire('languages');
|
||||
$maxLen = $database->getMaxIndexLength();
|
||||
if(strtolower($this->wire('config')->dbCharset) == 'utf8mb4') $maxLen -= 20;
|
||||
$sqls = array();
|
||||
$languages = $languageAdded ? array($languageAdded) : $this->wire()->languages;
|
||||
|
||||
if(strtolower($this->wire()->config->dbCharset) == 'utf8mb4') $maxLen -= 20;
|
||||
|
||||
// check for added languages
|
||||
foreach($languages as $language) {
|
||||
/** @var Language $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;
|
||||
if($database->columnExists($table, $valueCol)) 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());
|
||||
}
|
||||
$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)";
|
||||
}
|
||||
|
||||
// check for deleted languages
|
||||
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();
|
||||
while($row = $query->fetch(\PDO::FETCH_ASSOC)) {
|
||||
$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";
|
||||
$this->message("FieldtypeOptions: Delete language $id", Notice::debug);
|
||||
|
||||
// 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 {
|
||||
$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");
|
||||
$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($e->getMessage());
|
||||
$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());
|
||||
}
|
||||
|
Reference in New Issue
Block a user