Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 111 |
|
0.00% |
0 / 10 |
CRAP | |
0.00% |
0 / 1 |
MaintenanceController | |
0.00% |
0 / 111 |
|
0.00% |
0 / 10 |
812 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
homeAction | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
2 | |||
getScripts | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
6 | |||
scriptAction | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
12 | |||
clearcacheAction | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
12 | |||
deleteexpiredsearchesAction | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
deleteexpiredsessionsAction | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
updatebrowscapcacheAction | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
expire | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
20 | |||
updateBrowscapCache | |
0.00% |
0 / 35 |
|
0.00% |
0 / 1 |
110 |
1 | <?php |
2 | |
3 | /** |
4 | * Admin Maintenance Controller |
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 Controller |
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 VuFindAdmin\Controller; |
31 | |
32 | use DateTime; |
33 | use Laminas\Cache\Psr\SimpleCache\SimpleCacheDecorator; |
34 | use Laminas\Log\LoggerInterface; |
35 | use Laminas\ServiceManager\ServiceLocatorInterface; |
36 | use VuFind\Cache\Manager as CacheManager; |
37 | use VuFind\Db\Service\Feature\DeleteExpiredInterface; |
38 | use VuFind\Db\Service\SearchServiceInterface; |
39 | use VuFind\Db\Service\SessionServiceInterface; |
40 | use VuFind\Http\GuzzleService; |
41 | |
42 | use function ini_get; |
43 | use function intval; |
44 | |
45 | /** |
46 | * Class helps maintain database |
47 | * |
48 | * @category VuFind |
49 | * @package Controller |
50 | * @author Demian Katz <demian.katz@villanova.edu> |
51 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
52 | * @link https://vufind.org Main Site |
53 | */ |
54 | class MaintenanceController extends AbstractAdmin |
55 | { |
56 | /** |
57 | * Cache manager |
58 | * |
59 | * @var CacheManager |
60 | */ |
61 | protected $cacheManager; |
62 | |
63 | /** |
64 | * Guzzle service |
65 | * |
66 | * @var GuzzleService |
67 | */ |
68 | protected $guzzleService; |
69 | |
70 | /** |
71 | * Logger |
72 | * |
73 | * @var LoggerInterface |
74 | */ |
75 | protected $logger; |
76 | |
77 | /** |
78 | * Constructor |
79 | * |
80 | * @param ServiceLocatorInterface $sm Service locator |
81 | * @param CacheManager $cacheManager Cache manager |
82 | * @param GuzzleService $guzzleService Guzzle service |
83 | * @param LoggerInterface $logger Logger |
84 | */ |
85 | public function __construct( |
86 | ServiceLocatorInterface $sm, |
87 | CacheManager $cacheManager, |
88 | GuzzleService $guzzleService, |
89 | LoggerInterface $logger |
90 | ) { |
91 | parent::__construct($sm); |
92 | $this->cacheManager = $cacheManager; |
93 | $this->guzzleService = $guzzleService; |
94 | $this->logger = $logger; |
95 | } |
96 | |
97 | /** |
98 | * System Maintenance |
99 | * |
100 | * @return \Laminas\View\Model\ViewModel |
101 | */ |
102 | public function homeAction() |
103 | { |
104 | $view = $this->createViewModel(); |
105 | $cacheManager = $this->getService(\VuFind\Cache\Manager::class); |
106 | $view->caches = $cacheManager->getCacheList(); |
107 | $view->nonPersistentCaches = $cacheManager->getNonPersistentCacheList(); |
108 | $view->scripts = $this->getScripts(); |
109 | $view->setTemplate('admin/maintenance/home'); |
110 | return $view; |
111 | } |
112 | |
113 | /** |
114 | * Get a list of the names of scripts available to run through the admin panel. |
115 | * |
116 | * @return array |
117 | */ |
118 | protected function getScripts(): array |
119 | { |
120 | // Load the AdminScripts.ini settings |
121 | $config = $this->getService(\VuFind\Config\PluginManager::class) |
122 | ->get('AdminScripts')->toArray(); |
123 | $globalConfig = $config['Global'] ?? []; |
124 | unset($config['Global']); |
125 | |
126 | // Filter out any commands that the current user does not have permission to run: |
127 | $permission = $this->permission(); |
128 | $filter = function ($script) use ($permission, $globalConfig) { |
129 | $requiredPermission = $script['permission'] ?? $globalConfig['defaultPermission'] ?? null; |
130 | return empty($requiredPermission) || $permission->isAuthorized($requiredPermission); |
131 | }; |
132 | return array_filter($config, $filter); |
133 | } |
134 | |
135 | /** |
136 | * Run script action. |
137 | * |
138 | * @return mixed |
139 | */ |
140 | public function scriptAction() |
141 | { |
142 | $script = $this->params()->fromRoute('name'); |
143 | $scripts = $this->getScripts(); |
144 | $details = $scripts[$script] ?? null; |
145 | if (empty($details['command'])) { |
146 | $this->flashMessenger()->addErrorMessage('Unknown command: ' . $script); |
147 | } else { |
148 | $code = $output = null; |
149 | exec($details['command'], $output, $code); |
150 | $successCode = intval($details['successCode'] ?? 0); |
151 | if ($code !== $successCode) { |
152 | $this->flashMessenger()->addErrorMessage( |
153 | "Command failed; expected $successCode but received $code" |
154 | ); |
155 | } else { |
156 | $this->flashMessenger()->addSuccessMessage( |
157 | "Success ($script)! Output = " . implode("\n", $output) |
158 | ); |
159 | } |
160 | } |
161 | return $this->redirect()->toRoute('admin/maintenance'); |
162 | } |
163 | |
164 | /** |
165 | * Clear cache(s). |
166 | * |
167 | * @return mixed |
168 | */ |
169 | public function clearcacheAction() |
170 | { |
171 | $cache = null; |
172 | $cacheManager = $this->getService(\VuFind\Cache\Manager::class); |
173 | foreach ($this->params()->fromQuery('cache', []) as $cache) { |
174 | $cacheManager->getCache($cache)->flush(); |
175 | } |
176 | // If cache is unset, we didn't go through the loop above, so no message |
177 | // needs to be displayed. |
178 | if (isset($cache)) { |
179 | $this->flashMessenger()->addSuccessMessage('Cache(s) cleared.'); |
180 | } |
181 | return $this->forwardTo('AdminMaintenance', 'Home'); |
182 | } |
183 | |
184 | /** |
185 | * Delete expired searches. |
186 | * |
187 | * @return mixed |
188 | */ |
189 | public function deleteexpiredsearchesAction() |
190 | { |
191 | // Delete the expired searches--this cleans up any junk left in the |
192 | // database from old search histories that were not caught by the |
193 | // session garbage collector. |
194 | return $this->expire( |
195 | SearchServiceInterface::class, |
196 | '%%count%% expired searches deleted.', |
197 | 'No expired searches to delete.' |
198 | ); |
199 | } |
200 | |
201 | /** |
202 | * Delete expired sessions. |
203 | * |
204 | * @return mixed |
205 | */ |
206 | public function deleteexpiredsessionsAction() |
207 | { |
208 | // Delete the expired sessions--this cleans up any junk left in the |
209 | // database by the session garbage collector. |
210 | return $this->expire( |
211 | SessionServiceInterface::class, |
212 | '%%count%% expired sessions deleted.', |
213 | 'No expired sessions to delete.' |
214 | ); |
215 | } |
216 | |
217 | /** |
218 | * Update browscap cache action. |
219 | * |
220 | * @return mixed |
221 | */ |
222 | public function updatebrowscapcacheAction() |
223 | { |
224 | if (ini_get('max_execution_time') < 3600) { |
225 | ini_set('max_execution_time', '3600'); |
226 | } |
227 | $this->updateBrowscapCache(); |
228 | return $this->forwardTo('AdminMaintenance', 'Home'); |
229 | } |
230 | |
231 | /** |
232 | * Abstract delete method. |
233 | * |
234 | * @param string $serviceName Service to operate on. |
235 | * @param string $successString String for reporting success. |
236 | * @param string $failString String for reporting failure. |
237 | * @param int $minAge Minimum age allowed for expiration (also used |
238 | * as default value). |
239 | * |
240 | * @return mixed |
241 | */ |
242 | protected function expire($serviceName, $successString, $failString, $minAge = 2) |
243 | { |
244 | $daysOld = intval($this->params()->fromQuery('daysOld', $minAge)); |
245 | if ($daysOld < $minAge) { |
246 | $this->flashMessenger()->addErrorMessage( |
247 | str_replace( |
248 | '%%age%%', |
249 | $minAge, |
250 | 'Expiration age must be at least %%age%% days.' |
251 | ) |
252 | ); |
253 | } else { |
254 | $service = $this->getDbService($serviceName); |
255 | if (!$service instanceof DeleteExpiredInterface) { |
256 | throw new \Exception("Unsupported service: $serviceName"); |
257 | } |
258 | $count = $service->deleteExpired(new DateTime("now - $daysOld days")); |
259 | if ($count == 0) { |
260 | $msg = $failString; |
261 | } else { |
262 | $msg = str_replace('%%count%%', $count, $successString); |
263 | } |
264 | $this->flashMessenger()->addSuccessMessage($msg); |
265 | } |
266 | return $this->forwardTo('AdminMaintenance', 'Home'); |
267 | } |
268 | |
269 | /** |
270 | * Update browscap cache. |
271 | * |
272 | * Note that there's also similar functionality in BrowscapCommand CLI utility. |
273 | * |
274 | * @return void |
275 | */ |
276 | protected function updateBrowscapCache(): void |
277 | { |
278 | ini_set('memory_limit', '1024M'); |
279 | $type = $this->params()->fromQuery('cacheType', 'standard'); |
280 | switch ($type) { |
281 | case 'full': |
282 | $type = \BrowscapPHP\Helper\IniLoaderInterface::PHP_INI_FULL; |
283 | break; |
284 | case 'lite': |
285 | $type = \BrowscapPHP\Helper\IniLoaderInterface::PHP_INI_LITE; |
286 | break; |
287 | case 'standard': |
288 | $type = \BrowscapPHP\Helper\IniLoaderInterface::PHP_INI; |
289 | break; |
290 | default: |
291 | $this->flashMessenger()->addErrorMessage('Invalid browscap file-type specified'); |
292 | return; |
293 | } |
294 | |
295 | $cache = new SimpleCacheDecorator($this->cacheManager->getCache('browscap')); |
296 | $client = $this->guzzleService->createClient(); |
297 | |
298 | $bc = new \BrowscapPHP\BrowscapUpdater($cache, new \Laminas\Log\PsrLoggerAdapter($this->logger), $client); |
299 | try { |
300 | $bc->checkUpdate(); |
301 | } catch (\BrowscapPHP\Exception\NoNewVersionException $e) { |
302 | $this->flashMessenger() |
303 | ->addSuccessMessage('No newer browscap version available. Clear the cache to force update.'); |
304 | return; |
305 | } catch (\BrowscapPHP\Exception\FetcherException $e) { |
306 | $this->flashMessenger()->addErrorMessage($e->getMessage()); |
307 | $this->logger->err((string)$e); |
308 | return; |
309 | } catch (\BrowscapPHP\Exception\NoCachedVersionException $e) { |
310 | // Fall through... |
311 | } catch (\Exception $e) { |
312 | // Output the exception and continue (assume we don't have a current version): |
313 | $this->flashMessenger()->addWarningMessage($e->getMessage()); |
314 | $this->logger->warn((string)$e); |
315 | } |
316 | try { |
317 | $bc->update($type); |
318 | $this->logger->info('Browscap cache updated'); |
319 | $this->flashMessenger()->addSuccessMessage('Browscap cache successfully updated.'); |
320 | } catch (\Exception $e) { |
321 | $this->flashMessenger()->addErrorMessage($e->getMessage()); |
322 | $this->logger->warn((string)$e); |
323 | } |
324 | } |
325 | } |