1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types = 1); |
4
|
|
|
|
5
|
|
|
namespace Drupal\mongodb_watchdog\Controller; |
6
|
|
|
|
7
|
|
|
use Drupal\Core\Config\ImmutableConfig; |
8
|
|
|
use Drupal\Core\Pager\PagerManagerInterface; |
9
|
|
|
use Drupal\mongodb_watchdog\Logger; |
10
|
|
|
use MongoDB\Collection; |
11
|
|
|
use MongoDB\Database; |
12
|
|
|
use Psr\Log\LoggerInterface; |
13
|
|
|
use Symfony\Component\DependencyInjection\ContainerInterface; |
14
|
|
|
use Symfony\Component\HttpFoundation\Request; |
15
|
|
|
|
16
|
|
|
/** |
17
|
|
|
* The Top403/Top404 controllers. |
18
|
|
|
*/ |
19
|
|
|
class TopController extends ControllerBase { |
20
|
|
|
const TYPES = [ |
21
|
|
|
'page not found', |
22
|
|
|
'access denied', |
23
|
|
|
]; |
24
|
|
|
|
25
|
|
|
const TYPE_MAP = [ |
26
|
|
|
'root' => TopResult::class, |
27
|
|
|
]; |
28
|
|
|
|
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* The database holding the logger collections. |
32
|
|
|
* |
33
|
|
|
* @var \MongoDB\Database |
34
|
|
|
*/ |
35
|
|
|
protected $database; |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* TopController constructor. |
39
|
|
|
* |
40
|
|
|
* @param \Psr\Log\LoggerInterface $logger |
41
|
|
|
* The logger service, to log intervening events. |
42
|
|
|
* @param \Drupal\mongodb_watchdog\Logger $watchdog |
43
|
|
|
* The MongoDB logger, to load stored events. |
44
|
|
|
* @param \Drupal\Core\Config\ImmutableConfig $config |
45
|
|
|
* The module configuration. |
46
|
|
|
* @param \MongoDB\Database $database |
47
|
|
|
* Needed because there is no group() command in phplib yet. |
48
|
|
|
* @param \Drupal\Core\Pager\PagerManagerInterface $pagerManager |
49
|
|
|
* The core pager.manager service. |
50
|
|
|
* |
51
|
|
|
* @see https://jira.mongodb.org/browse/PHPLIB-177 |
52
|
|
|
*/ |
53
|
|
|
public function __construct( |
54
|
|
|
LoggerInterface $logger, |
55
|
|
|
Logger $watchdog, |
56
|
|
|
ImmutableConfig $config, |
57
|
|
|
Database $database, |
58
|
|
|
PagerManagerInterface $pagerManager) { |
59
|
|
|
parent::__construct($logger, $watchdog, $pagerManager, $config); |
60
|
|
|
|
61
|
|
|
$this->database = $database; |
62
|
|
|
} |
63
|
|
|
|
64
|
|
|
/** |
65
|
|
|
* Controller. |
66
|
|
|
* |
67
|
|
|
* @param \Symfony\Component\HttpFoundation\Request $request |
68
|
|
|
* The current request. |
69
|
|
|
* @param string $type |
70
|
|
|
* The type of top report to produce. |
71
|
|
|
* |
72
|
|
|
* @return array |
73
|
|
|
* A render array. |
74
|
|
|
*/ |
75
|
|
View Code Duplication |
public function build(Request $request, string $type): array { |
|
|
|
|
76
|
|
|
$top = $this->getTop(); |
77
|
|
|
|
78
|
|
|
$rows = $this->getRowData($request, $type); |
79
|
|
|
$main = empty($rows) |
80
|
|
|
? $this->buildEmpty($this->t('No "%type" message found', ['%type' => $type])) |
81
|
|
|
: $this->buildMainTable($rows); |
82
|
|
|
|
83
|
|
|
$ret = $this->buildDefaults($main, $top); |
84
|
|
|
return $ret; |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
/** |
88
|
|
|
* Build the main table. |
89
|
|
|
* |
90
|
|
|
* @param array $rows |
91
|
|
|
* The event data. |
92
|
|
|
* |
93
|
|
|
* @return array |
94
|
|
|
* A render array for the main table. |
95
|
|
|
*/ |
96
|
|
View Code Duplication |
protected function buildMainTable(array $rows): array { |
|
|
|
|
97
|
|
|
$ret = [ |
98
|
|
|
'#header' => $this->buildMainTableHeader(), |
99
|
|
|
'#rows' => $this->buildMainTableRows($rows), |
100
|
|
|
'#type' => 'table', |
101
|
|
|
]; |
102
|
|
|
return $ret; |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
/** |
106
|
|
|
* Build the main table header. |
107
|
|
|
* |
108
|
|
|
* @return \Drupal\Core\StringTranslation\TranslatableMarkup[] |
109
|
|
|
* A table header array. |
110
|
|
|
*/ |
111
|
|
|
protected function buildMainTableHeader(): array { |
112
|
|
|
$header = [ |
113
|
|
|
$this->t('#'), |
114
|
|
|
$this->t('Paths'), |
115
|
|
|
]; |
116
|
|
|
|
117
|
|
|
return $header; |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
/** |
121
|
|
|
* Build the main table rows. |
122
|
|
|
* |
123
|
|
|
* @param array[] $counts |
124
|
|
|
* The array of counts per 403/404 page. |
125
|
|
|
* |
126
|
|
|
* @return array |
127
|
|
|
* A render array for a table. |
128
|
|
|
*/ |
129
|
|
|
protected function buildMainTableRows(array $counts): array { |
130
|
|
|
$rows = []; |
131
|
|
|
/** @var \Drupal\mongodb_watchdog\Controller\TopResult $result */ |
132
|
|
|
foreach ($counts as $result) { |
133
|
|
|
$row = [ |
134
|
|
|
$result->count, |
135
|
|
|
$result->uri, |
136
|
|
|
]; |
137
|
|
|
$rows[] = $row; |
138
|
|
|
} |
139
|
|
|
|
140
|
|
|
return $rows; |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
/** |
144
|
|
|
* {@inheritdoc} |
145
|
|
|
*/ |
146
|
|
View Code Duplication |
public static function create(ContainerInterface $container): self { |
|
|
|
|
147
|
|
|
/** @var \Psr\Log\LoggerInterface $logger */ |
148
|
|
|
$logger = $container->get('logger.channel.mongodb_watchdog'); |
149
|
|
|
|
150
|
|
|
/** @var \Drupal\mongodb_watchdog\Logger $watchdog */ |
151
|
|
|
$watchdog = $container->get(Logger::SERVICE_LOGGER); |
152
|
|
|
|
153
|
|
|
/** @var \Drupal\Core\Config\ImmutableConfig $config */ |
154
|
|
|
$config = $container->get('config.factory')->get('mongodb_watchdog.settings'); |
155
|
|
|
|
156
|
|
|
/** @var \MongoDB\Database $database */ |
157
|
|
|
$database = $container->get('mongodb.watchdog_storage'); |
158
|
|
|
|
159
|
|
|
/** @var \Drupal\Core\Pager\PagerManagerInterface $pagerManager */ |
160
|
|
|
$pagerManager = $container->get('pager.manager'); |
161
|
|
|
|
162
|
|
|
return new static($logger, $watchdog, $config, $database, $pagerManager); |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
/** |
166
|
|
|
* Obtain the data from the logger. |
167
|
|
|
* |
168
|
|
|
* @param \Symfony\Component\HttpFoundation\Request $request |
169
|
|
|
* The current request. Needed for paging. |
170
|
|
|
* @param string $type |
171
|
|
|
* The type of top list to retrieve. |
172
|
|
|
* |
173
|
|
|
* @return array |
174
|
|
|
* The data array. |
175
|
|
|
*/ |
176
|
|
|
protected function getRowData(Request $request, string $type): array { |
177
|
|
|
// Find _id for the error type. |
178
|
|
|
$templateCollection = $this->watchdog->templateCollection(); |
179
|
|
|
$template = $templateCollection->findOne(['type' => $type], ['_id']); |
180
|
|
|
if (empty($template)) { |
181
|
|
|
return []; |
182
|
|
|
} |
183
|
|
|
|
184
|
|
|
// Find occurrences of error type. |
185
|
|
|
$collectionName = $template['_id']; |
186
|
|
|
$eventCollection = $this->watchdog->eventCollection($collectionName); |
187
|
|
|
|
188
|
|
|
$counts = $this->group($eventCollection, 'variables.@uri', []); |
189
|
|
|
|
190
|
|
|
$page = $this->setupPager($request, count($counts)); |
191
|
|
|
$skip = $page * $this->itemsPerPage; |
192
|
|
|
$counts = array_slice($counts, $skip, $this->itemsPerPage); |
193
|
|
|
|
194
|
|
|
return $counts; |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
/** |
198
|
|
|
* Command wrapper for removed MongoDB group() method/command. |
199
|
|
|
* |
200
|
|
|
* @param \MongoDB\Collection $collection |
201
|
|
|
* The collection on which to perform the command. |
202
|
|
|
* @param string $key |
203
|
|
|
* The grouping key. |
204
|
|
|
* @param array $cond |
205
|
|
|
* The condition. |
206
|
|
|
* |
207
|
|
|
* @return array |
208
|
|
|
* An array of stdClass rows with the following properties: |
209
|
|
|
* - _id: the URL |
210
|
|
|
* - count: the number of occurrences. |
211
|
|
|
* It may be empty. |
212
|
|
|
* |
213
|
|
|
* @throws \MongoDB\Driver\Exception\RuntimeException |
214
|
|
|
* @throws \MongoDB\Exception\InvalidArgumentException |
215
|
|
|
* @throws \MongoDB\Exception\UnexpectedValueException |
216
|
|
|
* @throws \MongoDB\Exception\UnsupportedException |
217
|
|
|
*/ |
218
|
|
|
public function group(Collection $collection, string $key, array $cond): array { |
219
|
|
|
$pipeline = []; |
220
|
|
|
if (!empty($cond)) { |
221
|
|
|
$pipeline[] = ['$match' => $cond]; |
222
|
|
|
} |
223
|
|
|
if (!empty($key)) { |
224
|
|
|
$pipeline[] = [ |
225
|
|
|
'$group' => [ |
226
|
|
|
'_id' => "\$${key}", |
227
|
|
|
'count' => ['$sum' => 1], |
228
|
|
|
], |
229
|
|
|
]; |
230
|
|
|
} |
231
|
|
|
$pipeline[] = [ |
232
|
|
|
'$sort' => [ |
233
|
|
|
'count' => -1, |
234
|
|
|
'_id' => 1, |
235
|
|
|
], |
236
|
|
|
]; |
237
|
|
|
|
238
|
|
|
// Aggregate always returns a cursor since MongoDB 3.6. |
239
|
|
|
/** @var \MongoDB\Driver\CursorInterface $res */ |
240
|
|
|
$res = $collection->aggregate($pipeline); |
241
|
|
|
$res->setTypeMap(static::TYPE_MAP); |
242
|
|
|
$ret = $res->toArray(); |
243
|
|
|
return $ret; |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
} |
247
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.