Passed
Pull Request — master (#405)
by
unknown
05:48
created

DashboardController   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 393
Duplicated Lines 0 %

Test Coverage

Coverage 13.95%

Importance

Changes 4
Bugs 0 Features 0
Metric Value
eloc 211
c 4
b 0
f 0
dl 0
loc 393
ccs 24
cts 172
cp 0.1395
rs 9.76
wmc 33

12 Methods

Rating   Name   Duplication   Size   Complexity  
A formatStat() 0 7 2
A search() 0 31 3
A getShortcuts() 0 33 4
A __construct() 0 14 1
A getRepository() 0 3 1
A getDrafts() 0 17 1
A getLoggedInUserActivities() 0 5 1
A index() 0 33 2
A getFacts() 0 47 4
B getPeriodStats() 0 76 5
A getAllActivities() 0 5 1
B formatActivity() 0 22 8
1
<?php
2
3
namespace A17\Twill\Http\Controllers\Admin;
4
5
use A17\Twill\Models\Behaviors\HasMedias;
6
use Analytics;
0 ignored issues
show
Bug introduced by
The type Analytics was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
7
use Illuminate\Config\Repository as Config;
8
use Illuminate\Contracts\Auth\Factory as AuthFactory;
9
use Illuminate\Contracts\Foundation\Application;
10
use Illuminate\Http\Request;
11
use Illuminate\Support\Collection;
12
use Illuminate\Support\Str;
13
use Illuminate\View\Factory as ViewFactory;
14
use Psr\Log\LoggerInterface as Logger;
15
use Spatie\Activitylog\Models\Activity;
16
use Spatie\Analytics\Exceptions\InvalidConfiguration;
17
use Spatie\Analytics\Period;
18
19
class DashboardController extends Controller
20
{
21
    /**
22
     * @var Application
23
     */
24
    protected $app;
25
26
    /**
27
     * @var Config
28
     */
29
    protected $config;
30
31
    /**
32
     * @var Logger
33
     */
34
    protected $logger;
35
36
    /**
37
     * @var ViewFactory
38
     */
39
    protected $viewFactory;
40
41
    /**
42
     * @var AuthFactory
43
     */
44
    protected $authFactory;
45
46 47
    public function __construct(
47
        Application $app,
48
        Config $config,
49
        Logger $logger,
50
        ViewFactory $viewFactory,
51
        AuthFactory $authFactory
52
    ) {
53 47
        parent::__construct();
54
55 47
        $this->app = $app;
56 47
        $this->config = $config;
57 47
        $this->logger = $logger;
58 47
        $this->viewFactory = $viewFactory;
59 47
        $this->authFactory = $authFactory;
60 47
    }
61
62
    /**
63
     * Displays the Twill dashboard.
64
     *
65
     * @return \Illuminate\View\View
66
     */
67 46
    public function index()
68
    {
69 46
        $modules = Collection::make($this->config->get('twill.dashboard.modules'));
70
71 46
        return $this->viewFactory->make('twill::layouts.dashboard', [
72 46
            'allActivityData' => $this->getAllActivities(),
73 46
            'myActivityData' => $this->getLoggedInUserActivities(),
74
            'tableColumns' => [
75
                [
76
                    'name' => 'thumbnail',
77
                    'label' => 'Thumbnail',
78
                    'visible' => true,
79
                    'optional' => false,
80
                    'sortable' => false,
81
                ],
82
                [
83
                    'name' => 'published',
84
                    'label' => 'Published',
85
                    'visible' => true,
86
                    'optional' => false,
87
                    'sortable' => false,
88
                ],
89
                [
90
                    'name' => 'name',
91
                    'label' => 'Name',
92
                    'visible' => true,
93
                    'optional' => false,
94
                    'sortable' => true,
95
                ],
96
            ],
97 46
            'shortcuts' => $this->getShortcuts($modules),
0 ignored issues
show
Bug introduced by
$modules of type Illuminate\Support\Collection is incompatible with the type array expected by parameter $modules of A17\Twill\Http\Controlle...troller::getShortcuts(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

97
            'shortcuts' => $this->getShortcuts(/** @scrutinizer ignore-type */ $modules),
Loading history...
98 46
            'facts' => $this->config->get('twill.dashboard.analytics.enabled', false) ? $this->getFacts() : null,
99 46
            'drafts' => $this->getDrafts($modules),
0 ignored issues
show
Bug introduced by
$modules of type Illuminate\Support\Collection is incompatible with the type array expected by parameter $modules of A17\Twill\Http\Controlle...Controller::getDrafts(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

99
            'drafts' => $this->getDrafts(/** @scrutinizer ignore-type */ $modules),
Loading history...
100
        ]);
101
    }
102
103
    /**
104
     * @param Request $request
105
     * @return Collection
106
     */
107
    public function search(Request $request)
108
    {
109
        $modules = Collection::make($this->config->get('twill.dashboard.modules'));
110
111
        return $modules->filter(function ($module) {
112
            return ($module['search'] ?? false);
113
        })->map(function ($module) use ($request) {
114
            $repository = $this->getRepository($module['name']);
115
116
            $found = $repository->cmsSearch($request->get('search'), $module['search_fields'] ?? ['title'])->take(10);
117
118
            return $found->map(function ($item) use ($module) {
119
                try {
120
                    $author = $item->revisions()->latest()->first()->user->name ?? 'Admin';
121
                } catch (\Exception $e) {
122
                    $author = 'Admin';
123
                }
124
125
                return [
126
                    'id' => $item->id,
127
                    'href' => moduleRoute($module['name'], $module['routePrefix'] ?? null, 'edit', $item->id),
128
                    'thumbnail' => method_exists($item, 'defaultCmsImage') ? $item->defaultCmsImage(['w' => 100, 'h' => 100]) : null,
129
                    'published' => $item->published,
130
                    'activity' => 'Last edited',
131
                    'date' => $item->updated_at->toIso8601String(),
132
                    'title' => $item->titleInDashboard ?? $item->title,
133
                    'author' => $author,
134
                    'type' => ucfirst($module['label_singular'] ?? Str::singular($module['name'])),
135
                ];
136
            });
137
        })->collapse()->values();
138
    }
139
140
    /**
141
     * @return array
142
     */
143 46
    private function getAllActivities()
144
    {
145
        return Activity::take(20)->latest()->get()->map(function ($activity) {
146
            return $this->formatActivity($activity);
147 46
        })->filter()->values();
148
    }
149
150
    /**
151
     * @return array
152
     */
153 46
    private function getLoggedInUserActivities()
154
    {
155
        return Activity::where('causer_id', $this->authFactory->guard('twill_users')->user()->id)->take(20)->latest()->get()->map(function ($activity) {
0 ignored issues
show
Bug introduced by
Accessing id on the interface Illuminate\Contracts\Auth\Authenticatable suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
156
            return $this->formatActivity($activity);
157 46
        })->filter()->values();
158
    }
159
160
    /**
161
     * @param \Spatie\Activitylog\Models\Activity $activity
162
     * @return array|null
163
     */
164
    private function formatActivity($activity)
165
    {
166
        $dashboardModule = $this->config->get('twill.dashboard.modules.' . $activity->subject_type);
0 ignored issues
show
Bug introduced by
The property subject_type does not seem to exist on Spatie\Activitylog\Models\Activity. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
167
168
        if (!$dashboardModule || !$dashboardModule['activity'] ?? false) {
169
            return null;
170
        }
171
172
        return [
173
            'id' => $activity->id,
174
            'type' => ucfirst($dashboardModule['label_singular'] ?? Str::singular($dashboardModule['name'])),
175
            'date' => $activity->created_at->toIso8601String(),
0 ignored issues
show
Bug introduced by
The method toIso8601String() does not exist on DateTime. It seems like you code against a sub-type of DateTime such as Carbon\Carbon. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

175
            'date' => $activity->created_at->/** @scrutinizer ignore-call */ toIso8601String(),
Loading history...
176
            'author' => $activity->causer->name ?? 'Unknown',
177
            'name' => $activity->subject->titleInDashboard ?? $activity->subject->title,
0 ignored issues
show
Bug introduced by
The property subject does not seem to exist on Spatie\Activitylog\Models\Activity. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
178
            'activity' => ucfirst($activity->description),
0 ignored issues
show
Bug introduced by
The property description does not seem to exist on Spatie\Activitylog\Models\Activity. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
179
        ] + (classHasTrait($activity->subject, HasMedias::class) ? [
180
            'thumbnail' => $activity->subject->defaultCmsImage(['w' => 100, 'h' => 100]),
181
        ] : []) + (!$activity->subject->trashed() ? [
182
            'edit' => moduleRoute($dashboardModule['name'], $dashboardModule['routePrefix'] ?? null, 'edit', $activity->subject_id),
0 ignored issues
show
Bug introduced by
The property subject_id does not seem to exist on Spatie\Activitylog\Models\Activity. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
183
        ] : []) + (!is_null($activity->subject->published) ? [
184
            'published' => $activity->description === 'published' ? true : ($activity->description === 'unpublished' ? false : $activity->subject->published),
185
        ] : []);
186
    }
187
188
    /**
189
     * @return array|\Illuminate\Support\Collection
190
     */
191
    private function getFacts()
192
    {
193
        try {
194
            $response = Analytics::performQuery(
195
                Period::days(60),
196
                'ga:users,ga:pageviews,ga:bouncerate,ga:pageviewsPerSession',
197
                ['dimensions' => 'ga:date']
198
            );
199
        } catch (InvalidConfiguration $exception) {
200
            $this->logger->error($exception);
201
            return [];
202
        }
203
204
        $statsByDate = Collection::make($response['rows'] ?? [])->map(function (array $dateRow) {
205
            return [
206
                'date' => $dateRow[0],
207
                'users' => (int) $dateRow[1],
208
                'pageViews' => (int) $dateRow[2],
209
                'bounceRate' => $dateRow[3],
210
                'pageviewsPerSession' => $dateRow[4],
211
            ];
212
        })->reverse()->values();
213
214
        return Collection::make([
215
            'today',
216
            'yesterday',
217
            'week',
218
            'month',
219
        ])->mapWithKeys(function ($period) use ($statsByDate) {
220
            $stats = $this->getPeriodStats($period, $statsByDate);
0 ignored issues
show
Bug introduced by
$statsByDate of type Illuminate\Support\Collection is incompatible with the type Illuminate\Database\Query\Builder expected by parameter $statsByDate of A17\Twill\Http\Controlle...oller::getPeriodStats(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

220
            $stats = $this->getPeriodStats($period, /** @scrutinizer ignore-type */ $statsByDate);
Loading history...
221
            return [
222
                $period => [
223
                    [
224
                        'label' => 'Users',
225
                        'figure' => $this->formatStat($stats['stats']['users']),
226
                        'insight' => round($stats['stats']['bounceRate']) . '% Bounce rate',
227
                        'trend' => $stats['moreUsers'] ? 'up' : 'down',
228
                        'data' => $stats['usersData']->reverse()->values()->toArray(),
229
                        'url' => 'https://analytics.google.com/analytics/web',
230
                    ],
231
                    [
232
                        'label' => 'Pageviews',
233
                        'figure' => $this->formatStat($stats['stats']['pageViews']),
234
                        'insight' => round($stats['stats']['pageviewsPerSession'], 1) . ' Pages / Session',
235
                        'trend' => $stats['morePageViews'] ? 'up' : 'down',
236
                        'data' => $stats['pageViewsData']->reverse()->values()->toArray(),
237
                        'url' => 'https://analytics.google.com/analytics/web',
238
                    ],
239
                ],
240
            ];
241
        });
242
    }
243
244
    /**
245
     * @param string $period
246
     * @param \Illuminate\Database\Query\Builder $statsByDate
247
     * @return array
248
     */
249
    private function getPeriodStats($period, $statsByDate)
250
    {
251
        if ($period === 'today') {
252
            return [
253
                'stats' => $stats = $statsByDate->first(),
254
                'moreUsers' => $stats['users'] > $statsByDate->get(1)['users'],
255
                'morePageViews' => $stats['pageViews'] > $statsByDate->get(1)['pageViews'],
256
                'usersData' => $statsByDate->take(7)->map(function ($stat) {
257
                    return $stat['users'];
258
                }),
259
                'pageViewsData' => $statsByDate->take(7)->map(function ($stat) {
260
                    return $stat['pageViews'];
261
                }),
262
            ];
263
        } elseif ($period === 'yesterday') {
264
            return [
265
                'stats' => $stats = $statsByDate->get(1),
266
                'moreUsers' => $stats['users'] > $statsByDate->get(2)['users'],
267
                'morePageViews' => $stats['pageViews'] > $statsByDate->get(2)['pageViews'],
268
                'usersData' => $statsByDate->slice(1)->take(7)->map(function ($stat) {
269
                    return $stat['users'];
270
                }),
271
                'pageViewsData' => $statsByDate->slice(1)->take(7)->map(function ($stat) {
272
                    return $stat['pageViews'];
273
                }),
274
            ];
275
        } elseif ($period === 'week') {
276
            $first7stats = $statsByDate->take(7)->all();
277
278
            $stats = [
279
                'users' => array_sum(array_column($first7stats, 'users')),
280
                'pageViews' => array_sum(array_column($first7stats, 'pageViews')),
281
                'bounceRate' => array_sum(array_column($first7stats, 'bounceRate')) / 7,
282
                'pageviewsPerSession' => array_sum(array_column($first7stats, 'pageviewsPerSession')) / 7,
283
            ];
284
285
            $compareStats = [
286
                'users' => array_sum(array_column($statsByDate->slice(7)->take(7)->all(), 'users')),
287
                'pageViews' => array_sum(array_column($statsByDate->slice(7)->take(7)->all(), 'pageViews')),
288
            ];
289
290
            return [
291
                'stats' => $stats,
292
                'moreUsers' => $stats['users'] > $compareStats['users'],
293
                'morePageViews' => $stats['pageViews'] > $compareStats['pageViews'],
294
                'usersData' => $statsByDate->slice(1)->take(29)->map(function ($stat) {
295
                    return $stat['users'];
296
                }),
297
                'pageViewsData' => $statsByDate->slice(1)->take(29)->map(function ($stat) {
298
                    return $stat['pageViews'];
299
                }),
300
            ];
301
        } elseif ($period === 'month') {
302
            $first30stats = $statsByDate->take(30)->all();
303
304
            $stats = [
305
                'users' => array_sum(array_column($first30stats, 'users')),
306
                'pageViews' => array_sum(array_column($first30stats, 'pageViews')),
307
                'bounceRate' => array_sum(array_column($first30stats, 'bounceRate')) / 30,
308
                'pageviewsPerSession' => array_sum(array_column($first30stats, 'pageviewsPerSession')) / 30,
309
            ];
310
311
            $compareStats = [
312
                'users' => array_sum(array_column($statsByDate->slice(30)->take(30)->all(), 'users')),
313
                'pageViews' => array_sum(array_column($statsByDate->slice(30)->take(30)->all(), 'pageViews')),
314
            ];
315
316
            return [
317
                'stats' => $stats,
318
                'moreUsers' => $stats['users'] > $compareStats['users'],
319
                'morePageViews' => $stats['pageViews'] > $compareStats['pageViews'],
320
                'usersData' => $statsByDate->slice(1)->take(29)->map(function ($stat) {
321
                    return $stat['users'];
322
                }),
323
                'pageViewsData' => $statsByDate->slice(1)->take(29)->map(function ($stat) {
324
                    return $stat['pageViews'];
325
                }),
326
            ];
327
        }
328
    }
329
330
    /**
331
     * @param int $count
332
     * @return string
333
     */
334
    private function formatStat($count)
335
    {
336
        if ($count >= 1000) {
337
            return round($count / 1000, 1) . "k";
338
        }
339
340
        return $count;
341
    }
342
343
    /**
344
     * @param array $modules
345
     * @return array
346
     */
347 46
    private function getShortcuts($modules)
348
    {
349
        return $modules->filter(function ($module) {
350
            return ($module['count'] ?? false) || ($module['create'] ?? false);
351
        })->map(function ($module) {
352
            $repository = $this->getRepository($module['name']);
353
354
            $moduleOptions = [
355
                'count' => $module['count'] ?? false,
356
                'create' => $module['create'] ?? false,
357
                'label' => $module['label'] ?? $module['name'],
358
                'singular' => $module['label_singular'] ?? Str::singular($module['name']),
359
            ];
360
361
            return [
362
                'label' => ucfirst($moduleOptions['label']),
363
                'singular' => ucfirst($moduleOptions['singular']),
364
                'number' => $moduleOptions['count'] ? $repository->getCountByStatusSlug(
365
                    'all', $module['countScope'] ?? []
366
                ) : null,
367
                'url' => moduleRoute(
368
                    $module['name'],
369
                    $module['routePrefix'] ?? null,
370
                    'index'
371
                ),
372
                'createUrl' => $moduleOptions['create'] ? moduleRoute(
373
                    $module['name'],
374
                    $module['routePrefix'] ?? null,
375
                    'index',
376
                    ['openCreate' => true]
377
                ) : null
378
            ];
379 46
        })->values();
380
    }
381
382
    /**
383
     * @param array $modules
384
     * @return array
385
     */
386 46
    private function getDrafts($modules)
387
    {
388
        return $modules->filter(function ($module) {
389
            return ($module['draft'] ?? false);
390
        })->map(function ($module) {
391
            $repository = $this->getRepository($module['name']);
392
393
            $drafts = $repository->draft()->mine()->limit(3)->latest()->get();
0 ignored issues
show
Bug introduced by
The method draft() does not exist on A17\Twill\Repositories\ModuleRepository. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

393
            $drafts = $repository->/** @scrutinizer ignore-call */ draft()->mine()->limit(3)->latest()->get();
Loading history...
394
395
            return $drafts->map(function ($draft) use ($module) {
396
                return [
397
                    'type' => ucfirst($module['label_singular'] ?? Str::singular($module['name'])),
398
                    'name' => $draft->titleInDashboard ?? $draft->title,
399
                    'url' => moduleRoute($module['name'], $module['routePrefix'] ?? null, 'edit', $draft->id),
400
                ];
401
            });
402 46
        })->collapse()->values();
403
    }
404
405
    /**
406
     * @param string $module
407
     * @return \A17\Twill\Repositories\ModuleRepository
408
     */
409
    private function getRepository($module)
410
    {
411
        return $this->app->make($this->config->get('twill.namespace') . "\Repositories\\" . ucfirst(Str::singular($module)) . "Repository");
412
    }
413
}
414