Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
199 lars 1
<?php namespace Clockwork\Support\Laravel;
2
 
3
use Clockwork\Clockwork;
4
use Clockwork\Authentication\NullAuthenticator;
5
use Clockwork\Authentication\SimpleAuthenticator;
6
use Clockwork\DataSource\PhpDataSource;
7
use Clockwork\Helpers\Serializer;
8
use Clockwork\Helpers\ServerTiming;
9
use Clockwork\Helpers\StackFilter;
10
use Clockwork\Helpers\StackTrace;
11
use Clockwork\Request\IncomingRequest;
12
use Clockwork\Request\Request;
13
use Clockwork\Storage\FileStorage;
14
use Clockwork\Storage\Search;
15
use Clockwork\Storage\SqlStorage;
16
use Clockwork\Web\Web;
17
 
18
use Illuminate\Contracts\Foundation\Application;
19
use Illuminate\Contracts\Console\Kernel as ConsoleKernel;
20
use Illuminate\Http\JsonResponse;
21
use Illuminate\Http\Response;
22
use Illuminate\Redis\RedisManager;
23
use Symfony\Component\HttpFoundation\BinaryFileResponse;
24
use Symfony\Component\HttpFoundation\Cookie;
25
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
26
 
27
// Support class for the Laravel integration
28
class ClockworkSupport
29
{
30
	// Laravel application instance
31
	protected $app;
32
 
33
	// Laravel artisan (console application) instance
34
	protected $artisan;
35
 
36
	// Incoming request instance
37
	protected $incomingRequest;
38
 
39
	public function __construct(Application $app)
40
	{
41
		$this->app = $app;
42
	}
43
 
44
	// Get a value form the Clockwork config
45
	public function getConfig($key, $default = null)
46
	{
47
		return $this->app['config']->get("clockwork.{$key}", $default);
48
	}
49
 
50
	// Retrieve metadata
51
	public function getData($id = null, $direction = null, $count = null, $filter = [], $extended = false)
52
	{
53
		if (isset($this->app['session'])) $this->app['session.store']->reflash();
54
 
55
		$authenticator = $this->app['clockwork']->authenticator();
56
		$storage = $this->app['clockwork']->storage();
57
 
58
		$authenticated = $authenticator->check($this->app['request']->header('X-Clockwork-Auth'));
59
 
60
		if ($authenticated !== true) {
61
			return new JsonResponse([ 'message' => $authenticated, 'requires' => $authenticator->requires() ], 403);
62
		}
63
 
64
		if ($direction == 'previous') {
65
			$data = $storage->previous($id, $count, Search::fromRequest($this->app['request']->all()));
66
		} elseif ($direction == 'next') {
67
			$data = $storage->next($id, $count, Search::fromRequest($this->app['request']->all()));
68
		} elseif ($id == 'latest') {
69
			$data = $storage->latest(Search::fromRequest($this->app['request']->all()));
70
		} else {
71
			$data = $storage->find($id);
72
		}
73
 
74
		if ($extended) {
75
			$this->addDataSources();
76
			$this->app['clockwork']->extendRequest($data);
77
		}
78
 
79
		$except = isset($filter['except']) ? explode(',', $filter['except']) : [];
80
		$only = isset($filter['only']) ? explode(',', $filter['only']) : null;
81
 
82
		if (is_array($data)) {
83
			$data = array_map(function ($request) use ($except, $only) {
84
				return $only ? $request->only(array_diff($only, [ 'updateToken' ])) : $request->except(array_merge($except, [ 'updateToken' ]));
85
			}, $data);
86
		} elseif ($data) {
87
			$data = $only ? $data->only(array_diff($only, [ 'updateToken' ])) : $data->except(array_merge($except, [ 'updateToken' ]));
88
		}
89
 
90
		return new JsonResponse($data);
91
	}
92
 
93
	// Retrieve extended metadata
94
	public function getExtendedData($id, $filter = [])
95
	{
96
		return $this->getData($id, null, null, $filter, true);
97
	}
98
 
99
	// Update metadata
100
	public function updateData($id, $input = [])
101
	{
102
		if (isset($this->app['session'])) $this->app['session.store']->reflash();
103
 
104
		if (! $this->isCollectingClientMetrics()) {
105
			throw new NotFoundHttpException;
106
		}
107
 
108
		$storage = $this->app['clockwork']->storage();
109
 
110
		$request = $storage->find($id);
111
 
112
		if (! $request) {
113
			return new JsonResponse([ 'message' => 'Request not found.' ], 404);
114
		}
115
 
116
		$token = isset($input['_token']) ? $input['_token'] : '';
117
 
118
		if (! $request->updateToken || ! hash_equals($request->updateToken, $token)) {
119
			return new JsonResponse([ 'message' => 'Invalid update token.' ], 403);
120
		}
121
 
122
		foreach ($input as $key => $value) {
123
			if (in_array($key, [ 'clientMetrics', 'webVitals' ])) {
124
				$request->$key = $value;
125
			}
126
		}
127
 
128
		$storage->update($request);
129
	}
130
 
131
	// Return an asset for web ui based on it's path, resolves correct mime-type and protectes from accessing files
132
	// outside of Clockwork public dir
133
	public function getWebAsset($path)
134
	{
135
		$asset = (new Web)->asset($path);
136
 
137
		if (! $asset) throw new NotFoundHttpException;
138
 
139
		return new BinaryFileResponse($asset['path'], 200, [ 'Content-Type' => $asset['mime'] ]);
140
	}
141
 
142
	// Add enabled data sources
143
	public function addDataSources()
144
	{
145
		$clockwork = $this->app['clockwork'];
146
 
147
		$clockwork
148
			->addDataSource(new PhpDataSource)
149
			->addDataSource($this->frameworkDataSource());
150
 
151
		if ($this->isFeatureEnabled('database')) $clockwork->addDataSource($this->app['clockwork.eloquent']);
152
		if ($this->isFeatureEnabled('cache')) $clockwork->addDataSource($this->app['clockwork.cache']);
153
		if ($this->isFeatureEnabled('redis')) $clockwork->addDataSource($this->app['clockwork.redis']);
154
		if ($this->isFeatureEnabled('queue')) $clockwork->addDataSource($this->app['clockwork.queue']);
155
		if ($this->isFeatureEnabled('events')) $clockwork->addDataSource($this->app['clockwork.events']);
156
		if ($this->isFeatureEnabled('notifications')) {
157
			$clockwork->addDataSource(
158
				$this->isFeatureAvailable('notifications-events')
159
					? $this->app['clockwork.notifications'] : $this->app['clockwork.swift']
160
			);
161
		}
162
		if ($this->isFeatureAvailable('xdebug')) $clockwork->addDataSource($this->app['clockwork.xdebug']);
163
		if ($this->isFeatureEnabled('views')) {
164
			$clockwork->addDataSource(
165
				$this->getConfig('features.views.use_twig_profiler', false)
166
					? $this->app['clockwork.twig'] : $this->app['clockwork.views']
167
			);
168
		}
169
 
170
		return $this;
171
	}
172
 
173
	// Start listening to events
174
	public function listenToEvents()
175
	{
176
		$this->frameworkDataSource()->listenToEvents();
177
 
178
		if ($this->isFeatureEnabled('cache')) $this->app['clockwork.cache']->listenToEvents();
179
		if ($this->isFeatureEnabled('database')) $this->app['clockwork.eloquent']->listenToEvents();
180
		if ($this->isFeatureEnabled('events')) $this->app['clockwork.events']->listenToEvents();
181
		if ($this->isFeatureEnabled('notifications')) {
182
			$this->isFeatureAvailable('notifications-events')
183
				? $this->app['clockwork.notifications']->listenToEvents() : $this->app['clockwork.swift']->listenToEvents();
184
		}
185
		if ($this->isFeatureEnabled('queue')) {
186
			$this->app['clockwork.queue']->listenToEvents();
187
			$this->app['clockwork.queue']->setCurrentRequestId($this->app['clockwork.request']->id);
188
		}
189
		if ($this->isFeatureEnabled('redis')) {
190
			$this->app[RedisManager::class]->enableEvents();
191
			$this->app['clockwork.redis']->listenToEvents();
192
		}
193
		if ($this->isFeatureEnabled('views')) {
194
			$this->getConfig('features.views.use_twig_profiler', false)
195
				? $this->app['clockwork.twig']->listenToEvents() : $this->app['clockwork.views']->listenToEvents();
196
		}
197
 
198
		if ($this->isCollectingCommands()) $this->collectCommands();
199
		if ($this->isCollectingQueueJobs()) $this->collectQueueJobs();
200
 
201
		return $this;
202
	}
203
 
204
	// Resolves the framework data source from the container
205
	protected function frameworkDataSource()
206
	{
207
		return $this->app['clockwork.laravel'];
208
	}
209
 
210
	public function handleArtisanEvents()
211
	{
212
		if (class_exists(\Illuminate\Console\Events\ArtisanStarting::class)) {
213
			$this->app['events']->listen(\Illuminate\Console\Events\ArtisanStarting::class, function ($event) {
214
				$this->artisan = $event->artisan;
215
			});
216
		}
217
	}
218
 
219
	public function handleOctaneEvents()
220
	{
221
		$this->app['events']->listen(\Laravel\Octane\Events\RequestReceived::class, function ($event) {
222
			$this->app = $event->sandbox;
223
			$this->incomingRequest = null;
224
 
225
			$this->app->forgetInstance('clockwork.request');
226
			$request = $this->app->make('clockwork.request')->override('requestTime', microtime(true));
227
 
228
			$this->app['clockwork']->reset()->request($request);
229
			$this->app['clockwork.laravel']->setApplication($this->app);
230
		});
231
	}
232
 
233
	// Make a storage instance based on the current configuration
234
	public function makeStorage()
235
	{
236
		$expiration = $this->getConfig('storage_expiration');
237
 
238
		if ($this->getConfig('storage', 'files') == 'sql') {
239
			$database = $this->getConfig('storage_sql_database', storage_path('clockwork.sqlite'));
240
			$table = $this->getConfig('storage_sql_table', 'clockwork');
241
 
242
			if ($this->app['config']->get("database.connections.{$database}")) {
243
				$database = $this->app['db']->connection($database)->getPdo();
244
			} else {
245
				$database = "sqlite:{$database}";
246
			}
247
 
248
			return new SqlStorage($database, $table, null, null, $expiration);
249
		} else {
250
			return new FileStorage(
251
				$this->getConfig('storage_files_path', storage_path('clockwork')),
252
				0700,
253
				$expiration,
254
				$this->getConfig('storage_files_compress', false)
255
			);
256
		}
257
	}
258
 
259
	// Make an authenticator instance based on the current configuration
260
	public function makeAuthenticator()
261
	{
262
		$authenticator = $this->getConfig('authentication');
263
 
264
		if (is_string($authenticator)) {
265
			return $this->app->make($authenticator);
266
		} elseif ($authenticator) {
267
			return new SimpleAuthenticator($this->getConfig('authentication_password'));
268
		} else {
269
			return new NullAuthenticator;
270
		}
271
	}
272
 
273
	// Set up collecting of executed artisan commands
274
	public function collectCommands()
275
	{
276
		$this->app['events']->listen(\Illuminate\Console\Events\CommandStarting::class, function ($event) {
277
			// only collect commands ran through artisan cli, other commands are recorded as part of respective request
278
			if (basename(StackTrace::get()->last()->file) != 'artisan') return;
279
 
280
			if (! $this->getConfig('artisan.collect_output')) return;
281
			if (! $event->command || $this->isCommandFiltered($event->command)) return;
282
 
283
			$event->output->setFormatter(
284
				version_compare(\Illuminate\Foundation\Application::VERSION, '9.0.0', '<')
285
					? new Console\CapturingLegacyFormatter($event->output->getFormatter())
286
					: new Console\CapturingFormatter($event->output->getFormatter())
287
			);
288
		});
289
 
290
		$this->app['events']->listen(\Illuminate\Console\Events\CommandFinished::class, function ($event) {
291
			// only collect commands ran through artisan cli, other commands are recorded as part of respective request
292
			if (basename(StackTrace::get()->last()->file) != 'artisan') return;
293
 
294
			if (! $event->command || $this->isCommandFiltered($event->command)) return;
295
 
296
			$command = $this->artisan->find($event->command);
297
 
298
			$allArguments = $event->input->getArguments();
299
			$allOptions = $event->input->getOptions();
300
 
301
			$defaultArguments = $command->getDefinition()->getArgumentDefaults();
302
			$defaultOptions = $command->getDefinition()->getOptionDefaults();
303
 
304
			$this->app->make('clockwork')
305
				->resolveAsCommand(
306
					$event->command,
307
					$event->exitCode,
308
					array_udiff_assoc($allArguments, $defaultArguments, function ($a, $b) { return $a == $b ? 0 : 1; }),
309
					array_udiff_assoc($allOptions, $defaultOptions, function ($a, $b) { return $a == $b ? 0 : 1; }),
310
					$defaultArguments,
311
					$defaultOptions,
312
					$this->getConfig('artisan.collect_output') ? $event->output->getFormatter()->capturedOutput() : null
313
				)
314
				->storeRequest();
315
		});
316
	}
317
 
318
	// Set up collecting of executed queue jobs
319
	public function collectQueueJobs()
320
	{
321
		$this->app['events']->listen(\Illuminate\Queue\Events\JobProcessing::class, function ($event) {
322
			// sync jobs are recorded as part of the parent request
323
			if ($event->job instanceof \Illuminate\Queue\Jobs\SyncJob) return;
324
 
325
			$payload = $event->job->payload();
326
 
327
			if (! isset($payload['clockwork_id']) || $this->isQueueJobFiltered($payload['displayName'])) return;
328
 
329
			$request = new Request([ 'id' => $payload['clockwork_id'] ]);
330
			if (isset($payload['clockwork_parent_id'])) $request->setParent($payload['clockwork_parent_id']);
331
 
332
			$this->app->make('clockwork')->reset()->request($request);
333
 
334
			$this->app['clockwork.queue']->setCurrentRequestId($request->id);
335
		});
336
 
337
		$this->app['events']->listen(\Illuminate\Queue\Events\JobProcessed::class, function ($event) {
338
			$this->processQueueJob($event->job);
339
		});
340
 
341
		$this->app['events']->listen(\Illuminate\Queue\Events\JobFailed::class, function ($event) {
342
			$this->processQueueJob($event->job, $event->exception);
343
		});
344
	}
345
 
346
	// Process an executed queue job, resolves and records the current request
347
	protected function processQueueJob($job, $exception = null)
348
	{
349
		// sync jobs are recorded as part of the parent request
350
		if ($job instanceof \Illuminate\Queue\Jobs\SyncJob) return;
351
 
352
		$payload = $job->payload();
353
 
354
		if (! isset($payload['clockwork_id'])) return;
355
 
356
		$unserialized = isset($payload['data']['command']) ? unserialize($payload['data']['command']) : null;
357
 
358
		if (! $unserialized || $this->isQueueJobFiltered(get_class($unserialized))) return;
359
 
360
		if ($exception) {
361
			$this->app->make('clockwork')->error($exception->getMessage(), [ 'exception' => $exception ]);
362
		}
363
 
364
		$this->app->make('clockwork')
365
			->resolveAsQueueJob(
366
				get_class($unserialized),
367
				$payload['displayName'],
368
				$job->hasFailed() ? 'failed' : ($job->isReleased() ? 'released' : 'done'),
369
				$unserialized,
370
				$job->getQueue(),
371
				$job->getConnectionName(),
372
				array_filter([
373
					'maxTries'     => isset($payload['maxTries']) ? $payload['maxTries'] : null,
374
					'delaySeconds' => isset($payload['delaySeconds']) ? $payload['delaySeconds'] : null,
375
					'timeout'      => isset($payload['timeout']) ? $payload['timeout'] : null,
376
					'timeoutAt'    => isset($payload['timeoutAt']) ? $payload['timeoutAt'] : null
377
				])
378
			)
379
			->storeRequest();
380
	}
381
 
382
	// Process an executed http request, resolves the current request, sets Clockwork headers and cookies
383
	public function processRequest($request, $response)
384
	{
385
		if (! $this->isCollectingRequests()) {
386
			return $response; // Clockwork is not collecting data, additional check when the middleware is enabled manually
387
		}
388
 
389
		$clockwork = $this->app['clockwork'];
390
		$clockworkRequest = $clockwork->request();
391
 
392
		$clockwork->event('Controller')->end();
393
 
394
		$this->setResponse($response);
395
 
396
		$clockwork->resolveRequest();
397
 
398
		if (! $this->isEnabled() || ! $this->isRecording($clockworkRequest)) {
399
			return $response; // Clockwork is disabled or we are not recording this request
400
		}
401
 
402
		$response->headers->set('X-Clockwork-Id', $clockworkRequest->id, true);
403
		$response->headers->set('X-Clockwork-Version', Clockwork::VERSION, true);
404
 
405
		if ($request->getBasePath()) {
406
			$response->headers->set('X-Clockwork-Path', $request->getBasePath() . '/__clockwork/', true);
407
		}
408
 
409
		foreach ($this->getConfig('headers', []) as $headerName => $headerValue) {
410
			$response->headers->set("X-Clockwork-Header-{$headerName}", $headerValue);
411
		}
412
 
413
		foreach ($clockwork->request()->subrequests as $subrequest) {
414
			$url = urlencode($subrequest['url']);
415
			$path = urlencode($subrequest['path']);
416
 
417
			$response->headers->set('X-Clockwork-Subrequest', "{$subrequest['id']};{$url};{$path}", false);
418
		}
419
 
420
		$this->appendServerTimingHeader($response, $clockworkRequest);
421
 
422
		if (! ($response instanceof Response)) {
423
			return $response;
424
		}
425
 
426
		if ($this->isCollectingClientMetrics() || $this->isToolbarEnabled()) {
427
			$clockworkBrowser = [
428
				'requestId' => $clockworkRequest->id,
429
				'version'   => Clockwork::VERSION,
430
				'path'      => $request->getBasePath() . '/__clockwork/',
431
				'webPath'   => $request->getBasePath() . '/' . $this->webPaths()[0] . '/app',
432
				'token'     => $clockworkRequest->updateToken,
433
				'metrics'   => $this->isCollectingClientMetrics(),
434
				'toolbar'   => $this->isToolbarEnabled()
435
			];
436
 
437
			$response->cookie(
438
				new Cookie('x-clockwork', json_encode($clockworkBrowser), time() + 60, null, null, null, false)
439
			);
440
		}
441
 
442
		return $response;
443
	}
444
 
445
	// Records the current http request
446
	public function recordRequest()
447
	{
448
		if (! $this->isCollectingRequests()) {
449
			return; // Clockwork is not collecting data, additional check when the middleware is enabled manually
450
		}
451
 
452
		$clockwork = $this->app['clockwork'];
453
 
454
		if (! $this->isRecording($clockwork->request())) {
455
			return; // Collecting data is disabled, return immediately
456
		}
457
 
458
		$clockwork->storeRequest();
459
	}
460
 
461
	// Set current http response on the framework data source
462
	protected function setResponse($response)
463
	{
464
		$this->app['clockwork.laravel']->setResponse($response);
465
	}
466
 
467
	// Configure serializer defaults
468
	public function configureSerializer()
469
	{
470
		Serializer::defaults([
471
			'limit'       => $this->getConfig('serialization_depth'),
472
			'blackbox'    => $this->getConfig('serialization_blackbox'),
473
			'traces'      => $this->getConfig('stack_traces.enabled', true),
474
			'tracesSkip'  => StackFilter::make()
475
				->isNotVendor(array_merge(
476
					$this->getConfig('stack_traces.skip_vendors', []),
477
					[ 'itsgoingd', 'laravel', 'illuminate', 'psr' ]
478
				))
479
				->isNotNamespace($this->getConfig('stack_traces.skip_namespaces', []))
480
				->isNotFunction([ 'call_user_func', 'call_user_func_array' ])
481
				->isNotClass($this->getConfig('stack_traces.skip_classes', [])),
482
			'tracesLimit' => $this->getConfig('stack_traces.limit', 10)
483
		]);
484
 
485
		return $this;
486
	}
487
 
488
	// Configure should collect rules
489
	public function configureShouldCollect()
490
	{
491
		$this->app['clockwork']->shouldCollect([
492
			'onDemand'        => $this->getConfig('requests.on_demand', false),
493
			'sample'          => $this->getConfig('requests.sample', false),
494
			'except'          => $this->getConfig('requests.except', []),
495
			'only'            => $this->getConfig('requests.only', []),
496
			'exceptPreflight' => $this->getConfig('requests.except_preflight', [])
497
		]);
498
 
499
		// don't collect data for Clockwork requests
500
		$webPath = $this->webPaths()[0];
501
		$this->app['clockwork']->shouldCollect()->except([ '/__clockwork(?:/.*)?', "/{$webPath}(?:/.*)?" ]);
502
 
503
		return $this;
504
	}
505
 
506
	// Configure should record rules
507
	public function configureShouldRecord()
508
	{
509
		$this->app['clockwork']->shouldRecord([
510
			'errorsOnly' => $this->getConfig('requests.errors_only', false),
511
			'slowOnly'   => $this->getConfig('requests.slow_only', false) ? $this->getConfig('requests.slow_threshold') : false
512
		]);
513
 
514
		return $this;
515
	}
516
 
517
	// Check whether Clockwork is enabled at all
518
	public function isEnabled()
519
	{
520
		return $this->getConfig('enable')
521
			|| $this->getConfig('enable') === null && $this->app['config']->get('app.debug');
522
	}
523
 
524
	// Check whether we are collecting data
525
	public function isCollectingData()
526
	{
527
		return $this->isCollectingCommands()
528
			|| $this->isCollectingQueueJobs()
529
			|| $this->isCollectingRequests()
530
			|| $this->isCollectingTests();
531
	}
532
 
533
	// Check whether we are collecting artisan commands
534
	public function isCollectingCommands()
535
	{
536
		return ($this->isEnabled() || $this->getConfig('collect_data_always', false))
537
			&& $this->app->runningInConsole()
538
			&& $this->getConfig('artisan.collect', false);
539
	}
540
 
541
	// Check whether we are collecting queue jobs
542
	public function isCollectingQueueJobs()
543
	{
544
		return ($this->isEnabled() || $this->getConfig('collect_data_always', false))
545
			&& $this->app->runningInConsole()
546
			&& $this->getConfig('queue.collect', false);
547
	}
548
 
549
	// Check whether we are collecting http requests
550
	public function isCollectingRequests()
551
	{
552
		return ($this->isEnabled() || $this->getConfig('collect_data_always', false))
553
			&& ! $this->app->runningInConsole()
554
			&& $this->app['clockwork']->shouldCollect()->filter($this->incomingRequest());
555
	}
556
 
557
	// Check whether we are collecting tests
558
	public function isCollectingTests()
559
	{
560
		return ($this->isEnabled() || $this->getConfig('collect_data_always', false))
561
			&& $this->app->runningInConsole()
562
			&& $this->getConfig('tests.collect', false);
563
	}
564
 
565
	// Check whether we are recording the passed request
566
	public function isRecording($incomingRequest)
567
	{
568
		return ($this->isEnabled() || $this->getConfig('collect_data_always', false))
569
			&& $this->app['clockwork']->shouldRecord()->filter($incomingRequest);
570
	}
571
 
572
	// Check whether a feature is enabled
573
	public function isFeatureEnabled($feature)
574
	{
575
		return $this->getConfig("features.{$feature}.enabled") && $this->isFeatureAvailable($feature);
576
	}
577
 
578
	// Check whether a feature is available
579
	public function isFeatureAvailable($feature)
580
	{
581
		if ($feature == 'database') {
582
			return $this->app['config']->get('database.default');
583
		} elseif ($feature == 'notifications-events') {
584
			return class_exists(\Illuminate\Mail\Events\MessageSent::class)
585
				&& class_exists(\Illuminate\Notifications\Events\NotificationSent::class);
586
		} elseif ($feature == 'redis') {
587
			return method_exists(\Illuminate\Redis\RedisManager::class, 'enableEvents');
588
		} elseif ($feature == 'queue') {
589
			return method_exists(\Illuminate\Queue\Queue::class, 'createPayloadUsing');
590
		} elseif ($feature == 'xdebug') {
591
			return in_array('xdebug', get_loaded_extensions());
592
		}
593
 
594
		return true;
595
	}
596
 
597
	// Check whether we are collecting client metrics
598
	public function isCollectingClientMetrics()
599
	{
600
		return $this->getConfig('features.performance.client_metrics', true);
601
	}
602
 
603
	// Check whether the toolbar is enabled
604
	public function isToolbarEnabled()
605
	{
606
		return $this->getConfig('toolbar', false);
607
	}
608
 
609
	// Check whether the web ui is enabled
610
	public function isWebEnabled()
611
	{
612
		return $this->getConfig('web', true);
613
	}
614
 
615
	// Check whether a command should not be collected
616
	protected function isCommandFiltered($command)
617
	{
618
		$only = $this->getConfig('artisan.only', []);
619
 
620
		if (count($only)) return ! in_array($command, $only);
621
 
622
		$except = $this->getConfig('artisan.except', []);
623
 
624
		if ($this->getConfig('artisan.except_laravel_commands', true)) {
625
			$except = array_merge($except, $this->builtinLaravelCommands());
626
		}
627
 
628
		$except = array_merge($except, $this->builtinClockworkCommands());
629
 
630
		return in_array($command, $except);
631
	}
632
 
633
	// Check whether a queue job should not be collected
634
	protected function isQueueJobFiltered($queueJob)
635
	{
636
		$only = $this->getConfig('queue.only', []);
637
 
638
		if (count($only)) return ! in_array($queueJob, $only);
639
 
640
		$except = $this->getConfig('queue.except', []);
641
 
642
		return in_array($queueJob, $except);
643
	}
644
 
645
	// Check whether a test should not be collected
646
	public function isTestFiltered($test)
647
	{
648
		$except = $this->getConfig('tests.except', []);
649
 
650
		return in_array($test, $except);
651
	}
652
 
653
	// Append server timing headers from a Clockwork request to a http response
654
	protected function appendServerTimingHeader($response, $request)
655
	{
656
		if (($eventsCount = $this->getConfig('server_timing', 10)) !== false) {
657
			$response->headers->set('Server-Timing', ServerTiming::fromRequest($request, $eventsCount)->value());
658
		}
659
	}
660
 
661
	// Make an incoming request instance
662
	protected function incomingRequest()
663
	{
664
		if ($this->incomingRequest) return $this->incomingRequest;
665
 
666
		return $this->incomingRequest = new IncomingRequest([
667
			'method'  => $this->app['request']->getMethod(),
668
			'uri'     => $this->app['request']->getRequestUri(),
669
			'input'   => $this->app['request']->input(),
670
			'cookies' => $this->app['request']->cookie()
671
		]);
672
	}
673
 
674
	// Return an array of web ui paths
675
	public function webPaths()
676
	{
677
		$path = $this->getConfig('web', true);
678
 
679
		if (is_string($path)) return collect([ trim($path, '/') ]);
680
 
681
		return collect([ 'clockwork', '__clockwork' ]);
682
	}
683
 
684
	// Return an array of built-in Laravel commands
685
	protected function builtinLaravelCommands()
686
	{
687
		return [
688
			'clear-compiled', 'completion', 'db', 'down', 'dump-server', 'env', 'help', 'list', 'migrate', 'optimize',
689
			'preset', 'serve', 'test', 'tinker', 'up',
690
			'app:name',
691
			'auth:clear-resets',
692
			'cache:clear', 'cache:forget', 'cache:table',
693
			'config:cache', 'config:clear',
694
			'db:seed', 'db:wipe',
695
			'event:cache', 'event:clear', 'event:generate', 'event:list',
696
			'horizon', 'horizon:clear', 'horizon:continue', 'horizon:continue-supervisor', 'horizon:forget',
697
			'horizon:install', 'horizon:list', 'horizon:pause', 'horizon:pause-supervisor', 'horizon:publish',
698
			'horizon:purge', 'horizon:snapshot', 'horizon:status', 'horizon:supervisors', 'horizon:terminate',
699
			'horizon:work',
700
			'key:generate',
701
			'make:auth', 'make:cast', 'make:channel', 'make:command', 'make:component', 'make:controller', 'make:event',
702
			'make:exception', 'make:factory', 'make:job', 'make:listener', 'make:mail', 'make:middleware',
703
			'make:migration', 'make:model', 'make:notification', 'make:observer', 'make:policy', 'make:provider',
704
			'make:request', 'make:resource', 'make:rule', 'make:scope', 'make:seeder', 'make:test',
705
			'migrate:fresh', 'migrate:install', 'migrate:refresh', 'migrate:reset', 'migrate:rollback',
706
			'migrate:status',
707
			'model:prune',
708
			'notifications:table',
709
			'octane:install', 'octane:reload', 'octane:start', 'octane:status', 'octane:stop',
710
			'optimize:clear',
711
			'package:discover',
712
			'queue:batches-table', 'queue:clear', 'queue:failed', 'queue:failed-table', 'queue:flush', 'queue:forget',
713
			'queue:listen', 'queue:monitor', 'queue:prune-batches', 'queue:prune-failed', 'queue:restart',
714
			'queue:retry', 'queue:retry-batch', 'queue:table', 'queue:work',
715
			'route:cache', 'route:clear', 'route:list',
716
			'sail:install', 'sail:publish',
717
			'schedule:clear-cache', 'schedule:list', 'schedule:run', 'schedule:test', 'schedule:work',
718
			'schema:dump',
719
			'session:table',
720
			'storage:link',
721
			'stub:publish',
722
			'vendor:publish',
723
			'view:cache', 'view:clear'
724
		];
725
	}
726
 
727
	// Return an array of built-in Clockwork commands
728
	protected function builtinClockworkCommands()
729
	{
730
		return [
731
			'clockwork:clean'
732
		];
733
	}
734
}