1ce42304aSGreg Roach<?php 2ce42304aSGreg Roach 3ce42304aSGreg Roach/** 4ce42304aSGreg Roach * webtrees: online genealogy 55bfc6897SGreg Roach * Copyright (C) 2022 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\Family; 23ce42304aSGreg Roachuse Fisharebest\Webtrees\GedcomRecord; 2481b729d3SGreg Roachuse Fisharebest\Webtrees\Http\Exceptions\HttpNotFoundException; 25ce42304aSGreg Roachuse Fisharebest\Webtrees\I18N; 26ce42304aSGreg Roachuse Fisharebest\Webtrees\Individual; 272da2d0c9SGreg 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 5743f2f523SGreg Roach private DataFixService $data_fix_service; 58ce42304aSGreg Roach 59ce42304aSGreg Roach /** 60ce42304aSGreg Roach * FixMissingDeaths constructor. 61ce42304aSGreg Roach * 62ce42304aSGreg Roach * @param DataFixService $data_fix_service 63ce42304aSGreg Roach */ 64ce42304aSGreg Roach public function __construct(DataFixService $data_fix_service) 65ce42304aSGreg Roach { 66ce42304aSGreg Roach $this->data_fix_service = $data_fix_service; 67ce42304aSGreg Roach } 68ce42304aSGreg Roach 69ce42304aSGreg Roach /** 70ce42304aSGreg Roach * How should this module be identified in the control panel, etc.? 71ce42304aSGreg Roach * 72ce42304aSGreg Roach * @return string 73ce42304aSGreg Roach */ 74ce42304aSGreg Roach public function title(): string 75ce42304aSGreg Roach { 76ce42304aSGreg Roach /* I18N: Name of a module */ 77ce42304aSGreg Roach return I18N::translate('Search and replace'); 78ce42304aSGreg Roach } 79ce42304aSGreg Roach 80ce42304aSGreg Roach /** 81ce42304aSGreg Roach * A sentence describing what this module does. 82ce42304aSGreg Roach * 83ce42304aSGreg Roach * @return string 84ce42304aSGreg Roach */ 85ce42304aSGreg Roach public function description(): string 86ce42304aSGreg Roach { 87ce42304aSGreg Roach /* I18N: Description of a “Data fix” module */ 88ce42304aSGreg Roach return I18N::translate('Search and replace text, using simple searches or advanced pattern matching.'); 89ce42304aSGreg Roach } 90ce42304aSGreg Roach 91ce42304aSGreg Roach /** 92ce42304aSGreg Roach * Options form. 93ce42304aSGreg Roach * 94ce42304aSGreg Roach * @param Tree $tree 95ce42304aSGreg Roach * 96ce42304aSGreg Roach * @return string 97ce42304aSGreg Roach */ 98ce42304aSGreg Roach public function fixOptions(Tree $tree): string 99ce42304aSGreg Roach { 100ce42304aSGreg Roach $methods = [ 101ce42304aSGreg Roach 'exact' => I18N::translate('Match the exact text, even if it occurs in the middle of a word.'), 102ce42304aSGreg Roach 'words' => I18N::translate('Match the exact text, unless it occurs in the middle of a word.'), 103ce42304aSGreg Roach 'wildcards' => I18N::translate('Use a “?” to match a single character, use “*” to match zero or more characters.'), 104ad3143ccSGreg Roach /* I18N: https://en.wikipedia.org/wiki/Regular_expression */ 1052ab7d347SGreg Roach 'regex' => I18N::translate('Regular expression'), 106ce42304aSGreg Roach ]; 107ce42304aSGreg Roach 108ce42304aSGreg Roach $types = [ 109ce42304aSGreg Roach Family::RECORD_TYPE => I18N::translate('Families'), 110ce42304aSGreg Roach Individual::RECORD_TYPE => I18N::translate('Individuals'), 1112da2d0c9SGreg Roach Location::RECORD_TYPE => I18N::translate('Locations'), 112ce42304aSGreg Roach Media::RECORD_TYPE => I18N::translate('Media objects'), 113ce42304aSGreg Roach Note::RECORD_TYPE => I18N::translate('Notes'), 114ce42304aSGreg Roach Repository::RECORD_TYPE => I18N::translate('Repositories'), 115ce42304aSGreg Roach Source::RECORD_TYPE => I18N::translate('Sources'), 116ce42304aSGreg Roach Submitter::RECORD_TYPE => I18N::translate('Submitters'), 117ce42304aSGreg Roach ]; 118ce42304aSGreg Roach 119ce42304aSGreg Roach asort($types); 120ce42304aSGreg Roach 121ce42304aSGreg Roach return view('modules/fix-search-and-replace/options', [ 122ce42304aSGreg Roach 'default_method' => 'exact', 123ce42304aSGreg Roach 'default_type' => Individual::RECORD_TYPE, 124ce42304aSGreg Roach 'methods' => $methods, 125ce42304aSGreg Roach 'types' => $types, 126ce42304aSGreg Roach ]); 127ce42304aSGreg Roach } 128ce42304aSGreg Roach 129ce42304aSGreg Roach /** 130ce42304aSGreg Roach * A list of all records that need examining. This may include records 131ce42304aSGreg Roach * that do not need updating, if we can't detect this quickly using SQL. 132ce42304aSGreg Roach * 133ce42304aSGreg Roach * @param Tree $tree 134ce42304aSGreg Roach * @param array<string,string> $params 135ce42304aSGreg Roach * 13636779af1SGreg Roach * @return Collection<int,string>|null 137ce42304aSGreg Roach */ 138ce42304aSGreg Roach protected function familiesToFix(Tree $tree, array $params): ?Collection 139ce42304aSGreg Roach { 140*748dbe15SGreg Roach if ($params['type'] !== Family::RECORD_TYPE || $params['search-for'] === '') { 141ce42304aSGreg Roach return null; 142ce42304aSGreg Roach } 143ce42304aSGreg Roach 144ce42304aSGreg Roach $query = DB::table('families')->where('f_file', '=', $tree->id()); 145ce42304aSGreg Roach $this->recordQuery($query, 'f_gedcom', $params); 146ce42304aSGreg Roach 147ce42304aSGreg Roach return $query->pluck('f_id'); 148ce42304aSGreg Roach } 149ce42304aSGreg Roach 150ce42304aSGreg Roach /** 151ce42304aSGreg Roach * A list of all records that need examining. This may include records 152ce42304aSGreg Roach * that do not need updating, if we can't detect this quickly using SQL. 153ce42304aSGreg Roach * 154ce42304aSGreg Roach * @param Tree $tree 155ce42304aSGreg Roach * @param array<string,string> $params 156ce42304aSGreg Roach * 15736779af1SGreg Roach * @return Collection<int,string>|null 158ce42304aSGreg Roach */ 159ce42304aSGreg Roach protected function individualsToFix(Tree $tree, array $params): ?Collection 160ce42304aSGreg Roach { 161*748dbe15SGreg Roach if ($params['type'] !== Individual::RECORD_TYPE || $params['search-for'] === '') { 162ce42304aSGreg Roach return null; 163ce42304aSGreg Roach } 164ce42304aSGreg Roach 165ce42304aSGreg Roach $query = DB::table('individuals') 166ce42304aSGreg Roach ->where('i_file', '=', $tree->id()); 167ce42304aSGreg Roach 168ce42304aSGreg Roach $this->recordQuery($query, 'i_gedcom', $params); 169ce42304aSGreg Roach 170ce42304aSGreg Roach return $query->pluck('i_id'); 171ce42304aSGreg Roach } 172ce42304aSGreg Roach 173ce42304aSGreg Roach /** 174ce42304aSGreg Roach * A list of all records that need examining. This may include records 175ce42304aSGreg Roach * that do not need updating, if we can't detect this quickly using SQL. 176ce42304aSGreg Roach * 177ce42304aSGreg Roach * @param Tree $tree 178ce42304aSGreg Roach * @param array<string,string> $params 179ce42304aSGreg Roach * 18036779af1SGreg Roach * @return Collection<int,string>|null 181ce42304aSGreg Roach */ 18292657c8aSGreg Roach protected function locationsToFix(Tree $tree, array $params): ?Collection 18392657c8aSGreg Roach { 184*748dbe15SGreg Roach if ($params['type'] !== Location::RECORD_TYPE || $params['search-for'] === '') { 18592657c8aSGreg Roach return null; 18692657c8aSGreg Roach } 18792657c8aSGreg Roach 18892657c8aSGreg Roach $query = DB::table('other') 18992657c8aSGreg Roach ->where('o_file', '=', $tree->id()) 19092657c8aSGreg Roach ->where('o_type', '=', Location::RECORD_TYPE); 19192657c8aSGreg Roach 19292657c8aSGreg Roach $this->recordQuery($query, 'o_gedcom', $params); 19392657c8aSGreg Roach 19492657c8aSGreg Roach return $query->pluck('o_id'); 19592657c8aSGreg Roach } 19692657c8aSGreg Roach 19792657c8aSGreg Roach /** 19892657c8aSGreg Roach * A list of all records that need examining. This may include records 19992657c8aSGreg Roach * that do not need updating, if we can't detect this quickly using SQL. 20092657c8aSGreg Roach * 20192657c8aSGreg Roach * @param Tree $tree 20292657c8aSGreg Roach * @param array<string,string> $params 20392657c8aSGreg Roach * 20436779af1SGreg Roach * @return Collection<int,string>|null 20592657c8aSGreg Roach */ 206ce42304aSGreg Roach protected function mediaToFix(Tree $tree, array $params): ?Collection 207ce42304aSGreg Roach { 208*748dbe15SGreg Roach if ($params['type'] !== Media::RECORD_TYPE || $params['search-for'] === '') { 209ce42304aSGreg Roach return null; 210ce42304aSGreg Roach } 211ce42304aSGreg Roach 212ce42304aSGreg Roach $query = DB::table('media') 213ce42304aSGreg Roach ->where('m_file', '=', $tree->id()); 214ce42304aSGreg Roach 215ce42304aSGreg Roach $this->recordQuery($query, 'm_gedcom', $params); 216ce42304aSGreg Roach 217ce42304aSGreg Roach return $query->pluck('m_id'); 218ce42304aSGreg Roach } 219ce42304aSGreg Roach 220ce42304aSGreg Roach /** 221ce42304aSGreg Roach * A list of all records that need examining. This may include records 222ce42304aSGreg Roach * that do not need updating, if we can't detect this quickly using SQL. 223ce42304aSGreg Roach * 224ce42304aSGreg Roach * @param Tree $tree 225ce42304aSGreg Roach * @param array<string,string> $params 226ce42304aSGreg Roach * 22736779af1SGreg Roach * @return Collection<int,string>|null 228ce42304aSGreg Roach */ 229ce42304aSGreg Roach protected function notesToFix(Tree $tree, array $params): ?Collection 230ce42304aSGreg Roach { 231*748dbe15SGreg Roach if ($params['type'] !== Note::RECORD_TYPE || $params['search-for'] === '') { 232ce42304aSGreg Roach return null; 233ce42304aSGreg Roach } 234ce42304aSGreg Roach 235ce42304aSGreg Roach $query = DB::table('other') 236ce42304aSGreg Roach ->where('o_file', '=', $tree->id()) 237ce42304aSGreg Roach ->where('o_type', '=', Note::RECORD_TYPE); 238ce42304aSGreg Roach 239ce42304aSGreg Roach $this->recordQuery($query, 'o_gedcom', $params); 240ce42304aSGreg Roach 241ce42304aSGreg Roach return $query->pluck('o_id'); 242ce42304aSGreg Roach } 243ce42304aSGreg Roach 244ce42304aSGreg Roach /** 245ce42304aSGreg Roach * A list of all records that need examining. This may include records 246ce42304aSGreg Roach * that do not need updating, if we can't detect this quickly using SQL. 247ce42304aSGreg Roach * 248ce42304aSGreg Roach * @param Tree $tree 249ce42304aSGreg Roach * @param array<string,string> $params 250ce42304aSGreg Roach * 25136779af1SGreg Roach * @return Collection<int,string>|null 252ce42304aSGreg Roach */ 253ce42304aSGreg Roach protected function repositoriesToFix(Tree $tree, array $params): ?Collection 254ce42304aSGreg Roach { 255*748dbe15SGreg Roach if ($params['type'] !== Repository::RECORD_TYPE || $params['search-for'] === '') { 256ce42304aSGreg Roach return null; 257ce42304aSGreg Roach } 258ce42304aSGreg Roach 259ce42304aSGreg Roach $query = DB::table('other') 260ce42304aSGreg Roach ->where('o_file', '=', $tree->id()) 261ce42304aSGreg Roach ->where('o_type', '=', Repository::RECORD_TYPE); 262ce42304aSGreg Roach 263ce42304aSGreg Roach $this->recordQuery($query, 'o_gedcom', $params); 264ce42304aSGreg Roach 265ce42304aSGreg Roach return $query->pluck('o_id'); 266ce42304aSGreg Roach } 267ce42304aSGreg Roach 268ce42304aSGreg Roach /** 269ce42304aSGreg Roach * A list of all records that need examining. This may include records 270ce42304aSGreg Roach * that do not need updating, if we can't detect this quickly using SQL. 271ce42304aSGreg Roach * 272ce42304aSGreg Roach * @param Tree $tree 273ce42304aSGreg Roach * @param array<string,string> $params 274ce42304aSGreg Roach * 27536779af1SGreg Roach * @return Collection<int,string>|null 276ce42304aSGreg Roach */ 277ce42304aSGreg Roach protected function sourcesToFix(Tree $tree, array $params): ?Collection 278ce42304aSGreg Roach { 279*748dbe15SGreg Roach if ($params['type'] !== Source::RECORD_TYPE || $params['search-for'] === '') { 280ce42304aSGreg Roach return null; 281ce42304aSGreg Roach } 282ce42304aSGreg Roach 2837684867eSGreg Roach $query = $this->sourcesToFixQuery($tree, $params); 284ce42304aSGreg Roach 285ce42304aSGreg Roach $this->recordQuery($query, 's_gedcom', $params); 286ce42304aSGreg Roach 287ce42304aSGreg Roach return $query->pluck('s_id'); 288ce42304aSGreg Roach } 289ce42304aSGreg Roach 290ce42304aSGreg Roach /** 291ce42304aSGreg Roach * A list of all records that need examining. This may include records 292ce42304aSGreg Roach * that do not need updating, if we can't detect this quickly using SQL. 293ce42304aSGreg Roach * 294ce42304aSGreg Roach * @param Tree $tree 295ce42304aSGreg Roach * @param array<string,string> $params 296ce42304aSGreg Roach * 29736779af1SGreg Roach * @return Collection<int,string>|null 298ce42304aSGreg Roach */ 299ce42304aSGreg Roach protected function submittersToFix(Tree $tree, array $params): ?Collection 300ce42304aSGreg Roach { 301*748dbe15SGreg Roach if ($params['type'] !== Submitter::RECORD_TYPE || $params['search-for'] === '') { 302ce42304aSGreg Roach return null; 303ce42304aSGreg Roach } 304ce42304aSGreg Roach 3057684867eSGreg Roach $query = $this->submittersToFixQuery($tree, $params); 306ce42304aSGreg Roach 307ce42304aSGreg Roach $this->recordQuery($query, 'o_gedcom', $params); 308ce42304aSGreg Roach 309ce42304aSGreg Roach return $query->pluck('o_id'); 310ce42304aSGreg Roach } 311ce42304aSGreg Roach 312ce42304aSGreg Roach /** 313ce42304aSGreg Roach * Does a record need updating? 314ce42304aSGreg Roach * 315ce42304aSGreg Roach * @param GedcomRecord $record 316ce42304aSGreg Roach * @param array<string,string> $params 317ce42304aSGreg Roach * 318ce42304aSGreg Roach * @return bool 319ce42304aSGreg Roach */ 320ce42304aSGreg Roach public function doesRecordNeedUpdate(GedcomRecord $record, array $params): bool 321ce42304aSGreg Roach { 322ce42304aSGreg Roach return preg_match($this->createRegex($params), $record->gedcom()) === 1; 323ce42304aSGreg Roach } 324ce42304aSGreg Roach 325ce42304aSGreg Roach /** 326ce42304aSGreg Roach * Show the changes we would make 327ce42304aSGreg Roach * 328ce42304aSGreg Roach * @param GedcomRecord $record 329ce42304aSGreg Roach * @param array<string,string> $params 330ce42304aSGreg Roach * 331ce42304aSGreg Roach * @return string 332ce42304aSGreg Roach */ 333ce42304aSGreg Roach public function previewUpdate(GedcomRecord $record, array $params): string 334ce42304aSGreg Roach { 335ce42304aSGreg Roach $old = $record->gedcom(); 336ce42304aSGreg Roach $new = $this->updateGedcom($record, $params); 337ce42304aSGreg Roach 338ce42304aSGreg Roach return $this->data_fix_service->gedcomDiff($record->tree(), $old, $new); 339ce42304aSGreg Roach } 340ce42304aSGreg Roach 341ce42304aSGreg Roach /** 342ce42304aSGreg Roach * Fix a record 343ce42304aSGreg Roach * 344ce42304aSGreg Roach * @param GedcomRecord $record 345ce42304aSGreg Roach * @param array<string,string> $params 346ce42304aSGreg Roach * 347ce42304aSGreg Roach * @return void 348ce42304aSGreg Roach */ 349ce42304aSGreg Roach public function updateRecord(GedcomRecord $record, array $params): void 350ce42304aSGreg Roach { 351ce42304aSGreg Roach $record->updateRecord($this->updateGedcom($record, $params), false); 352ce42304aSGreg Roach } 353ce42304aSGreg Roach 354ce42304aSGreg Roach /** 355ce42304aSGreg Roach * @param GedcomRecord $record 356ce42304aSGreg Roach * @param array<string,string> $params 357ce42304aSGreg Roach * 358ce42304aSGreg Roach * @return string 359ce42304aSGreg Roach */ 360ce42304aSGreg Roach private function updateGedcom(GedcomRecord $record, array $params): string 361ce42304aSGreg Roach { 362ce42304aSGreg Roach // Allow "\n" to indicate a line-feed in replacement text. 363ce42304aSGreg Roach // Back-references such as $1, $2 are handled automatically. 364*748dbe15SGreg Roach $replace = strtr($params['replace-with'], ['\n' => "\n"]); 365ce42304aSGreg Roach 366ce42304aSGreg Roach $regex = $this->createRegex($params); 367ce42304aSGreg Roach 368ce42304aSGreg Roach return preg_replace($regex, $replace, $record->gedcom()); 369ce42304aSGreg Roach } 370ce42304aSGreg Roach 371ce42304aSGreg Roach /** 372ce42304aSGreg Roach * Create a regular expression from the search pattern. 373ce42304aSGreg Roach * 374ce42304aSGreg Roach * @param array<string,string> $params 375ce42304aSGreg Roach * 376ce42304aSGreg Roach * @return string 377ce42304aSGreg Roach */ 378ce42304aSGreg Roach private function createRegex(array $params): string 379ce42304aSGreg Roach { 380*748dbe15SGreg Roach $search = $params['search-for']; 381ce42304aSGreg Roach $method = $params['method']; 382ce42304aSGreg Roach $case = $params['case']; 383ce42304aSGreg Roach 384ce42304aSGreg Roach switch ($method) { 385ce42304aSGreg Roach case 'exact': 386681f0c68SGreg Roach return '/' . preg_quote($search, '/') . '/u' . $case; 387ce42304aSGreg Roach 388ce42304aSGreg Roach case 'words': 389681f0c68SGreg Roach return '/\b' . preg_quote($search, '/') . '\b/u' . $case; 390ce42304aSGreg Roach 391ce42304aSGreg Roach case 'wildcards': 392681f0c68SGreg Roach return '/\b' . strtr(preg_quote($search, '/'), ['\*' => '.*', '\?' => '.']) . '\b/u' . $case; 393ce42304aSGreg Roach 394ce42304aSGreg Roach case 'regex': 395a4f494bbSGreg Roach $regex = '/' . addcslashes($search, '/') . '/u' . $case; 396ce42304aSGreg Roach 397ce42304aSGreg Roach try { 398ce42304aSGreg Roach // A valid regex on an empty string returns zero. 399ce42304aSGreg Roach // An invalid regex on an empty string returns false and throws a warning. 400ce42304aSGreg Roach preg_match($regex, ''); 401ce42304aSGreg Roach } catch (Throwable $ex) { 402ce42304aSGreg Roach $regex = self::INVALID_REGEX; 403ce42304aSGreg Roach } 404ce42304aSGreg Roach 405ce42304aSGreg Roach return $regex; 406ce42304aSGreg Roach } 407ce42304aSGreg Roach 408ce42304aSGreg Roach throw new HttpNotFoundException(); 409ce42304aSGreg Roach } 410ce42304aSGreg Roach 411ce42304aSGreg Roach /** 412ce42304aSGreg Roach * Create a regular expression from the search pattern. 413ce42304aSGreg Roach * 414ce42304aSGreg Roach * @param Builder $query 415ce42304aSGreg Roach * @param string $column 416ce42304aSGreg Roach * @param array<string,string> $params 417ce42304aSGreg Roach * 418ce42304aSGreg Roach * @return void 419ce42304aSGreg Roach */ 420ce42304aSGreg Roach private function recordQuery(Builder $query, string $column, array $params): void 421ce42304aSGreg Roach { 422*748dbe15SGreg Roach $search = $params['search-for']; 423ce42304aSGreg Roach $method = $params['method']; 424b5961194SGreg Roach $like = '%' . addcslashes($search, '\\%_') . '%'; 425ce42304aSGreg Roach 426ce42304aSGreg Roach switch ($method) { 427ce42304aSGreg Roach case 'exact': 428ce42304aSGreg Roach case 'words': 429b5961194SGreg Roach $query->where($column, 'LIKE', $like); 430ce42304aSGreg Roach break; 431ce42304aSGreg Roach 432ce42304aSGreg Roach case 'wildcards': 433ce42304aSGreg Roach $like = strtr($like, ['?' => '_', '*' => '%']); 434b5961194SGreg Roach $query->where($column, 'LIKE', $like); 435ce42304aSGreg Roach break; 436ce42304aSGreg Roach 437ce42304aSGreg Roach case 'regex': 4389a92a0c1SGreg Roach // Substituting newlines seems to be necessary on *some* versions 439e93aa0bdSGreg Roach // of MySQL (e.g. 5.7), and harmless on others (e.g. 8.0). 4409a92a0c1SGreg Roach $search = strtr($search, ['\n' => "\n"]); 4419a92a0c1SGreg Roach 442ce42304aSGreg Roach switch (DB::connection()->getDriverName()) { 443ce42304aSGreg Roach case 'sqlite': 444ce42304aSGreg Roach case 'mysql': 445ce42304aSGreg Roach $query->where($column, 'REGEXP', $search); 446ce42304aSGreg Roach break; 447ce42304aSGreg Roach 448ce42304aSGreg Roach case 'pgsql': 449ce42304aSGreg Roach $query->where($column, '~', $search); 450ce42304aSGreg Roach break; 451ce42304aSGreg Roach 4527a468185SGreg Roach case 'sqlsrv': 453ce42304aSGreg Roach // Not available 454ce42304aSGreg Roach break; 455ce42304aSGreg Roach } 456ce42304aSGreg Roach break; 457ce42304aSGreg Roach } 458ce42304aSGreg Roach } 459ce42304aSGreg Roach} 460