1ce42304aSGreg Roach<?php 2ce42304aSGreg Roach 3ce42304aSGreg Roach/** 4ce42304aSGreg Roach * webtrees: online genealogy 589f7189bSGreg Roach * Copyright (C) 2021 webtrees development team 6ce42304aSGreg Roach * This program is free software: you can redistribute it and/or modify 7ce42304aSGreg Roach * it under the terms of the GNU General Public License as published by 8ce42304aSGreg Roach * the Free Software Foundation, either version 3 of the License, or 9ce42304aSGreg Roach * (at your option) any later version. 10ce42304aSGreg Roach * This program is distributed in the hope that it will be useful, 11ce42304aSGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of 12ce42304aSGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13ce42304aSGreg Roach * GNU General Public License for more details. 14ce42304aSGreg Roach * You should have received a copy of the GNU General Public License 1589f7189bSGreg Roach * along with this program. If not, see <https://www.gnu.org/licenses/>. 16ce42304aSGreg Roach */ 17ce42304aSGreg Roach 18ce42304aSGreg Roachdeclare(strict_types=1); 19ce42304aSGreg Roach 20ce42304aSGreg Roachnamespace Fisharebest\Webtrees\Module; 21ce42304aSGreg Roach 22ce42304aSGreg Roachuse Fisharebest\Webtrees\Exceptions\HttpNotFoundException; 23ce42304aSGreg Roachuse Fisharebest\Webtrees\Family; 24ce42304aSGreg Roachuse Fisharebest\Webtrees\GedcomRecord; 25ce42304aSGreg Roachuse Fisharebest\Webtrees\I18N; 26ce42304aSGreg Roachuse Fisharebest\Webtrees\Individual; 27*2da2d0c9SGreg Roachuse Fisharebest\Webtrees\Location; 28ce42304aSGreg Roachuse Fisharebest\Webtrees\Media; 29ce42304aSGreg Roachuse Fisharebest\Webtrees\Note; 30ce42304aSGreg Roachuse Fisharebest\Webtrees\Repository; 31ce42304aSGreg Roachuse Fisharebest\Webtrees\Services\DataFixService; 32ce42304aSGreg Roachuse Fisharebest\Webtrees\Source; 33ce42304aSGreg Roachuse Fisharebest\Webtrees\Submitter; 34ce42304aSGreg Roachuse Fisharebest\Webtrees\Tree; 35ce42304aSGreg Roachuse Illuminate\Database\Capsule\Manager as DB; 36ce42304aSGreg Roachuse Illuminate\Database\Query\Builder; 37ce42304aSGreg Roachuse Illuminate\Support\Collection; 38ce42304aSGreg Roachuse Throwable; 39ce42304aSGreg Roach 40ce42304aSGreg Roachuse function addcslashes; 41ce42304aSGreg Roachuse function asort; 42ce42304aSGreg Roachuse function preg_match; 43ce42304aSGreg Roachuse function preg_quote; 44ce42304aSGreg Roachuse function preg_replace; 45ce42304aSGreg Roachuse function view; 46ce42304aSGreg Roach 47ce42304aSGreg Roach/** 48ce42304aSGreg Roach * Class FixSearchAndReplace 49ce42304aSGreg Roach */ 50ce42304aSGreg Roachclass FixSearchAndReplace extends AbstractModule implements ModuleDataFixInterface 51ce42304aSGreg Roach{ 52ce42304aSGreg Roach use ModuleDataFixTrait; 53ce42304aSGreg Roach 54ce42304aSGreg Roach // A regular expression that never matches. 55ce42304aSGreg Roach private const INVALID_REGEX = '/(?!)/'; 56ce42304aSGreg Roach 57ce42304aSGreg Roach /** @var DataFixService */ 58ce42304aSGreg Roach private $data_fix_service; 59ce42304aSGreg Roach 60ce42304aSGreg Roach /** 61ce42304aSGreg Roach * FixMissingDeaths constructor. 62ce42304aSGreg Roach * 63ce42304aSGreg Roach * @param DataFixService $data_fix_service 64ce42304aSGreg Roach */ 65ce42304aSGreg Roach public function __construct(DataFixService $data_fix_service) 66ce42304aSGreg Roach { 67ce42304aSGreg Roach $this->data_fix_service = $data_fix_service; 68ce42304aSGreg Roach } 69ce42304aSGreg Roach 70ce42304aSGreg Roach /** 71ce42304aSGreg Roach * How should this module be identified in the control panel, etc.? 72ce42304aSGreg Roach * 73ce42304aSGreg Roach * @return string 74ce42304aSGreg Roach */ 75ce42304aSGreg Roach public function title(): string 76ce42304aSGreg Roach { 77ce42304aSGreg Roach /* I18N: Name of a module */ 78ce42304aSGreg Roach return I18N::translate('Search and replace'); 79ce42304aSGreg Roach } 80ce42304aSGreg Roach 81ce42304aSGreg Roach /** 82ce42304aSGreg Roach * A sentence describing what this module does. 83ce42304aSGreg Roach * 84ce42304aSGreg Roach * @return string 85ce42304aSGreg Roach */ 86ce42304aSGreg Roach public function description(): string 87ce42304aSGreg Roach { 88ce42304aSGreg Roach /* I18N: Description of a “Data fix” module */ 89ce42304aSGreg Roach return I18N::translate('Search and replace text, using simple searches or advanced pattern matching.'); 90ce42304aSGreg Roach } 91ce42304aSGreg Roach 92ce42304aSGreg Roach /** 93ce42304aSGreg Roach * Options form. 94ce42304aSGreg Roach * 95ce42304aSGreg Roach * @param Tree $tree 96ce42304aSGreg Roach * 97ce42304aSGreg Roach * @return string 98ce42304aSGreg Roach */ 99ce42304aSGreg Roach public function fixOptions(Tree $tree): string 100ce42304aSGreg Roach { 101ce42304aSGreg Roach $methods = [ 102ce42304aSGreg Roach 'exact' => I18N::translate('Match the exact text, even if it occurs in the middle of a word.'), 103ce42304aSGreg Roach 'words' => I18N::translate('Match the exact text, unless it occurs in the middle of a word.'), 104ce42304aSGreg Roach 'wildcards' => I18N::translate('Use a “?” to match a single character, use “*” to match zero or more characters.'), 105ce42304aSGreg Roach /* I18N: http://en.wikipedia.org/wiki/Regular_expression */ 1062ab7d347SGreg Roach 'regex' => I18N::translate('Regular expression'), 107ce42304aSGreg Roach ]; 108ce42304aSGreg Roach 109ce42304aSGreg Roach $types = [ 110ce42304aSGreg Roach Family::RECORD_TYPE => I18N::translate('Families'), 111ce42304aSGreg Roach Individual::RECORD_TYPE => I18N::translate('Individuals'), 112*2da2d0c9SGreg Roach Location::RECORD_TYPE => I18N::translate('Locations'), 113ce42304aSGreg Roach Media::RECORD_TYPE => I18N::translate('Media objects'), 114ce42304aSGreg Roach Note::RECORD_TYPE => I18N::translate('Notes'), 115ce42304aSGreg Roach Repository::RECORD_TYPE => I18N::translate('Repositories'), 116ce42304aSGreg Roach Source::RECORD_TYPE => I18N::translate('Sources'), 117ce42304aSGreg Roach Submitter::RECORD_TYPE => I18N::translate('Submitters'), 118ce42304aSGreg Roach ]; 119ce42304aSGreg Roach 120ce42304aSGreg Roach asort($types); 121ce42304aSGreg Roach 122ce42304aSGreg Roach return view('modules/fix-search-and-replace/options', [ 123ce42304aSGreg Roach 'default_method' => 'exact', 124ce42304aSGreg Roach 'default_type' => Individual::RECORD_TYPE, 125ce42304aSGreg Roach 'methods' => $methods, 126ce42304aSGreg Roach 'types' => $types, 127ce42304aSGreg Roach ]); 128ce42304aSGreg Roach } 129ce42304aSGreg Roach 130ce42304aSGreg Roach /** 131ce42304aSGreg Roach * A list of all records that need examining. This may include records 132ce42304aSGreg Roach * that do not need updating, if we can't detect this quickly using SQL. 133ce42304aSGreg Roach * 134ce42304aSGreg Roach * @param Tree $tree 135ce42304aSGreg Roach * @param array<string,string> $params 136ce42304aSGreg Roach * 137ce42304aSGreg Roach * @return Collection<string>|null 138ce42304aSGreg Roach */ 139ce42304aSGreg Roach protected function familiesToFix(Tree $tree, array $params): ?Collection 140ce42304aSGreg Roach { 141ce42304aSGreg Roach if ($params['type'] !== Family::RECORD_TYPE || $params['search'] === '') { 142ce42304aSGreg Roach return null; 143ce42304aSGreg Roach } 144ce42304aSGreg Roach 145ce42304aSGreg Roach $query = DB::table('families')->where('f_file', '=', $tree->id()); 146ce42304aSGreg Roach $this->recordQuery($query, 'f_gedcom', $params); 147ce42304aSGreg Roach 148ce42304aSGreg Roach return $query->pluck('f_id'); 149ce42304aSGreg Roach } 150ce42304aSGreg Roach 151ce42304aSGreg Roach /** 152ce42304aSGreg Roach * A list of all records that need examining. This may include records 153ce42304aSGreg Roach * that do not need updating, if we can't detect this quickly using SQL. 154ce42304aSGreg Roach * 155ce42304aSGreg Roach * @param Tree $tree 156ce42304aSGreg Roach * @param array<string,string> $params 157ce42304aSGreg Roach * 158ce42304aSGreg Roach * @return Collection<string>|null 159ce42304aSGreg Roach */ 160ce42304aSGreg Roach protected function individualsToFix(Tree $tree, array $params): ?Collection 161ce42304aSGreg Roach { 162ce42304aSGreg Roach if ($params['type'] !== Individual::RECORD_TYPE || $params['search'] === '') { 163ce42304aSGreg Roach return null; 164ce42304aSGreg Roach } 165ce42304aSGreg Roach 166ce42304aSGreg Roach $query = DB::table('individuals') 167ce42304aSGreg Roach ->where('i_file', '=', $tree->id()); 168ce42304aSGreg Roach 169ce42304aSGreg Roach $this->recordQuery($query, 'i_gedcom', $params); 170ce42304aSGreg Roach 171ce42304aSGreg Roach return $query->pluck('i_id'); 172ce42304aSGreg Roach } 173ce42304aSGreg Roach 174ce42304aSGreg Roach /** 175ce42304aSGreg Roach * A list of all records that need examining. This may include records 176ce42304aSGreg Roach * that do not need updating, if we can't detect this quickly using SQL. 177ce42304aSGreg Roach * 178ce42304aSGreg Roach * @param Tree $tree 179ce42304aSGreg Roach * @param array<string,string> $params 180ce42304aSGreg Roach * 181ce42304aSGreg Roach * @return Collection<string>|null 182ce42304aSGreg Roach */ 18392657c8aSGreg Roach protected function locationsToFix(Tree $tree, array $params): ?Collection 18492657c8aSGreg Roach { 18592657c8aSGreg Roach if ($params['type'] !== Note::RECORD_TYPE || $params['search'] === '') { 18692657c8aSGreg Roach return null; 18792657c8aSGreg Roach } 18892657c8aSGreg Roach 18992657c8aSGreg Roach $query = DB::table('other') 19092657c8aSGreg Roach ->where('o_file', '=', $tree->id()) 19192657c8aSGreg Roach ->where('o_type', '=', Location::RECORD_TYPE); 19292657c8aSGreg Roach 19392657c8aSGreg Roach $this->recordQuery($query, 'o_gedcom', $params); 19492657c8aSGreg Roach 19592657c8aSGreg Roach return $query->pluck('o_id'); 19692657c8aSGreg Roach } 19792657c8aSGreg Roach 19892657c8aSGreg Roach /** 19992657c8aSGreg Roach * A list of all records that need examining. This may include records 20092657c8aSGreg Roach * that do not need updating, if we can't detect this quickly using SQL. 20192657c8aSGreg Roach * 20292657c8aSGreg Roach * @param Tree $tree 20392657c8aSGreg Roach * @param array<string,string> $params 20492657c8aSGreg Roach * 20592657c8aSGreg Roach * @return Collection<string>|null 20692657c8aSGreg Roach */ 207ce42304aSGreg Roach protected function mediaToFix(Tree $tree, array $params): ?Collection 208ce42304aSGreg Roach { 209ce42304aSGreg Roach if ($params['type'] !== Media::RECORD_TYPE || $params['search'] === '') { 210ce42304aSGreg Roach return null; 211ce42304aSGreg Roach } 212ce42304aSGreg Roach 213ce42304aSGreg Roach $query = DB::table('media') 214ce42304aSGreg Roach ->where('m_file', '=', $tree->id()); 215ce42304aSGreg Roach 216ce42304aSGreg Roach $this->recordQuery($query, 'm_gedcom', $params); 217ce42304aSGreg Roach 218ce42304aSGreg Roach return $query->pluck('m_id'); 219ce42304aSGreg Roach } 220ce42304aSGreg Roach 221ce42304aSGreg Roach /** 222ce42304aSGreg Roach * A list of all records that need examining. This may include records 223ce42304aSGreg Roach * that do not need updating, if we can't detect this quickly using SQL. 224ce42304aSGreg Roach * 225ce42304aSGreg Roach * @param Tree $tree 226ce42304aSGreg Roach * @param array<string,string> $params 227ce42304aSGreg Roach * 228ce42304aSGreg Roach * @return Collection<string>|null 229ce42304aSGreg Roach */ 230ce42304aSGreg Roach protected function notesToFix(Tree $tree, array $params): ?Collection 231ce42304aSGreg Roach { 232ce42304aSGreg Roach if ($params['type'] !== Note::RECORD_TYPE || $params['search'] === '') { 233ce42304aSGreg Roach return null; 234ce42304aSGreg Roach } 235ce42304aSGreg Roach 236ce42304aSGreg Roach $query = DB::table('other') 237ce42304aSGreg Roach ->where('o_file', '=', $tree->id()) 238ce42304aSGreg Roach ->where('o_type', '=', Note::RECORD_TYPE); 239ce42304aSGreg Roach 240ce42304aSGreg Roach $this->recordQuery($query, 'o_gedcom', $params); 241ce42304aSGreg Roach 242ce42304aSGreg Roach return $query->pluck('o_id'); 243ce42304aSGreg Roach } 244ce42304aSGreg Roach 245ce42304aSGreg Roach /** 246ce42304aSGreg Roach * A list of all records that need examining. This may include records 247ce42304aSGreg Roach * that do not need updating, if we can't detect this quickly using SQL. 248ce42304aSGreg Roach * 249ce42304aSGreg Roach * @param Tree $tree 250ce42304aSGreg Roach * @param array<string,string> $params 251ce42304aSGreg Roach * 252ce42304aSGreg Roach * @return Collection<string>|null 253ce42304aSGreg Roach */ 254ce42304aSGreg Roach protected function repositoriesToFix(Tree $tree, array $params): ?Collection 255ce42304aSGreg Roach { 256ce42304aSGreg Roach if ($params['type'] !== Repository::RECORD_TYPE || $params['search'] === '') { 257ce42304aSGreg Roach return null; 258ce42304aSGreg Roach } 259ce42304aSGreg Roach 260ce42304aSGreg Roach $query = DB::table('other') 261ce42304aSGreg Roach ->where('o_file', '=', $tree->id()) 262ce42304aSGreg Roach ->where('o_type', '=', Repository::RECORD_TYPE); 263ce42304aSGreg Roach 264ce42304aSGreg Roach $this->recordQuery($query, 'o_gedcom', $params); 265ce42304aSGreg Roach 266ce42304aSGreg Roach return $query->pluck('o_id'); 267ce42304aSGreg Roach } 268ce42304aSGreg Roach 269ce42304aSGreg Roach /** 270ce42304aSGreg Roach * A list of all records that need examining. This may include records 271ce42304aSGreg Roach * that do not need updating, if we can't detect this quickly using SQL. 272ce42304aSGreg Roach * 273ce42304aSGreg Roach * @param Tree $tree 274ce42304aSGreg Roach * @param array<string,string> $params 275ce42304aSGreg Roach * 276ce42304aSGreg Roach * @return Collection<string>|null 277ce42304aSGreg Roach */ 278ce42304aSGreg Roach protected function sourcesToFix(Tree $tree, array $params): ?Collection 279ce42304aSGreg Roach { 280ce42304aSGreg Roach if ($params['type'] !== Source::RECORD_TYPE || $params['search'] === '') { 281ce42304aSGreg Roach return null; 282ce42304aSGreg Roach } 283ce42304aSGreg Roach 2847684867eSGreg Roach $query = $this->sourcesToFixQuery($tree, $params); 285ce42304aSGreg Roach 286ce42304aSGreg Roach $this->recordQuery($query, 's_gedcom', $params); 287ce42304aSGreg Roach 288ce42304aSGreg Roach return $query->pluck('s_id'); 289ce42304aSGreg Roach } 290ce42304aSGreg Roach 291ce42304aSGreg Roach /** 292ce42304aSGreg Roach * A list of all records that need examining. This may include records 293ce42304aSGreg Roach * that do not need updating, if we can't detect this quickly using SQL. 294ce42304aSGreg Roach * 295ce42304aSGreg Roach * @param Tree $tree 296ce42304aSGreg Roach * @param array<string,string> $params 297ce42304aSGreg Roach * 298ce42304aSGreg Roach * @return Collection<string>|null 299ce42304aSGreg Roach */ 300ce42304aSGreg Roach protected function submittersToFix(Tree $tree, array $params): ?Collection 301ce42304aSGreg Roach { 302ce42304aSGreg Roach if ($params['type'] !== Submitter::RECORD_TYPE || $params['search'] === '') { 303ce42304aSGreg Roach return null; 304ce42304aSGreg Roach } 305ce42304aSGreg Roach 3067684867eSGreg Roach $query = $this->submittersToFixQuery($tree, $params); 307ce42304aSGreg Roach 308ce42304aSGreg Roach $this->recordQuery($query, 'o_gedcom', $params); 309ce42304aSGreg Roach 310ce42304aSGreg Roach return $query->pluck('o_id'); 311ce42304aSGreg Roach } 312ce42304aSGreg Roach 313ce42304aSGreg Roach /** 314ce42304aSGreg Roach * Does a record need updating? 315ce42304aSGreg Roach * 316ce42304aSGreg Roach * @param GedcomRecord $record 317ce42304aSGreg Roach * @param array<string,string> $params 318ce42304aSGreg Roach * 319ce42304aSGreg Roach * @return bool 320ce42304aSGreg Roach */ 321ce42304aSGreg Roach public function doesRecordNeedUpdate(GedcomRecord $record, array $params): bool 322ce42304aSGreg Roach { 323ce42304aSGreg Roach return preg_match($this->createRegex($params), $record->gedcom()) === 1; 324ce42304aSGreg Roach } 325ce42304aSGreg Roach 326ce42304aSGreg Roach /** 327ce42304aSGreg Roach * Show the changes we would make 328ce42304aSGreg Roach * 329ce42304aSGreg Roach * @param GedcomRecord $record 330ce42304aSGreg Roach * @param array<string,string> $params 331ce42304aSGreg Roach * 332ce42304aSGreg Roach * @return string 333ce42304aSGreg Roach */ 334ce42304aSGreg Roach public function previewUpdate(GedcomRecord $record, array $params): string 335ce42304aSGreg Roach { 336ce42304aSGreg Roach $old = $record->gedcom(); 337ce42304aSGreg Roach $new = $this->updateGedcom($record, $params); 338ce42304aSGreg Roach 339ce42304aSGreg Roach return $this->data_fix_service->gedcomDiff($record->tree(), $old, $new); 340ce42304aSGreg Roach } 341ce42304aSGreg Roach 342ce42304aSGreg Roach /** 343ce42304aSGreg Roach * Fix a record 344ce42304aSGreg Roach * 345ce42304aSGreg Roach * @param GedcomRecord $record 346ce42304aSGreg Roach * @param array<string,string> $params 347ce42304aSGreg Roach * 348ce42304aSGreg Roach * @return void 349ce42304aSGreg Roach */ 350ce42304aSGreg Roach public function updateRecord(GedcomRecord $record, array $params): void 351ce42304aSGreg Roach { 352ce42304aSGreg Roach $record->updateRecord($this->updateGedcom($record, $params), false); 353ce42304aSGreg Roach } 354ce42304aSGreg Roach 355ce42304aSGreg Roach /** 356ce42304aSGreg Roach * @param GedcomRecord $record 357ce42304aSGreg Roach * @param array<string,string> $params 358ce42304aSGreg Roach * 359ce42304aSGreg Roach * @return string 360ce42304aSGreg Roach */ 361ce42304aSGreg Roach private function updateGedcom(GedcomRecord $record, array $params): string 362ce42304aSGreg Roach { 363ce42304aSGreg Roach // Allow "\n" to indicate a line-feed in replacement text. 364ce42304aSGreg Roach // Back-references such as $1, $2 are handled automatically. 36549d0de55SGreg Roach $replace = strtr($params['replace'], ['\n' => "\n"]); 366ce42304aSGreg Roach 367ce42304aSGreg Roach $regex = $this->createRegex($params); 368ce42304aSGreg Roach 369ce42304aSGreg Roach return preg_replace($regex, $replace, $record->gedcom()); 370ce42304aSGreg Roach } 371ce42304aSGreg Roach 372ce42304aSGreg Roach /** 373ce42304aSGreg Roach * Create a regular expression from the search pattern. 374ce42304aSGreg Roach * 375ce42304aSGreg Roach * @param array<string,string> $params 376ce42304aSGreg Roach * 377ce42304aSGreg Roach * @return string 378ce42304aSGreg Roach */ 379ce42304aSGreg Roach private function createRegex(array $params): string 380ce42304aSGreg Roach { 381ce42304aSGreg Roach $search = $params['search']; 382ce42304aSGreg Roach $method = $params['method']; 383ce42304aSGreg Roach $case = $params['case']; 384ce42304aSGreg Roach 385ce42304aSGreg Roach switch ($method) { 386ce42304aSGreg Roach case 'exact': 387681f0c68SGreg Roach return '/' . preg_quote($search, '/') . '/u' . $case; 388ce42304aSGreg Roach 389ce42304aSGreg Roach case 'words': 390681f0c68SGreg Roach return '/\b' . preg_quote($search, '/') . '\b/u' . $case; 391ce42304aSGreg Roach 392ce42304aSGreg Roach case 'wildcards': 393681f0c68SGreg Roach return '/\b' . strtr(preg_quote($search, '/'), ['\*' => '.*', '\?' => '.']) . '\b/u' . $case; 394ce42304aSGreg Roach 395ce42304aSGreg Roach case 'regex': 396a4f494bbSGreg Roach $regex = '/' . addcslashes($search, '/') . '/u' . $case; 397ce42304aSGreg Roach 398ce42304aSGreg Roach try { 399ce42304aSGreg Roach // A valid regex on an empty string returns zero. 400ce42304aSGreg Roach // An invalid regex on an empty string returns false and throws a warning. 401ce42304aSGreg Roach preg_match($regex, ''); 402ce42304aSGreg Roach } catch (Throwable $ex) { 403ce42304aSGreg Roach $regex = self::INVALID_REGEX; 404ce42304aSGreg Roach } 405ce42304aSGreg Roach 406ce42304aSGreg Roach return $regex; 407ce42304aSGreg Roach } 408ce42304aSGreg Roach 409ce42304aSGreg Roach throw new HttpNotFoundException(); 410ce42304aSGreg Roach } 411ce42304aSGreg Roach 412ce42304aSGreg Roach /** 413ce42304aSGreg Roach * Create a regular expression from the search pattern. 414ce42304aSGreg Roach * 415ce42304aSGreg Roach * @param Builder $query 416ce42304aSGreg Roach * @param string $column 417ce42304aSGreg Roach * @param array<string,string> $params 418ce42304aSGreg Roach * 419ce42304aSGreg Roach * @return void 420ce42304aSGreg Roach */ 421ce42304aSGreg Roach private function recordQuery(Builder $query, string $column, array $params): void 422ce42304aSGreg Roach { 423ce42304aSGreg Roach $search = $params['search']; 424ce42304aSGreg Roach $method = $params['method']; 425b5961194SGreg Roach $like = '%' . addcslashes($search, '\\%_') . '%'; 426ce42304aSGreg Roach 427ce42304aSGreg Roach switch ($method) { 428ce42304aSGreg Roach case 'exact': 429ce42304aSGreg Roach case 'words': 430b5961194SGreg Roach $query->where($column, 'LIKE', $like); 431ce42304aSGreg Roach break; 432ce42304aSGreg Roach 433ce42304aSGreg Roach case 'wildcards': 434ce42304aSGreg Roach $like = strtr($like, ['?' => '_', '*' => '%']); 435b5961194SGreg Roach $query->where($column, 'LIKE', $like); 436ce42304aSGreg Roach break; 437ce42304aSGreg Roach 438ce42304aSGreg Roach case 'regex': 4399a92a0c1SGreg Roach // Substituting newlines seems to be necessary on *some* versions 4409a92a0c1SGreg Roach //.of MySQL (e.g. 5.7), and harmless on others (e.g. 8.0). 4419a92a0c1SGreg Roach $search = strtr($search, ['\n' => "\n"]); 4429a92a0c1SGreg Roach 443ce42304aSGreg Roach switch (DB::connection()->getDriverName()) { 444ce42304aSGreg Roach case 'sqlite': 445ce42304aSGreg Roach case 'mysql': 446ce42304aSGreg Roach $query->where($column, 'REGEXP', $search); 447ce42304aSGreg Roach break; 448ce42304aSGreg Roach 449ce42304aSGreg Roach case 'pgsql': 450ce42304aSGreg Roach $query->where($column, '~', $search); 451ce42304aSGreg Roach break; 452ce42304aSGreg Roach 453ce42304aSGreg Roach case 'sqlsvr': 454ce42304aSGreg Roach // Not available 455ce42304aSGreg Roach break; 456ce42304aSGreg Roach } 457ce42304aSGreg Roach break; 458ce42304aSGreg Roach } 459ce42304aSGreg Roach } 460ce42304aSGreg Roach} 461