Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
79.56% |
436 / 548 |
|
52.50% |
21 / 40 |
CRAP | |
0.00% |
0 / 1 |
Upgrade | |
79.56% |
436 / 548 |
|
52.50% |
21 / 40 |
586.49 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
1 | |||
run | |
100.00% |
17 / 17 |
|
100.00% |
1 / 1 |
2 | |||
getNewConfigs | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getWarnings | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
addWarning | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
iniMerge | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
20 | |||
loadOldBaseConfig | |
45.45% |
5 / 11 |
|
0.00% |
0 / 1 |
9.06 | |||
getOldConfigPath | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
4 | |||
loadConfigs | |
100.00% |
16 / 16 |
|
100.00% |
1 / 1 |
6 | |||
applyOldSettings | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
4 | |||
saveModifiedConfig | |
10.00% |
2 / 20 |
|
0.00% |
0 / 1 |
68.05 | |||
saveUnmodifiedConfig | |
14.29% |
2 / 14 |
|
0.00% |
0 / 1 |
48.30 | |||
checkTheme | |
75.00% |
15 / 20 |
|
0.00% |
0 / 1 |
5.39 | |||
isDefaultBulkExportOptions | |
83.33% |
10 / 12 |
|
0.00% |
0 / 1 |
6.17 | |||
checkAmazonConfig | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
3 | |||
upgradeConfig | |
93.58% |
102 / 109 |
|
0.00% |
0 / 1 |
32.27 | |||
upgradeAdminPermissions | |
93.33% |
14 / 15 |
|
0.00% |
0 / 1 |
5.01 | |||
changeArrayKey | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
3 | |||
renameFacet | |
100.00% |
17 / 17 |
|
100.00% |
1 / 1 |
4 | |||
upgradeFacetsAndCollection | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
2 | |||
upgradeAutocompleteName | |
66.67% |
2 / 3 |
|
0.00% |
0 / 1 |
2.15 | |||
upgradeSearches | |
100.00% |
22 / 22 |
|
100.00% |
1 / 1 |
8 | |||
upgradeSpellingSettings | |
72.22% |
13 / 18 |
|
0.00% |
0 / 1 |
12.14 | |||
upgradeFulltext | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
upgradeSitemap | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
upgradeSms | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
upgradeAuthority | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
upgradeReserves | |
60.00% |
3 / 5 |
|
0.00% |
0 / 1 |
3.58 | |||
upgradeSummon | |
92.31% |
12 / 13 |
|
0.00% |
0 / 1 |
5.01 | |||
upgradeSummonPermissions | |
94.12% |
16 / 17 |
|
0.00% |
0 / 1 |
6.01 | |||
upgradePrimo | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
upgradePrimoPermissions | |
90.00% |
27 / 30 |
|
0.00% |
0 / 1 |
10.10 | |||
upgradePrimoServerSettings | |
90.00% |
9 / 10 |
|
0.00% |
0 / 1 |
3.01 | |||
upgradeWorldCat | |
100.00% |
23 / 23 |
|
100.00% |
1 / 1 |
7 | |||
fileContainsMeaningfulLines | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
4 | |||
upgradeSolrMarc | |
16.67% |
2 / 12 |
|
0.00% |
0 / 1 |
35.36 | |||
upgradeSearchSpecs | |
20.00% |
2 / 10 |
|
0.00% |
0 / 1 |
17.80 | |||
upgradeILS | |
33.33% |
5 / 15 |
|
0.00% |
0 / 1 |
26.96 | |||
upgradeShardSettings | |
30.00% |
6 / 20 |
|
0.00% |
0 / 1 |
29.95 | |||
extractComments | |
100.00% |
36 / 36 |
|
100.00% |
1 / 1 |
13 |
1 | <?php |
2 | |
3 | /** |
4 | * VF Configuration Upgrade Tool |
5 | * |
6 | * PHP version 8 |
7 | * |
8 | * Copyright (C) Villanova University 2010. |
9 | * |
10 | * This program is free software; you can redistribute it and/or modify |
11 | * it under the terms of the GNU General Public License version 2, |
12 | * as published by the Free Software Foundation. |
13 | * |
14 | * This program is distributed in the hope that it will be useful, |
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
17 | * GNU General Public License for more details. |
18 | * |
19 | * You should have received a copy of the GNU General Public License |
20 | * along with this program; if not, write to the Free Software |
21 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
22 | * |
23 | * @category VuFind |
24 | * @package Config |
25 | * @author Demian Katz <demian.katz@villanova.edu> |
26 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
27 | * @link https://vufind.org Main Site |
28 | */ |
29 | |
30 | namespace VuFind\Config; |
31 | |
32 | use Composer\Semver\Comparator; |
33 | use VuFind\Config\Writer as ConfigWriter; |
34 | use VuFind\Exception\FileAccess as FileAccessException; |
35 | |
36 | use function count; |
37 | use function in_array; |
38 | use function is_array; |
39 | |
40 | /** |
41 | * Class to upgrade previous VuFind configurations to the current version |
42 | * |
43 | * @category VuFind |
44 | * @package Config |
45 | * @author Demian Katz <demian.katz@villanova.edu> |
46 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
47 | * @link https://vufind.org Main Site |
48 | */ |
49 | class Upgrade |
50 | { |
51 | /** |
52 | * Version we're upgrading from |
53 | * |
54 | * @var string |
55 | */ |
56 | protected $from; |
57 | |
58 | /** |
59 | * Version we're upgrading to |
60 | * |
61 | * @var string |
62 | */ |
63 | protected $to; |
64 | |
65 | /** |
66 | * Directory containing configurations to upgrade |
67 | * |
68 | * @var string |
69 | */ |
70 | protected $oldDir; |
71 | |
72 | /** |
73 | * Directory containing unmodified new configurations |
74 | * |
75 | * @var string |
76 | */ |
77 | protected $rawDir; |
78 | |
79 | /** |
80 | * Directory where new configurations should be written (null for test mode) |
81 | * |
82 | * @var string |
83 | */ |
84 | protected $newDir; |
85 | |
86 | /** |
87 | * Parsed old configurations |
88 | * |
89 | * @var array |
90 | */ |
91 | protected $oldConfigs = []; |
92 | |
93 | /** |
94 | * Processed new configurations |
95 | * |
96 | * @var array |
97 | */ |
98 | protected $newConfigs = []; |
99 | |
100 | /** |
101 | * Comments parsed from configuration files |
102 | * |
103 | * @var array |
104 | */ |
105 | protected $comments = []; |
106 | |
107 | /** |
108 | * Warnings generated during upgrade process |
109 | * |
110 | * @var array |
111 | */ |
112 | protected $warnings = []; |
113 | |
114 | /** |
115 | * Are we upgrading files in place rather than creating them? |
116 | * |
117 | * @var bool |
118 | */ |
119 | protected $inPlaceUpgrade; |
120 | |
121 | /** |
122 | * Have we modified permissions.ini? |
123 | * |
124 | * @var bool |
125 | */ |
126 | protected $permissionsModified = false; |
127 | |
128 | /** |
129 | * Constructor |
130 | * |
131 | * @param string $from Version we're upgrading from. |
132 | * @param string $to Version we're upgrading to. |
133 | * @param string $oldDir Directory containing old configurations. |
134 | * @param string $rawDir Directory containing raw new configurations. |
135 | * @param string $newDir Directory to write updated new configurations into |
136 | * (leave null to disable writes -- used in test mode). |
137 | */ |
138 | public function __construct($from, $to, $oldDir, $rawDir, $newDir = null) |
139 | { |
140 | $this->from = $from; |
141 | $this->to = $to; |
142 | $this->oldDir = $oldDir; |
143 | $this->rawDir = $rawDir; |
144 | $this->newDir = $newDir; |
145 | $this->inPlaceUpgrade = ($this->oldDir == $this->newDir); |
146 | } |
147 | |
148 | /** |
149 | * Run through all of the necessary upgrading. |
150 | * |
151 | * @return void |
152 | */ |
153 | public function run() |
154 | { |
155 | // Load all old configurations: |
156 | $this->loadConfigs(); |
157 | |
158 | // Upgrade them one by one and write the results to disk; order is |
159 | // important since in some cases, settings may migrate out of config.ini |
160 | // and into other files. |
161 | $this->upgradeConfig(); |
162 | $this->upgradeAuthority(); |
163 | $this->upgradeFacetsAndCollection(); |
164 | $this->upgradeFulltext(); |
165 | $this->upgradeReserves(); |
166 | $this->upgradeSearches(); |
167 | $this->upgradeSitemap(); |
168 | $this->upgradeSms(); |
169 | $this->upgradeSummon(); |
170 | $this->upgradePrimo(); |
171 | $this->upgradeWorldCat(); |
172 | |
173 | // The previous upgrade routines may have added values to permissions.ini, |
174 | // so we should save it last. It doesn't have its own upgrade routine. |
175 | $this->saveModifiedConfig('permissions.ini'); |
176 | |
177 | // The following routines load special configurations that were not |
178 | // explicitly loaded by loadConfigs... note that some pieces only apply to |
179 | // the 1.x upgrade! |
180 | if (Comparator::lessThan($this->from, '2.0')) { |
181 | $this->upgradeSolrMarc(); |
182 | $this->upgradeSearchSpecs(); |
183 | } |
184 | $this->upgradeILS(); |
185 | } |
186 | |
187 | /** |
188 | * Get processed configurations (used by test routines). |
189 | * |
190 | * @return array |
191 | */ |
192 | public function getNewConfigs() |
193 | { |
194 | return $this->newConfigs; |
195 | } |
196 | |
197 | /** |
198 | * Get warning strings generated during upgrade process. |
199 | * |
200 | * @return array |
201 | */ |
202 | public function getWarnings() |
203 | { |
204 | return $this->warnings; |
205 | } |
206 | |
207 | /** |
208 | * Add a warning message. |
209 | * |
210 | * @param string $msg Warning message. |
211 | * |
212 | * @return void |
213 | */ |
214 | protected function addWarning($msg) |
215 | { |
216 | $this->warnings[] = $msg; |
217 | } |
218 | |
219 | /** |
220 | * Support function -- merge the contents of two arrays parsed from ini files. |
221 | * |
222 | * @param string $config_ini The base config array. |
223 | * @param string $custom_ini Overrides to apply on top of the base array. |
224 | * |
225 | * @return array The merged results. |
226 | */ |
227 | public static function iniMerge($config_ini, $custom_ini) |
228 | { |
229 | foreach ($custom_ini as $k => $v) { |
230 | // Make a recursive call if we need to merge array values into an |
231 | // existing key... otherwise just drop the value in place. |
232 | if (is_array($v) && isset($config_ini[$k])) { |
233 | $config_ini[$k] = self::iniMerge($config_ini[$k], $custom_ini[$k]); |
234 | } else { |
235 | $config_ini[$k] = $v; |
236 | } |
237 | } |
238 | return $config_ini; |
239 | } |
240 | |
241 | /** |
242 | * Load the old config.ini settings. |
243 | * |
244 | * @return void |
245 | */ |
246 | protected function loadOldBaseConfig() |
247 | { |
248 | // Load the base settings: |
249 | $oldIni = $this->oldDir . '/config.ini'; |
250 | $mainArray = file_exists($oldIni) ? parse_ini_file($oldIni, true) : []; |
251 | |
252 | // Merge in local overrides as needed. VuFind 2 structures configurations |
253 | // differently, so people who used this mechanism will need to refactor |
254 | // their configurations to take advantage of the new "local directory" |
255 | // feature. For now, we'll just merge everything to avoid losing settings. |
256 | if ( |
257 | isset($mainArray['Extra_Config']) |
258 | && isset($mainArray['Extra_Config']['local_overrides']) |
259 | ) { |
260 | $file = trim( |
261 | $this->oldDir . '/' . $mainArray['Extra_Config']['local_overrides'] |
262 | ); |
263 | $localOverride = @parse_ini_file($file, true); |
264 | if ($localOverride) { |
265 | $mainArray = self::iniMerge($mainArray, $localOverride); |
266 | } |
267 | } |
268 | |
269 | // Save the configuration to the appropriate place: |
270 | $this->oldConfigs['config.ini'] = $mainArray; |
271 | } |
272 | |
273 | /** |
274 | * Find the path to the old configuration file. |
275 | * |
276 | * @param string $filename Filename of configuration file. |
277 | * |
278 | * @return string |
279 | */ |
280 | protected function getOldConfigPath($filename) |
281 | { |
282 | // Check if the user has overridden the filename in the [Extra_Config] |
283 | // section: |
284 | $index = str_replace('.ini', '', $filename); |
285 | if (isset($this->oldConfigs['config.ini']['Extra_Config'][$index])) { |
286 | $path = $this->oldDir . '/' |
287 | . $this->oldConfigs['config.ini']['Extra_Config'][$index]; |
288 | if (file_exists($path) && is_file($path)) { |
289 | return $path; |
290 | } |
291 | } |
292 | return $this->oldDir . '/' . $filename; |
293 | } |
294 | |
295 | /** |
296 | * Load all of the user's existing configurations. |
297 | * |
298 | * @return void |
299 | */ |
300 | protected function loadConfigs() |
301 | { |
302 | // Configuration files to load. Note that config.ini must always be loaded |
303 | // first so that getOldConfigPath can work properly! |
304 | $configs = ['config.ini']; |
305 | foreach (glob($this->rawDir . '/*.ini') as $ini) { |
306 | $parts = explode('/', str_replace('\\', '/', $ini)); |
307 | $filename = array_pop($parts); |
308 | if ($filename !== 'config.ini') { |
309 | $configs[] = $filename; |
310 | } |
311 | } |
312 | foreach ($configs as $config) { |
313 | // Special case for config.ini, since we may need to overlay extra |
314 | // settings: |
315 | if ($config == 'config.ini') { |
316 | $this->loadOldBaseConfig(); |
317 | } else { |
318 | $path = $this->getOldConfigPath($config); |
319 | $this->oldConfigs[$config] = file_exists($path) |
320 | ? parse_ini_file($path, true) : []; |
321 | } |
322 | $this->newConfigs[$config] |
323 | = parse_ini_file($this->rawDir . '/' . $config, true); |
324 | $this->comments[$config] |
325 | = $this->extractComments($this->rawDir . '/' . $config); |
326 | } |
327 | } |
328 | |
329 | /** |
330 | * Apply settings from an old configuration to a new configuration. |
331 | * |
332 | * @param string $filename Name of the configuration being updated. |
333 | * @param array $fullSections Array of section names that need to be fully |
334 | * overridden (as opposed to overridden on a setting-by-setting basis). |
335 | * |
336 | * @return void |
337 | */ |
338 | protected function applyOldSettings($filename, $fullSections = []) |
339 | { |
340 | // First override all individual settings: |
341 | foreach ($this->oldConfigs[$filename] as $section => $subsection) { |
342 | foreach ($subsection as $key => $value) { |
343 | $this->newConfigs[$filename][$section][$key] = $value; |
344 | } |
345 | } |
346 | |
347 | // Now override on a section-by-section basis where necessary: |
348 | foreach ($fullSections as $section) { |
349 | $this->newConfigs[$filename][$section] |
350 | = $this->oldConfigs[$filename][$section] ?? []; |
351 | } |
352 | } |
353 | |
354 | /** |
355 | * Save a modified configuration file. |
356 | * |
357 | * @param string $filename Name of config file to write (contents will be |
358 | * pulled from current state of object properties). |
359 | * |
360 | * @throws FileAccessException |
361 | * @return void |
362 | */ |
363 | protected function saveModifiedConfig($filename) |
364 | { |
365 | if (null === $this->newDir) { // skip write if no destination |
366 | return; |
367 | } |
368 | |
369 | // If we're doing an in-place upgrade, and the source file is empty, |
370 | // there is no point in upgrading anything (the file doesn't exist). |
371 | if (empty($this->oldConfigs[$filename]) && $this->inPlaceUpgrade) { |
372 | // Special case: if we set up custom permissions, we need to |
373 | // write the file even if it didn't previously exist. |
374 | if (!$this->permissionsModified || $filename !== 'permissions.ini') { |
375 | return; |
376 | } |
377 | } |
378 | |
379 | // If target file already exists, back it up: |
380 | $outfile = $this->newDir . '/' . $filename; |
381 | $bakfile = $outfile . '.bak.' . time(); |
382 | if (file_exists($outfile) && !copy($outfile, $bakfile)) { |
383 | throw new FileAccessException( |
384 | "Error: Could not copy {$outfile} to {$bakfile}." |
385 | ); |
386 | } |
387 | |
388 | $writer = new ConfigWriter( |
389 | $outfile, |
390 | $this->newConfigs[$filename], |
391 | $this->comments[$filename] |
392 | ); |
393 | if (!$writer->save()) { |
394 | throw new FileAccessException( |
395 | "Error: Problem writing to {$outfile}." |
396 | ); |
397 | } |
398 | } |
399 | |
400 | /** |
401 | * Save an unmodified configuration file -- copy the old version, unless it is |
402 | * the same as the new version! |
403 | * |
404 | * @param string $filename Path to the old config file |
405 | * |
406 | * @throws FileAccessException |
407 | * @return void |
408 | */ |
409 | protected function saveUnmodifiedConfig($filename) |
410 | { |
411 | if (null === $this->newDir) { // skip write if no destination |
412 | return; |
413 | } |
414 | |
415 | if ($this->inPlaceUpgrade) { // skip write if doing in-place upgrade |
416 | return; |
417 | } |
418 | |
419 | // Figure out directories for all versions of this config file: |
420 | $src = $this->getOldConfigPath($filename); |
421 | $raw = $this->rawDir . '/' . $filename; |
422 | $dest = $this->newDir . '/' . $filename; |
423 | |
424 | // Compare the source file against the raw file; if they happen to be the |
425 | // same, we don't need to copy anything! |
426 | if ( |
427 | file_exists($src) && file_exists($raw) |
428 | && md5(file_get_contents($src)) === md5(file_get_contents($raw)) |
429 | ) { |
430 | return; |
431 | } |
432 | |
433 | // If we got this far, we need to copy the user's file into place: |
434 | if (file_exists($src) && !copy($src, $dest)) { |
435 | throw new FileAccessException( |
436 | "Error: Could not copy {$src} to {$dest}." |
437 | ); |
438 | } |
439 | } |
440 | |
441 | /** |
442 | * Check for invalid theme setting. |
443 | * |
444 | * @param string $setting Name of setting in [Site] section to check. |
445 | * @param string $default Default value to use if invalid option was found. |
446 | * |
447 | * @return void |
448 | */ |
449 | protected function checkTheme($setting, $default = null) |
450 | { |
451 | // If a setting is not set, there is nothing to check: |
452 | $theme = $this->newConfigs['config.ini']['Site'][$setting] ?? null; |
453 | if (empty($theme)) { |
454 | return; |
455 | } |
456 | |
457 | $parts = explode(',', $theme); |
458 | $theme = trim($parts[0]); |
459 | |
460 | if ( |
461 | !file_exists(APPLICATION_PATH . '/themes/' . $theme) |
462 | || !is_dir(APPLICATION_PATH . '/themes/' . $theme) |
463 | ) { |
464 | if ($default === null) { |
465 | $this->addWarning( |
466 | "WARNING: This version of VuFind does not support the {$theme} " |
467 | . "theme. As such, we have disabled your {$setting} setting." |
468 | ); |
469 | unset($this->newConfigs['config.ini']['Site'][$setting]); |
470 | } else { |
471 | $this->addWarning( |
472 | 'WARNING: This version of VuFind does not support ' |
473 | . "the {$theme} theme. Your config.ini [Site] {$setting} setting" |
474 | . " has been reset to the default: {$default}. You may need to " |
475 | . 'reimplement your custom theme.' |
476 | ); |
477 | $this->newConfigs['config.ini']['Site'][$setting] = $default; |
478 | } |
479 | } |
480 | } |
481 | |
482 | /** |
483 | * Is this a default BulkExport options setting? |
484 | * |
485 | * @param string $eo Bulk export options |
486 | * |
487 | * @return bool |
488 | */ |
489 | protected function isDefaultBulkExportOptions($eo) |
490 | { |
491 | if (Comparator::greaterThanOrEqualTo($this->from, '2.4')) { |
492 | $default = 'MARC:MARCXML:EndNote:EndNoteWeb:RefWorks:BibTeX:RIS'; |
493 | } elseif (Comparator::greaterThanOrEqualTo($this->from, '2.0')) { |
494 | $default = 'MARC:MARCXML:EndNote:EndNoteWeb:RefWorks:BibTeX'; |
495 | } elseif (Comparator::greaterThanOrEqualTo($this->from, '1.4')) { |
496 | $default = 'MARC:MARCXML:EndNote:RefWorks:BibTeX'; |
497 | } elseif (Comparator::greaterThanOrEqualTo($this->from, '1.3')) { |
498 | $default = 'MARC:EndNote:RefWorks:BibTeX'; |
499 | } elseif (Comparator::greaterThanOrEqualTo($this->from, '1.2')) { |
500 | $default = 'MARC:EndNote:BibTeX'; |
501 | } else { |
502 | $default = 'MARC:EndNote'; |
503 | } |
504 | return $eo == $default; |
505 | } |
506 | |
507 | /** |
508 | * Add warnings if Amazon problems were found. |
509 | * |
510 | * @param array $config Configuration to check |
511 | * |
512 | * @return void |
513 | */ |
514 | protected function checkAmazonConfig($config) |
515 | { |
516 | // Warn the user if they have Amazon enabled but do not have the appropriate |
517 | // credentials set up. |
518 | $hasAmazonReview = stristr($config['Content']['reviews'] ?? '', 'amazon'); |
519 | $hasAmazonCover = stristr($config['Content']['coverimages'] ?? '', 'amazon'); |
520 | if ($hasAmazonReview || $hasAmazonCover) { |
521 | $this->addWarning( |
522 | 'WARNING: You have Amazon content enabled, but VuFind no longer ' |
523 | . 'supports it. You should remove Amazon references from config.ini.' |
524 | ); |
525 | } |
526 | } |
527 | |
528 | /** |
529 | * Upgrade config.ini. |
530 | * |
531 | * @throws FileAccessException |
532 | * @return void |
533 | */ |
534 | protected function upgradeConfig() |
535 | { |
536 | // override new version's defaults with matching settings from old version: |
537 | $this->applyOldSettings('config.ini'); |
538 | |
539 | // Set up reference for convenience (and shorter lines): |
540 | $newConfig = & $this->newConfigs['config.ini']; |
541 | |
542 | // If the [BulkExport] options setting is present and non-default, warn |
543 | // the user about its deprecation. |
544 | if (isset($newConfig['BulkExport']['options'])) { |
545 | $default = $this->isDefaultBulkExportOptions( |
546 | $newConfig['BulkExport']['options'] |
547 | ); |
548 | if (!$default) { |
549 | $this->addWarning( |
550 | 'The [BulkExport] options setting is deprecated; please ' |
551 | . 'customize the [Export] section instead.' |
552 | ); |
553 | } |
554 | unset($newConfig['BulkExport']['options']); |
555 | } |
556 | |
557 | // If [Statistics] is present, warn the user about its removal. |
558 | if (isset($newConfig['Statistics'])) { |
559 | $this->addWarning( |
560 | 'The Statistics module has been removed from VuFind. ' . |
561 | 'For usage tracking, please configure Google Analytics or Matomo.' |
562 | ); |
563 | unset($newConfig['Statistics']); |
564 | } |
565 | |
566 | // Warn the user about Amazon configuration issues: |
567 | $this->checkAmazonConfig($newConfig); |
568 | |
569 | // Warn the user if they have enabled a deprecated Google API: |
570 | if (isset($newConfig['GoogleSearch'])) { |
571 | unset($newConfig['GoogleSearch']); |
572 | $this->addWarning( |
573 | 'The [GoogleSearch] section of config.ini is no ' |
574 | . 'longer supported due to changes in Google APIs.' |
575 | ); |
576 | } |
577 | if ( |
578 | isset($newConfig['Content']['recordMap']) |
579 | && 'google' == strtolower($newConfig['Content']['recordMap']) |
580 | ) { |
581 | unset($newConfig['Content']['recordMap']); |
582 | unset($newConfig['Content']['googleMapApiKey']); |
583 | $this->addWarning( |
584 | 'Google Maps is no longer a supported Content/recordMap option;' |
585 | . ' please review your config.ini.' |
586 | ); |
587 | } |
588 | if (isset($newConfig['GoogleAnalytics']['apiKey'])) { |
589 | if ( |
590 | !isset($newConfig['GoogleAnalytics']['universal']) |
591 | || !$newConfig['GoogleAnalytics']['universal'] |
592 | ) { |
593 | $this->addWarning( |
594 | 'The [GoogleAnalytics] universal setting is off. See config.ini ' |
595 | . 'for important information on how to upgrade your Analytics.' |
596 | ); |
597 | } |
598 | } |
599 | |
600 | // Upgrade CAPTCHA Options |
601 | $legacySettingsMap = [ |
602 | 'publicKey' => 'recaptcha_siteKey', |
603 | 'siteKey' => 'recaptcha_siteKey', |
604 | 'privateKey' => 'recaptcha_secretKey', |
605 | 'secretKey' => 'recaptcha_secretKey', |
606 | 'theme' => 'recaptcha_theme', |
607 | ]; |
608 | $foundRecaptcha = false; |
609 | foreach ($legacySettingsMap as $old => $new) { |
610 | if (isset($newConfig['Captcha'][$old])) { |
611 | $newConfig['Captcha'][$new] |
612 | = $newConfig['Captcha'][$old]; |
613 | unset($newConfig['Captcha'][$old]); |
614 | } |
615 | if (isset($newConfig['Captcha'][$new])) { |
616 | $foundRecaptcha = true; |
617 | } |
618 | } |
619 | if ($foundRecaptcha && !isset($newConfig['Captcha']['types'])) { |
620 | $newConfig['Captcha']['types'] = ['recaptcha']; |
621 | } |
622 | |
623 | // Warn the user about deprecated WorldCat settings: |
624 | if (isset($newConfig['WorldCat']['LimitCodes'])) { |
625 | unset($newConfig['WorldCat']['LimitCodes']); |
626 | $this->addWarning( |
627 | 'The [WorldCat] LimitCodes setting never had any effect and has been' |
628 | . ' removed.' |
629 | ); |
630 | } |
631 | $badKeys |
632 | = ['id', 'xISBN_token', 'xISBN_secret', 'xISSN_token', 'xISSN_secret']; |
633 | foreach ($badKeys as $key) { |
634 | if (isset($newConfig['WorldCat'][$key])) { |
635 | unset($newConfig['WorldCat'][$key]); |
636 | $this->addWarning( |
637 | 'The [WorldCat] ' . $key . ' setting is no longer used and' |
638 | . ' has been removed.' |
639 | ); |
640 | } |
641 | } |
642 | if ( |
643 | isset($newConfig['Record']['related']) |
644 | && in_array('Editions', $newConfig['Record']['related']) |
645 | ) { |
646 | $newConfig['Record']['related'] = array_diff( |
647 | $newConfig['Record']['related'], |
648 | ['Editions'] |
649 | ); |
650 | $this->addWarning( |
651 | 'The Editions related record module is no longer ' |
652 | . 'supported due to OCLC\'s xID API shutdown.' |
653 | . ' It has been removed from your settings.' |
654 | ); |
655 | } |
656 | |
657 | // Upgrade Google Options: |
658 | if ( |
659 | isset($newConfig['Content']['GoogleOptions']) |
660 | && !is_array($newConfig['Content']['GoogleOptions']) |
661 | ) { |
662 | $newConfig['Content']['GoogleOptions'] |
663 | = ['link' => $newConfig['Content']['GoogleOptions']]; |
664 | } |
665 | |
666 | // Disable unused, obsolete setting: |
667 | unset($newConfig['Index']['local']); |
668 | |
669 | // Warn the user if they are using an unsupported theme: |
670 | $this->checkTheme('theme', 'bootprint3'); |
671 | $this->checkTheme('mobile_theme', null); |
672 | |
673 | // Translate legacy auth settings: |
674 | if (strtolower($newConfig['Authentication']['method']) == 'db') { |
675 | $newConfig['Authentication']['method'] = 'Database'; |
676 | } |
677 | if (strtolower($newConfig['Authentication']['method']) == 'sip') { |
678 | $newConfig['Authentication']['method'] = 'SIP2'; |
679 | } |
680 | |
681 | // Translate legacy session settings: |
682 | $newConfig['Session']['type'] = ucwords( |
683 | str_replace('session', '', strtolower($newConfig['Session']['type'])) |
684 | ); |
685 | if ($newConfig['Session']['type'] == 'Mysql') { |
686 | $newConfig['Session']['type'] = 'Database'; |
687 | } |
688 | |
689 | // Eliminate obsolete database settings: |
690 | $newConfig['Database'] |
691 | = ['database' => $newConfig['Database']['database']]; |
692 | |
693 | // Eliminate obsolete config override settings: |
694 | unset($newConfig['Extra_Config']); |
695 | |
696 | // Update generator if it contains a version number: |
697 | if ( |
698 | isset($newConfig['Site']['generator']) |
699 | && preg_match('/^VuFind (\d+\.?)+$/', $newConfig['Site']['generator']) |
700 | ) { |
701 | $newConfig['Site']['generator'] = 'VuFind ' . $this->to; |
702 | } |
703 | |
704 | // Update Syndetics config: |
705 | if (isset($newConfig['Syndetics']['url'])) { |
706 | $newConfig['Syndetics']['use_ssl'] |
707 | = (!str_contains($newConfig['Syndetics']['url'], 'https://')) |
708 | ? '' : 1; |
709 | unset($newConfig['Syndetics']['url']); |
710 | } |
711 | |
712 | // Convert spellchecker 'simple' option |
713 | if ( |
714 | // If 'simple' is set |
715 | isset($newConfig['Spelling']['simple']) && |
716 | // and 'dictionaries' is set to default |
717 | ($newConfig['Spelling']['dictionaries'] == ['default', 'basicSpell']) |
718 | ) { |
719 | $newConfig['Spelling']['dictionaries'] = $newConfig['Spelling']['simple'] |
720 | ? ['basicSpell'] : ['default', 'basicSpell']; |
721 | } |
722 | unset($newConfig['Spelling']['simple']); |
723 | |
724 | // Translate obsolete permission settings: |
725 | $this->upgradeAdminPermissions(); |
726 | |
727 | // Deal with shard settings (which may have to be moved to another file): |
728 | $this->upgradeShardSettings(); |
729 | |
730 | // save the file |
731 | $this->saveModifiedConfig('config.ini'); |
732 | } |
733 | |
734 | /** |
735 | * Translate obsolete permission settings. |
736 | * |
737 | * @return void |
738 | */ |
739 | protected function upgradeAdminPermissions() |
740 | { |
741 | $config = & $this->newConfigs['config.ini']; |
742 | $permissions = & $this->newConfigs['permissions.ini']; |
743 | |
744 | if (isset($config['AdminAuth'])) { |
745 | $permissions['access.AdminModule'] = []; |
746 | if (isset($config['AdminAuth']['ipRegEx'])) { |
747 | $permissions['access.AdminModule']['ipRegEx'] |
748 | = $config['AdminAuth']['ipRegEx']; |
749 | } |
750 | if (isset($config['AdminAuth']['userWhitelist'])) { |
751 | $permissions['access.AdminModule']['username'] |
752 | = $config['AdminAuth']['userWhitelist']; |
753 | } |
754 | // If no settings exist in config.ini, we grant access to everyone |
755 | // by allowing both logged-in and logged-out roles. |
756 | if (empty($permissions['access.AdminModule'])) { |
757 | $permissions['access.AdminModule']['role'] = ['guest', 'loggedin']; |
758 | } |
759 | $permissions['access.AdminModule']['permission'] = 'access.AdminModule'; |
760 | $this->permissionsModified = true; |
761 | |
762 | // Remove any old settings remaining in config.ini: |
763 | unset($config['AdminAuth']); |
764 | } |
765 | } |
766 | |
767 | /** |
768 | * Change an array key. |
769 | * |
770 | * @param array $array Array to rewrite |
771 | * @param string $old Old key name |
772 | * @param string $new New key name |
773 | * |
774 | * @return array |
775 | */ |
776 | protected function changeArrayKey($array, $old, $new) |
777 | { |
778 | $newArr = []; |
779 | foreach ($array as $k => $v) { |
780 | if ($k === $old) { |
781 | $k = $new; |
782 | } |
783 | $newArr[$k] = $v; |
784 | } |
785 | return $newArr; |
786 | } |
787 | |
788 | /** |
789 | * Support method for upgradeFacetsAndCollection() - change the name of |
790 | * a facet field. |
791 | * |
792 | * @param string $old Old field name |
793 | * @param string $new New field name |
794 | * |
795 | * @return void |
796 | */ |
797 | protected function renameFacet($old, $new) |
798 | { |
799 | $didWork = false; |
800 | if (isset($this->newConfigs['facets.ini']['Results'][$old])) { |
801 | $this->newConfigs['facets.ini']['Results'] = $this->changeArrayKey( |
802 | $this->newConfigs['facets.ini']['Results'], |
803 | $old, |
804 | $new |
805 | ); |
806 | $didWork = true; |
807 | } |
808 | if (isset($this->newConfigs['Collection.ini']['Facets'][$old])) { |
809 | $this->newConfigs['Collection.ini']['Facets'] = $this->changeArrayKey( |
810 | $this->newConfigs['Collection.ini']['Facets'], |
811 | $old, |
812 | $new |
813 | ); |
814 | $didWork = true; |
815 | } |
816 | if ($didWork) { |
817 | $this->newConfigs['facets.ini']['LegacyFields'][$old] = $new; |
818 | } |
819 | } |
820 | |
821 | /** |
822 | * Upgrade facets.ini and Collection.ini (since these are tied together). |
823 | * |
824 | * @throws FileAccessException |
825 | * @return void |
826 | */ |
827 | protected function upgradeFacetsAndCollection() |
828 | { |
829 | // we want to retain the old installation's various facet groups |
830 | // exactly as-is |
831 | $facetGroups = [ |
832 | 'Results', 'ResultsTop', 'Advanced', 'Author', 'CheckboxFacets', |
833 | 'HomePage', |
834 | ]; |
835 | $this->applyOldSettings('facets.ini', $facetGroups); |
836 | $this->applyOldSettings('Collection.ini', ['Facets', 'Sort']); |
837 | |
838 | // fill in home page facets with advanced facets if missing: |
839 | if (!isset($this->oldConfigs['facets.ini']['HomePage'])) { |
840 | $this->newConfigs['facets.ini']['HomePage'] |
841 | = $this->newConfigs['facets.ini']['Advanced']; |
842 | } |
843 | |
844 | // rename changed facets |
845 | $this->renameFacet('authorStr', 'author_facet'); |
846 | |
847 | // save the file |
848 | $this->saveModifiedConfig('facets.ini'); |
849 | $this->saveModifiedConfig('Collection.ini'); |
850 | } |
851 | |
852 | /** |
853 | * Update an old VuFind 1.x-style autocomplete handler name to the new style. |
854 | * |
855 | * @param string $name Name of module. |
856 | * |
857 | * @return string |
858 | */ |
859 | protected function upgradeAutocompleteName($name) |
860 | { |
861 | if ($name == 'NoAutocomplete') { |
862 | return 'None'; |
863 | } |
864 | return str_replace('Autocomplete', '', $name); |
865 | } |
866 | |
867 | /** |
868 | * Upgrade searches.ini. |
869 | * |
870 | * @throws FileAccessException |
871 | * @return void |
872 | */ |
873 | protected function upgradeSearches() |
874 | { |
875 | // we want to retain the old installation's Basic/Advanced search settings |
876 | // and sort settings exactly as-is |
877 | $groups = [ |
878 | 'Basic_Searches', 'Advanced_Searches', 'Sorting', 'DefaultSortingByType', |
879 | ]; |
880 | $this->applyOldSettings('searches.ini', $groups); |
881 | |
882 | // Fix autocomplete settings in case they use the old style: |
883 | $newConfig = & $this->newConfigs['searches.ini']; |
884 | if (isset($newConfig['Autocomplete']['default_handler'])) { |
885 | $newConfig['Autocomplete']['default_handler'] |
886 | = $this->upgradeAutocompleteName( |
887 | $newConfig['Autocomplete']['default_handler'] |
888 | ); |
889 | } |
890 | if (isset($newConfig['Autocomplete_Types'])) { |
891 | foreach ($newConfig['Autocomplete_Types'] as $k => $v) { |
892 | $parts = explode(':', $v); |
893 | $parts[0] = $this->upgradeAutocompleteName($parts[0]); |
894 | $newConfig['Autocomplete_Types'][$k] = implode(':', $parts); |
895 | } |
896 | } |
897 | |
898 | // fix call number sort settings: |
899 | if (isset($newConfig['Sorting']['callnumber'])) { |
900 | $newConfig['Sorting']['callnumber-sort'] |
901 | = $newConfig['Sorting']['callnumber']; |
902 | unset($newConfig['Sorting']['callnumber']); |
903 | } |
904 | if (isset($newConfig['DefaultSortingByType'])) { |
905 | foreach ($newConfig['DefaultSortingByType'] as & $v) { |
906 | if ($v === 'callnumber') { |
907 | $v = 'callnumber-sort'; |
908 | } |
909 | } |
910 | } |
911 | $this->upgradeSpellingSettings('searches.ini', ['CallNumber', 'WorkKeys']); |
912 | |
913 | // save the file |
914 | $this->saveModifiedConfig('searches.ini'); |
915 | } |
916 | |
917 | /** |
918 | * Upgrade spelling settings to account for refactoring of spelling as a |
919 | * recommendation module starting in release 2.4. |
920 | * |
921 | * @param string $ini .ini file to modify |
922 | * @param array $skip Keys to skip within [TopRecommendations] |
923 | * |
924 | * @return void |
925 | */ |
926 | protected function upgradeSpellingSettings($ini, $skip = []) |
927 | { |
928 | // Turn on the spelling recommendations if we're upgrading from a version |
929 | // prior to 2.4. |
930 | if (Comparator::lessThan($this->from, '2.4')) { |
931 | // Fix defaults in general section: |
932 | $cfg = & $this->newConfigs[$ini]['General']; |
933 | $keys = ['default_top_recommend', 'default_noresults_recommend']; |
934 | foreach ($keys as $key) { |
935 | if (!isset($cfg[$key])) { |
936 | $cfg[$key] = []; |
937 | } |
938 | if (!in_array('SpellingSuggestions', $cfg[$key])) { |
939 | $cfg[$key][] = 'SpellingSuggestions'; |
940 | } |
941 | } |
942 | |
943 | // Fix settings in [TopRecommendations] |
944 | $cfg = & $this->newConfigs[$ini]['TopRecommendations']; |
945 | // Add SpellingSuggestions to all non-skipped handlers: |
946 | foreach ($cfg as $key => & $value) { |
947 | if ( |
948 | !in_array($key, $skip) |
949 | && !in_array('SpellingSuggestions', $value) |
950 | ) { |
951 | $value[] = 'SpellingSuggestions'; |
952 | } |
953 | } |
954 | // Define handlers with no spelling support as the default minus the |
955 | // Spelling option: |
956 | foreach ($skip as $key) { |
957 | if (!isset($cfg[$key])) { |
958 | $cfg[$key] = array_diff( |
959 | $this->newConfigs[$ini]['General']['default_top_recommend'], |
960 | ['SpellingSuggestions'] |
961 | ); |
962 | } |
963 | } |
964 | } |
965 | } |
966 | |
967 | /** |
968 | * Upgrade fulltext.ini. |
969 | * |
970 | * @throws FileAccessException |
971 | * @return void |
972 | */ |
973 | protected function upgradeFulltext() |
974 | { |
975 | $this->saveUnmodifiedConfig('fulltext.ini'); |
976 | } |
977 | |
978 | /** |
979 | * Upgrade sitemap.ini. |
980 | * |
981 | * @throws FileAccessException |
982 | * @return void |
983 | */ |
984 | protected function upgradeSitemap() |
985 | { |
986 | $this->saveUnmodifiedConfig('sitemap.ini'); |
987 | } |
988 | |
989 | /** |
990 | * Upgrade sms.ini. |
991 | * |
992 | * @throws FileAccessException |
993 | * @return void |
994 | */ |
995 | protected function upgradeSms() |
996 | { |
997 | $this->applyOldSettings('sms.ini', ['Carriers']); |
998 | $this->saveModifiedConfig('sms.ini'); |
999 | } |
1000 | |
1001 | /** |
1002 | * Upgrade authority.ini. |
1003 | * |
1004 | * @throws FileAccessException |
1005 | * @return void |
1006 | */ |
1007 | protected function upgradeAuthority() |
1008 | { |
1009 | // we want to retain the old installation's search and facet settings |
1010 | // exactly as-is |
1011 | $groups = [ |
1012 | 'Facets', 'Basic_Searches', 'Advanced_Searches', 'Sorting', |
1013 | ]; |
1014 | $this->applyOldSettings('authority.ini', $groups); |
1015 | |
1016 | // save the file |
1017 | $this->saveModifiedConfig('authority.ini'); |
1018 | } |
1019 | |
1020 | /** |
1021 | * Upgrade reserves.ini. |
1022 | * |
1023 | * @throws FileAccessException |
1024 | * @return void |
1025 | */ |
1026 | protected function upgradeReserves() |
1027 | { |
1028 | // If Reserves module is disabled, don't bother updating config: |
1029 | if ( |
1030 | !isset($this->newConfigs['config.ini']['Reserves']['search_enabled']) |
1031 | || !$this->newConfigs['config.ini']['Reserves']['search_enabled'] |
1032 | ) { |
1033 | return; |
1034 | } |
1035 | |
1036 | // we want to retain the old installation's search and facet settings |
1037 | // exactly as-is |
1038 | $groups = [ |
1039 | 'Facets', 'Basic_Searches', 'Advanced_Searches', 'Sorting', |
1040 | ]; |
1041 | $this->applyOldSettings('reserves.ini', $groups); |
1042 | |
1043 | // save the file |
1044 | $this->saveModifiedConfig('reserves.ini'); |
1045 | } |
1046 | |
1047 | /** |
1048 | * Upgrade Summon.ini. |
1049 | * |
1050 | * @throws FileAccessException |
1051 | * @return void |
1052 | */ |
1053 | protected function upgradeSummon() |
1054 | { |
1055 | // If Summon is disabled in our current configuration, we don't need to |
1056 | // load any Summon-specific settings: |
1057 | if (!isset($this->newConfigs['config.ini']['Summon']['apiKey'])) { |
1058 | return; |
1059 | } |
1060 | |
1061 | // we want to retain the old installation's search and facet settings |
1062 | // exactly as-is |
1063 | $groups = [ |
1064 | 'Facets', 'FacetsTop', 'Basic_Searches', 'Advanced_Searches', 'Sorting', |
1065 | ]; |
1066 | $this->applyOldSettings('Summon.ini', $groups); |
1067 | |
1068 | // Turn on advanced checkbox facets if we're upgrading from a version |
1069 | // prior to 2.3. |
1070 | if (Comparator::lessThan($this->from, '2.3')) { |
1071 | $cfg = & $this->newConfigs['Summon.ini']['Advanced_Facet_Settings']; |
1072 | $specialFacets = $cfg['special_facets'] ?? null; |
1073 | if (empty($specialFacets)) { |
1074 | $cfg['special_facets'] = 'checkboxes:Summon'; |
1075 | } elseif (!str_contains('checkboxes', (string)$specialFacets)) { |
1076 | $cfg['special_facets'] .= ',checkboxes:Summon'; |
1077 | } |
1078 | } |
1079 | |
1080 | // update permission settings |
1081 | $this->upgradeSummonPermissions(); |
1082 | |
1083 | $this->upgradeSpellingSettings('Summon.ini'); |
1084 | |
1085 | // save the file |
1086 | $this->saveModifiedConfig('Summon.ini'); |
1087 | } |
1088 | |
1089 | /** |
1090 | * Translate obsolete permission settings. |
1091 | * |
1092 | * @return void |
1093 | */ |
1094 | protected function upgradeSummonPermissions() |
1095 | { |
1096 | $config = & $this->newConfigs['Summon.ini']; |
1097 | $permissions = & $this->newConfigs['permissions.ini']; |
1098 | if (isset($config['Auth'])) { |
1099 | $permissions['access.SummonExtendedResults'] = []; |
1100 | if ( |
1101 | isset($config['Auth']['check_login']) |
1102 | && $config['Auth']['check_login'] |
1103 | ) { |
1104 | $permissions['access.SummonExtendedResults']['role'] = ['loggedin']; |
1105 | } |
1106 | if (isset($config['Auth']['ip_range'])) { |
1107 | $permissions['access.SummonExtendedResults']['ipRegEx'] |
1108 | = $config['Auth']['ip_range']; |
1109 | } |
1110 | if (!empty($permissions['access.SummonExtendedResults'])) { |
1111 | $permissions['access.SummonExtendedResults']['boolean'] = 'OR'; |
1112 | $permissions['access.SummonExtendedResults']['permission'] |
1113 | = 'access.SummonExtendedResults'; |
1114 | $this->permissionsModified = true; |
1115 | } else { |
1116 | unset($permissions['access.SummonExtendedResults']); |
1117 | } |
1118 | |
1119 | // Remove any old settings remaining in Summon.ini: |
1120 | unset($config['Auth']); |
1121 | } |
1122 | } |
1123 | |
1124 | /** |
1125 | * Upgrade Primo.ini. |
1126 | * |
1127 | * @throws FileAccessException |
1128 | * @return void |
1129 | */ |
1130 | protected function upgradePrimo() |
1131 | { |
1132 | // we want to retain the old installation's search and facet settings |
1133 | // exactly as-is |
1134 | $groups = [ |
1135 | 'Facets', 'FacetsTop', 'Basic_Searches', 'Advanced_Searches', 'Sorting', |
1136 | ]; |
1137 | $this->applyOldSettings('Primo.ini', $groups); |
1138 | |
1139 | // update permission settings |
1140 | $this->upgradePrimoPermissions(); |
1141 | |
1142 | // update server settings |
1143 | $this->upgradePrimoServerSettings(); |
1144 | |
1145 | // save the file |
1146 | $this->saveModifiedConfig('Primo.ini'); |
1147 | } |
1148 | |
1149 | /** |
1150 | * Translate obsolete permission settings. |
1151 | * |
1152 | * @return void |
1153 | */ |
1154 | protected function upgradePrimoPermissions() |
1155 | { |
1156 | $config = & $this->newConfigs['Primo.ini']; |
1157 | $permissions = & $this->newConfigs['permissions.ini']; |
1158 | if ( |
1159 | isset($config['Institutions']['code']) |
1160 | && isset($config['Institutions']['regex']) |
1161 | ) { |
1162 | $codes = $config['Institutions']['code']; |
1163 | $regex = $config['Institutions']['regex']; |
1164 | if (count($regex) != count($codes)) { |
1165 | $this->addWarning( |
1166 | 'Mismatched code/regex counts in Primo.ini [Institutions].' |
1167 | ); |
1168 | } |
1169 | |
1170 | // Map parallel arrays into code => array of regexes and detect |
1171 | // wildcard regex to treat as default code. |
1172 | $map = []; |
1173 | $default = null; |
1174 | foreach ($codes as $i => $code) { |
1175 | if ($regex[$i] == '/.*/') { |
1176 | $default = $code; |
1177 | } else { |
1178 | $map[$code] = !isset($map[$code]) |
1179 | ? [$regex[$i]] |
1180 | : array_merge($map[$code], [$regex[$i]]); |
1181 | } |
1182 | } |
1183 | foreach ($map as $code => $regexes) { |
1184 | $perm = "access.PrimoInstitution.$code"; |
1185 | $config['Institutions']["onCampusRule['$code']"] = $perm; |
1186 | $permissions[$perm] = [ |
1187 | 'ipRegEx' => count($regexes) == 1 ? $regexes[0] : $regexes, |
1188 | 'permission' => $perm, |
1189 | ]; |
1190 | $this->permissionsModified = true; |
1191 | } |
1192 | if (null !== $default) { |
1193 | $config['Institutions']['defaultCode'] = $default; |
1194 | } |
1195 | |
1196 | // Remove any old settings remaining in Primo.ini: |
1197 | unset($config['Institutions']['code']); |
1198 | unset($config['Institutions']['regex']); |
1199 | } |
1200 | } |
1201 | |
1202 | /** |
1203 | * Translate obsolete server settings. |
1204 | * |
1205 | * @return void |
1206 | */ |
1207 | protected function upgradePrimoServerSettings() |
1208 | { |
1209 | $config = & $this->newConfigs['Primo.ini']; |
1210 | // Convert apiId to url |
1211 | if (isset($config['General']['apiId'])) { |
1212 | $url = 'http://' . $config['General']['apiId'] |
1213 | . '.hosted.exlibrisgroup.com'; |
1214 | if (isset($config['General']['port'])) { |
1215 | $url .= ':' . $config['General']['port']; |
1216 | } else { |
1217 | $url .= ':1701'; |
1218 | } |
1219 | |
1220 | $config['General']['url'] = $url; |
1221 | |
1222 | // Remove any old settings remaining in Primo.ini: |
1223 | unset($config['General']['apiId']); |
1224 | unset($config['General']['port']); |
1225 | } |
1226 | } |
1227 | |
1228 | /** |
1229 | * Upgrade WorldCat.ini. |
1230 | * |
1231 | * @throws FileAccessException |
1232 | * @return void |
1233 | */ |
1234 | protected function upgradeWorldCat() |
1235 | { |
1236 | // If WorldCat is disabled in our current configuration, we don't need to |
1237 | // load any WorldCat-specific settings: |
1238 | if (!isset($this->newConfigs['config.ini']['WorldCat']['apiKey'])) { |
1239 | return; |
1240 | } |
1241 | |
1242 | // we want to retain the old installation's search settings exactly as-is |
1243 | $groups = [ |
1244 | 'Basic_Searches', 'Advanced_Searches', 'Sorting', |
1245 | ]; |
1246 | $this->applyOldSettings('WorldCat.ini', $groups); |
1247 | |
1248 | // we need to fix an obsolete search setting for authors |
1249 | foreach (['Basic_Searches', 'Advanced_Searches'] as $section) { |
1250 | $new = []; |
1251 | foreach ($this->newConfigs['WorldCat.ini'][$section] as $k => $v) { |
1252 | if ($k == 'srw.au:srw.pn:srw.cn') { |
1253 | $k = 'srw.au'; |
1254 | } |
1255 | $new[$k] = $v; |
1256 | } |
1257 | $this->newConfigs['WorldCat.ini'][$section] = $new; |
1258 | } |
1259 | |
1260 | // Deal with deprecated related record module. |
1261 | $newConfig = & $this->newConfigs['WorldCat.ini']; |
1262 | if ( |
1263 | isset($newConfig['Record']['related']) |
1264 | && in_array('WorldCatEditions', $newConfig['Record']['related']) |
1265 | ) { |
1266 | $newConfig['Record']['related'] = array_diff( |
1267 | $newConfig['Record']['related'], |
1268 | ['WorldCatEditions'] |
1269 | ); |
1270 | $this->addWarning( |
1271 | 'The WorldCatEditions related record module is no longer ' |
1272 | . 'supported due to OCLC\'s xID API shutdown.' |
1273 | . ' It has been removed from your settings.' |
1274 | ); |
1275 | } |
1276 | |
1277 | // save the file |
1278 | $this->saveModifiedConfig('WorldCat.ini'); |
1279 | } |
1280 | |
1281 | /** |
1282 | * Does the specified properties file contain any meaningful |
1283 | * (non-empty/non-comment) lines? |
1284 | * |
1285 | * @param string $src File to check |
1286 | * |
1287 | * @return bool |
1288 | */ |
1289 | protected function fileContainsMeaningfulLines($src) |
1290 | { |
1291 | // Does the file contain any meaningful lines? |
1292 | foreach (file($src) as $line) { |
1293 | $line = trim($line); |
1294 | if ('' !== $line && !str_starts_with($line, '#')) { |
1295 | return true; |
1296 | } |
1297 | } |
1298 | return false; |
1299 | } |
1300 | |
1301 | /** |
1302 | * Upgrade SolrMarc configurations. |
1303 | * |
1304 | * @throws FileAccessException |
1305 | * @return void |
1306 | */ |
1307 | protected function upgradeSolrMarc() |
1308 | { |
1309 | if (null === $this->newDir) { // skip this step if no write destination |
1310 | return; |
1311 | } |
1312 | |
1313 | // Is there a marc_local.properties file? |
1314 | $src = realpath($this->oldDir . '/../../import/marc_local.properties'); |
1315 | if (empty($src) || !file_exists($src)) { |
1316 | return; |
1317 | } |
1318 | |
1319 | // Copy the file if it contains customizations: |
1320 | if ($this->fileContainsMeaningfulLines($src)) { |
1321 | $dest = realpath($this->newDir . '/../../import') |
1322 | . '/marc_local.properties'; |
1323 | if (!copy($src, $dest) || !file_exists($dest)) { |
1324 | throw new FileAccessException( |
1325 | "Cannot copy {$src} to {$dest}." |
1326 | ); |
1327 | } |
1328 | } |
1329 | } |
1330 | |
1331 | /** |
1332 | * Upgrade .yaml configurations. |
1333 | * |
1334 | * @throws FileAccessException |
1335 | * @return void |
1336 | */ |
1337 | protected function upgradeSearchSpecs() |
1338 | { |
1339 | if (null === $this->newDir) { // skip this step if no write destination |
1340 | return; |
1341 | } |
1342 | |
1343 | // VuFind 1.x uses *_local.yaml files as overrides; VuFind 2.x uses files |
1344 | // with the same filename in the local directory. Copy any old override |
1345 | // files into the new expected location: |
1346 | $files = ['searchspecs', 'authsearchspecs', 'reservessearchspecs']; |
1347 | foreach ($files as $file) { |
1348 | $old = $this->oldDir . '/' . $file . '_local.yaml'; |
1349 | $new = $this->newDir . '/' . $file . '.yaml'; |
1350 | if (file_exists($old)) { |
1351 | if (!copy($old, $new)) { |
1352 | throw new FileAccessException( |
1353 | "Cannot copy {$old} to {$new}." |
1354 | ); |
1355 | } |
1356 | } |
1357 | } |
1358 | } |
1359 | |
1360 | /** |
1361 | * Upgrade ILS driver configuration. |
1362 | * |
1363 | * @throws FileAccessException |
1364 | * @return void |
1365 | */ |
1366 | protected function upgradeILS() |
1367 | { |
1368 | $driver = $this->newConfigs['config.ini']['Catalog']['driver'] ?? ''; |
1369 | if (empty($driver)) { |
1370 | $this->addWarning('WARNING: Could not find ILS driver setting.'); |
1371 | } elseif ('Sample' == $driver) { |
1372 | // No configuration file for Sample driver |
1373 | } elseif ('AdminScripts' == $driver) { |
1374 | // Prevent abuse if upgrade process is hijacked |
1375 | } elseif (!file_exists($this->oldDir . '/' . $driver . '.ini')) { |
1376 | $this->addWarning( |
1377 | "WARNING: Could not find {$driver}.ini file; " |
1378 | . 'check your ILS driver configuration.' |
1379 | ); |
1380 | } else { |
1381 | $this->saveUnmodifiedConfig($driver . '.ini'); |
1382 | } |
1383 | |
1384 | // If we're set to load NoILS.ini on failure, copy that over as well: |
1385 | if ( |
1386 | isset($this->newConfigs['config.ini']['Catalog']['loadNoILSOnFailure']) |
1387 | && $this->newConfigs['config.ini']['Catalog']['loadNoILSOnFailure'] |
1388 | ) { |
1389 | // If NoILS is also the main driver, we don't need to copy it twice: |
1390 | if ($driver != 'NoILS') { |
1391 | $this->saveUnmodifiedConfig('NoILS.ini'); |
1392 | } |
1393 | } |
1394 | } |
1395 | |
1396 | /** |
1397 | * Upgrade shard settings (they have moved to a different config file, so |
1398 | * this is handled as a separate method so that all affected settings are |
1399 | * addressed in one place. |
1400 | * |
1401 | * This gets called from updateConfig(), which gets called before other |
1402 | * configuration upgrade routines. This means that we need to modify the |
1403 | * config.ini settings in the newConfigs property (since it is currently |
1404 | * being worked on and will be written to disk shortly), but we need to |
1405 | * modify the searches.ini/facets.ini settings in the oldConfigs property |
1406 | * (because they have not been processed yet). |
1407 | * |
1408 | * @return void |
1409 | */ |
1410 | protected function upgradeShardSettings() |
1411 | { |
1412 | // move settings from config.ini to searches.ini: |
1413 | if (isset($this->newConfigs['config.ini']['IndexShards'])) { |
1414 | $this->oldConfigs['searches.ini']['IndexShards'] |
1415 | = $this->newConfigs['config.ini']['IndexShards']; |
1416 | unset($this->newConfigs['config.ini']['IndexShards']); |
1417 | } |
1418 | if (isset($this->newConfigs['config.ini']['ShardPreferences'])) { |
1419 | $this->oldConfigs['searches.ini']['ShardPreferences'] |
1420 | = $this->newConfigs['config.ini']['ShardPreferences']; |
1421 | unset($this->newConfigs['config.ini']['ShardPreferences']); |
1422 | } |
1423 | |
1424 | // move settings from facets.ini to searches.ini (merging StripFacets |
1425 | // setting with StripFields setting): |
1426 | if (isset($this->oldConfigs['facets.ini']['StripFacets'])) { |
1427 | if (!isset($this->oldConfigs['searches.ini']['StripFields'])) { |
1428 | $this->oldConfigs['searches.ini']['StripFields'] = []; |
1429 | } |
1430 | foreach ($this->oldConfigs['facets.ini']['StripFacets'] as $k => $v) { |
1431 | // If we already have values for the current key, merge and dedupe: |
1432 | if (isset($this->oldConfigs['searches.ini']['StripFields'][$k])) { |
1433 | $v .= ',' . $this->oldConfigs['searches.ini']['StripFields'][$k]; |
1434 | $parts = explode(',', $v); |
1435 | foreach ($parts as $i => $part) { |
1436 | $parts[$i] = trim($part); |
1437 | } |
1438 | $v = implode(',', array_unique($parts)); |
1439 | } |
1440 | $this->oldConfigs['searches.ini']['StripFields'][$k] = $v; |
1441 | } |
1442 | unset($this->oldConfigs['facets.ini']['StripFacets']); |
1443 | } |
1444 | } |
1445 | |
1446 | /** |
1447 | * Read the specified file and return an associative array of this format |
1448 | * containing all comments extracted from the file: |
1449 | * |
1450 | * [ |
1451 | * 'sections' => array |
1452 | * 'section_name_1' => array |
1453 | * 'before' => string ("Comments found at the beginning of this section") |
1454 | * 'inline' => string ("Comments found at the end of the section's line") |
1455 | * 'settings' => array |
1456 | * 'setting_name_1' => array |
1457 | * 'before' => string ("Comments found before this setting") |
1458 | * 'inline' => string ("Comments found at the end of setting's line") |
1459 | * ... |
1460 | * 'setting_name_n' => array (same keys as setting_name_1) |
1461 | * ... |
1462 | * 'section_name_n' => array (same keys as section_name_1) |
1463 | * 'after' => string ("Comments found at the very end of the file") |
1464 | * ] |
1465 | * |
1466 | * @param string $filename Name of ini file to read. |
1467 | * |
1468 | * @return array Associative array as described above. |
1469 | */ |
1470 | protected function extractComments($filename) |
1471 | { |
1472 | $lines = file($filename); |
1473 | |
1474 | // Initialize our return value: |
1475 | $retVal = ['sections' => [], 'after' => '']; |
1476 | |
1477 | // Initialize variables for tracking status during parsing: |
1478 | $section = $comments = ''; |
1479 | |
1480 | foreach ($lines as $line) { |
1481 | // To avoid redundant processing, create a trimmed version of the current |
1482 | // line: |
1483 | $trimmed = trim($line); |
1484 | |
1485 | // Is the current line a comment? If so, add to the currentComments |
1486 | // string. Note that we treat blank lines as comments. |
1487 | if ('' === $trimmed || str_starts_with($trimmed, ';')) { |
1488 | $comments .= $line; |
1489 | } elseif ( |
1490 | str_starts_with($trimmed, '[') |
1491 | && ($closeBracket = strpos($trimmed, ']')) > 1 |
1492 | ) { |
1493 | // Is the current line the start of a section? If so, create the |
1494 | // appropriate section of the return value: |
1495 | $section = substr($trimmed, 1, $closeBracket - 1); |
1496 | if ('' !== $section) { |
1497 | // Grab comments at the end of the line, if any: |
1498 | if (($semicolon = strpos($trimmed, ';')) !== false) { |
1499 | $inline = trim(substr($trimmed, $semicolon)); |
1500 | } else { |
1501 | $inline = ''; |
1502 | } |
1503 | $retVal['sections'][$section] = [ |
1504 | 'before' => $comments, |
1505 | 'inline' => $inline, |
1506 | 'settings' => []]; |
1507 | $comments = ''; |
1508 | } |
1509 | } elseif (($equals = strpos($trimmed, '=')) !== false) { |
1510 | // Is the current line a setting? If so, add to the return value: |
1511 | $set = trim(substr($trimmed, 0, $equals)); |
1512 | $set = trim(str_replace('[]', '', $set)); |
1513 | if ('' !== $section && '' !== $set) { |
1514 | // Grab comments at the end of the line, if any: |
1515 | if (($semicolon = strpos($trimmed, ';')) !== false) { |
1516 | $inline = trim(substr($trimmed, $semicolon)); |
1517 | } else { |
1518 | $inline = ''; |
1519 | } |
1520 | // Currently, this data structure doesn't support arrays very |
1521 | // well, since it can't distinguish which line of the array |
1522 | // corresponds with which comments. For now, we just append all |
1523 | // the preceding and inline comments together for arrays. Since |
1524 | // we rarely use arrays in the config.ini file, this isn't a big |
1525 | // concern, but we should improve it if we ever need to. |
1526 | if (!isset($retVal['sections'][$section]['settings'][$set])) { |
1527 | $retVal['sections'][$section]['settings'][$set] |
1528 | = ['before' => $comments, 'inline' => $inline]; |
1529 | } else { |
1530 | $retVal['sections'][$section]['settings'][$set]['before'] |
1531 | .= $comments; |
1532 | $retVal['sections'][$section]['settings'][$set]['inline'] |
1533 | .= "\n" . $inline; |
1534 | } |
1535 | $comments = ''; |
1536 | } |
1537 | } |
1538 | } |
1539 | |
1540 | // Store any leftover comments following the last setting: |
1541 | $retVal['after'] = $comments; |
1542 | |
1543 | return $retVal; |
1544 | } |
1545 | } |