Passed
Pull Request — master (#5629)
by Angel Fernando Quiroz
08:49
created

XApiPlugin::generateLaunchUrl()   A

Complexity

Conditions 6
Paths 3

Size

Total Lines 30
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 14
nc 3
nop 9
dl 0
loc 30
c 1
b 0
f 0
cc 6
rs 9.2222

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Entity\XApiToolLaunch;
6
use Doctrine\ORM\EntityManager;
7
use Doctrine\ORM\Mapping\Driver\SimplifiedXmlDriver;
8
use Doctrine\ORM\ORMException;
9
use GuzzleHttp\RequestOptions;
10
use Http\Adapter\Guzzle6\Client;
11
use Http\Message\MessageFactory\GuzzleMessageFactory;
12
use Symfony\Component\Uid\Uuid;
13
use Xabbuh\XApi\Client\Api\StatementsApiClientInterface;
14
use Xabbuh\XApi\Client\XApiClientBuilder;
15
use Xabbuh\XApi\Model\Agent;
16
use Xabbuh\XApi\Model\IRI;
17
use Xabbuh\XApi\Serializer\Symfony\Serializer;
18
19
/**
20
 * Class XApiPlugin.
21
 */
22
class XApiPlugin extends Plugin implements HookPluginInterface
23
{
24
    public const SETTING_LRS_URL = 'lrs_url';
25
    public const SETTING_LRS_AUTH_USERNAME = 'lrs_auth_username';
26
    public const SETTING_LRS_AUTH_PASSWORD = 'lrs_auth_password';
27
    public const SETTING_CRON_LRS_URL = 'cron_lrs_url';
28
    public const SETTING_CRON_LRS_AUTH_USERNAME = 'cron_lrs_auth_username';
29
    public const SETTING_CRON_LRS_AUTH_PASSWORD = 'cron_lrs_auth_password';
30
    public const SETTING_UUID_NAMESPACE = 'uuid_namespace';
31
    public const SETTING_LRS_LP_ITEM_ACTIVE = 'lrs_lp_item_viewed_active';
32
    public const SETTING_LRS_LP_ACTIVE = 'lrs_lp_end_active';
33
    public const SETTING_LRS_QUIZ_ACTIVE = 'lrs_quiz_active';
34
    public const SETTING_LRS_QUIZ_QUESTION_ACTIVE = 'lrs_quiz_question_active';
35
    public const SETTING_LRS_PORTFOLIO_ACTIVE = 'lrs_portfolio_active';
36
37
    public const STATE_FIRST_LAUNCH = 'first_launch';
38
    public const STATE_LAST_LAUNCH = 'last_launch';
39
40
    /**
41
     * XApiPlugin constructor.
42
     */
43
    protected function __construct()
44
    {
45
        $version = '0.3 (beta)';
46
        $author = [
47
            'Angel Fernando Quiroz Campos <[email protected]>',
48
        ];
49
        $settings = [
50
            self::SETTING_UUID_NAMESPACE => 'text',
51
52
            self::SETTING_LRS_URL => 'text',
53
            self::SETTING_LRS_AUTH_USERNAME => 'text',
54
            self::SETTING_LRS_AUTH_PASSWORD => 'text',
55
56
            self::SETTING_CRON_LRS_URL => 'text',
57
            self::SETTING_CRON_LRS_AUTH_USERNAME => 'text',
58
            self::SETTING_CRON_LRS_AUTH_PASSWORD => 'text',
59
60
            self::SETTING_LRS_LP_ITEM_ACTIVE => 'boolean',
61
            self::SETTING_LRS_LP_ACTIVE => 'boolean',
62
            self::SETTING_LRS_QUIZ_ACTIVE => 'boolean',
63
            self::SETTING_LRS_QUIZ_QUESTION_ACTIVE => 'boolean',
64
            self::SETTING_LRS_PORTFOLIO_ACTIVE => 'boolean',
65
        ];
66
67
        parent::__construct(
68
            $version,
69
            implode(', ', $author),
70
            $settings
71
        );
72
    }
73
74
    /**
75
     * @return \XApiPlugin
76
     */
77
    public static function create()
78
    {
79
        static $result = null;
80
81
        return $result ? $result : $result = new self();
82
    }
83
84
    /**
85
     * Process to install plugin.
86
     */
87
    public function install()
88
    {
89
        $this->installInitialConfig();
90
        $this->addCourseTools();
91
        $this->installHook();
92
    }
93
94
    /**
95
     * Process to uninstall plugin.
96
     */
97
    public function uninstall()
98
    {
99
        $this->uninstallHook();
100
        $this->deleteCourseTools();
101
    }
102
103
    /**
104
     * {@inheritdoc}
105
     */
106
    public function uninstallHook()
107
    {
108
        $learningPathItemViewedHook = XApiLearningPathItemViewedHookObserver::create();
109
        $learningPathEndHook = XApiLearningPathEndHookObserver::create();
110
        $quizQuestionAnsweredHook = XApiQuizQuestionAnsweredHookObserver::create();
111
        $quizEndHook = XApiQuizEndHookObserver::create();
112
        $createCourseHook = XApiCreateCourseHookObserver::create();
113
        $portfolioItemAddedHook = XApiPortfolioItemAddedHookObserver::create();
114
        $portfolioItemCommentedHook = XApiPortfolioItemCommentedHookObserver::create();
115
        $portfolioItemHighlightedHook = XApiPortfolioItemHighlightedHookObserver::create();
116
        $portfolioDownloaded = XApiPortfolioDownloadedHookObserver::create();
117
        $portfolioItemScoredHook = XApiPortfolioItemScoredHookObserver::create();
118
        $portfolioCommentedScoredHook = XApiPortfolioCommentScoredHookObserver::create();
119
        $portfolioItemEditedHook = XApiPortfolioItemEditedHookObserver::create();
120
        $portfolioCommentEditedHook = XApiPortfolioCommentEditedHookObserver::create();
121
122
        HookLearningPathItemViewed::create()->detach($learningPathItemViewedHook);
123
        HookLearningPathEnd::create()->detach($learningPathEndHook);
124
        HookQuizQuestionAnswered::create()->detach($quizQuestionAnsweredHook);
125
        HookQuizEnd::create()->detach($quizEndHook);
126
        HookCreateCourse::create()->detach($createCourseHook);
127
        HookPortfolioItemAdded::create()->detach($portfolioItemAddedHook);
128
        HookPortfolioItemCommented::create()->detach($portfolioItemCommentedHook);
129
        HookPortfolioItemHighlighted::create()->detach($portfolioItemHighlightedHook);
130
        HookPortfolioDownloaded::create()->detach($portfolioDownloaded);
131
        HookPortfolioItemScored::create()->detach($portfolioItemScoredHook);
132
        HookPortfolioCommentScored::create()->detach($portfolioCommentedScoredHook);
133
        HookPortfolioItemEdited::create()->detach($portfolioItemEditedHook);
134
        HookPortfolioCommentEdited::create()->detach($portfolioCommentEditedHook);
135
136
        return 1;
137
    }
138
139
    /**
140
     * @param string|null $lrsUrl
141
     * @param string|null $lrsAuthUsername
142
     * @param string|null $lrsAuthPassword
143
     *
144
     * @return \Xabbuh\XApi\Client\Api\StateApiClientInterface
145
     */
146
    public function getXApiStateClient($lrsUrl = null, $lrsAuthUsername = null, $lrsAuthPassword = null)
147
    {
148
        return $this
149
            ->createXApiClient($lrsUrl, $lrsAuthUsername, $lrsAuthPassword)
150
            ->getStateApiClient();
151
    }
152
153
    public function getXApiStatementClient(): StatementsApiClientInterface
154
    {
155
        return $this->createXApiClient()->getStatementsApiClient();
156
    }
157
158
    public function getXapiStatementCronClient(): StatementsApiClientInterface
159
    {
160
        $lrsUrl = $this->get(self::SETTING_CRON_LRS_URL);
161
        $lrsUsername = $this->get(self::SETTING_CRON_LRS_AUTH_USERNAME);
162
        $lrsPassword = $this->get(self::SETTING_CRON_LRS_AUTH_PASSWORD);
163
164
        return $this
165
            ->createXApiClient(
166
                empty($lrsUrl) ? null : $lrsUrl,
167
                empty($lrsUsername) ? null : $lrsUsername,
168
                empty($lrsPassword) ? null : $lrsPassword
169
            )
170
            ->getStatementsApiClient();
171
    }
172
173
    /**
174
     * Perform actions after save the plugin configuration.
175
     *
176
     * @return \XApiPlugin
177
     */
178
    public function performActionsAfterConfigure()
179
    {
180
        $learningPathItemViewedHook = XApiLearningPathItemViewedHookObserver::create();
181
        $learningPathEndHook = XApiLearningPathEndHookObserver::create();
182
        $quizQuestionAnsweredHook = XApiQuizQuestionAnsweredHookObserver::create();
183
        $quizEndHook = XApiQuizEndHookObserver::create();
184
        $portfolioItemAddedHook = XApiPortfolioItemAddedHookObserver::create();
185
        $portfolioItemCommentedHook = XApiPortfolioItemCommentedHookObserver::create();
186
        $portfolioItemViewedHook = XApiPortfolioItemViewedHookObserver::create();
187
        $portfolioItemHighlightedHook = XApiPortfolioItemHighlightedHookObserver::create();
188
        $portfolioDownloadedHook = XApiPortfolioDownloadedHookObserver::create();
189
        $portfolioItemScoredHook = XApiPortfolioItemScoredHookObserver::create();
190
        $portfolioCommentScoredHook = XApiPortfolioCommentScoredHookObserver::create();
191
        $portfolioItemEditedHook = XApiPortfolioItemEditedHookObserver::create();
192
        $portfolioCommentEditedHook = XApiPortfolioCommentEditedHookObserver::create();
193
194
        $learningPathItemViewedEvent = HookLearningPathItemViewed::create();
195
        $learningPathEndEvent = HookLearningPathEnd::create();
196
        $quizQuestionAnsweredEvent = HookQuizQuestionAnswered::create();
197
        $quizEndEvent = HookQuizEnd::create();
198
        $portfolioItemAddedEvent = HookPortfolioItemAdded::create();
199
        $portfolioItemCommentedEvent = HookPortfolioItemCommented::create();
200
        $portfolioItemViewedEvent = HookPortfolioItemViewed::create();
201
        $portfolioItemHighlightedEvent = HookPortfolioItemHighlighted::create();
202
        $portfolioDownloadedEvent = HookPortfolioDownloaded::create();
203
        $portfolioItemScoredEvent = HookPortfolioItemScored::create();
204
        $portfolioCommentScoredEvent = HookPortfolioCommentScored::create();
205
        $portfolioItemEditedEvent = HookPortfolioItemEdited::create();
206
        $portfolioCommentEditedEvent = HookPortfolioCommentEdited::create();
207
208
        if ('true' === $this->get(self::SETTING_LRS_LP_ITEM_ACTIVE)) {
209
            $learningPathItemViewedEvent->attach($learningPathItemViewedHook);
210
        } else {
211
            $learningPathItemViewedEvent->detach($learningPathItemViewedHook);
212
        }
213
214
        if ('true' === $this->get(self::SETTING_LRS_LP_ACTIVE)) {
215
            $learningPathEndEvent->attach($learningPathEndHook);
216
        } else {
217
            $learningPathEndEvent->detach($learningPathEndHook);
218
        }
219
220
        if ('true' === $this->get(self::SETTING_LRS_QUIZ_ACTIVE)) {
221
            $quizQuestionAnsweredEvent->attach($quizQuestionAnsweredHook);
222
        } else {
223
            $quizQuestionAnsweredEvent->detach($quizQuestionAnsweredHook);
224
        }
225
226
        if ('true' === $this->get(self::SETTING_LRS_QUIZ_QUESTION_ACTIVE)) {
227
            $quizEndEvent->attach($quizEndHook);
228
        } else {
229
            $quizEndEvent->detach($quizEndHook);
230
        }
231
232
        if ('true' === $this->get(self::SETTING_LRS_PORTFOLIO_ACTIVE)) {
233
            $portfolioItemAddedEvent->attach($portfolioItemAddedHook);
234
            $portfolioItemCommentedEvent->attach($portfolioItemCommentedHook);
235
            $portfolioItemViewedEvent->attach($portfolioItemViewedHook);
236
            $portfolioItemHighlightedEvent->attach($portfolioItemHighlightedHook);
237
            $portfolioDownloadedEvent->attach($portfolioDownloadedHook);
238
            $portfolioItemScoredEvent->attach($portfolioItemScoredHook);
239
            $portfolioCommentScoredEvent->attach($portfolioCommentScoredHook);
240
            $portfolioItemEditedEvent->attach($portfolioItemEditedHook);
241
            $portfolioCommentEditedEvent->attach($portfolioCommentEditedHook);
242
        } else {
243
            $portfolioItemAddedEvent->detach($portfolioItemAddedHook);
244
            $portfolioItemCommentedEvent->detach($portfolioItemCommentedHook);
245
            $portfolioItemViewedEvent->detach($portfolioItemViewedHook);
246
            $portfolioItemHighlightedEvent->detach($portfolioItemHighlightedHook);
247
            $portfolioDownloadedEvent->detach($portfolioDownloadedHook);
248
            $portfolioItemScoredEvent->detach($portfolioItemScoredHook);
249
            $portfolioCommentScoredEvent->detach($portfolioCommentScoredHook);
250
            $portfolioItemEditedEvent->detach($portfolioItemEditedHook);
251
            $portfolioCommentEditedEvent->detach($portfolioCommentEditedHook);
252
        }
253
254
        return $this;
255
    }
256
257
    /**
258
     * {@inheritdoc}
259
     */
260
    public function installHook()
261
    {
262
        $createCourseHook = XApiCreateCourseHookObserver::create();
263
264
        HookCreateCourse::create()->attach($createCourseHook);
265
    }
266
267
    /**
268
     * @param string $variable
269
     *
270
     * @return array
271
     */
272
    public function getLangMap($variable)
273
    {
274
        $platformLanguage = api_get_setting('platformLanguage');
275
        $platformLanguageIso = api_get_language_isocode($platformLanguage);
276
277
        $map = [];
278
        $map[$platformLanguageIso] = $this->getLangFromFile($variable, $platformLanguage);
279
280
        try {
281
            $interfaceLanguage = api_get_interface_language();
0 ignored issues
show
Bug introduced by
The function api_get_interface_language was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

281
            $interfaceLanguage = /** @scrutinizer ignore-call */ api_get_interface_language();
Loading history...
282
        } catch (Exception $e) {
283
            return $map;
284
        }
285
286
        if (!empty($interfaceLanguage) && $platformLanguage !== $interfaceLanguage) {
287
            $interfaceLanguageIso = api_get_language_isocode($interfaceLanguage);
288
289
            $map[$interfaceLanguageIso] = $this->getLangFromFile($variable, $interfaceLanguage);
290
        }
291
292
        return $map;
293
    }
294
295
    /**
296
     * @param string $value
297
     * @param string $type
298
     *
299
     * @return \Xabbuh\XApi\Model\IRI
300
     */
301
    public function generateIri($value, $type)
302
    {
303
        return IRI::fromString(
304
            api_get_path(WEB_PATH)."xapi/$type/$value"
305
        );
306
    }
307
308
    /**
309
     * @param int $courseId
310
     */
311
    public function addCourseToolForTinCan($courseId)
312
    {
313
        // The $link param is set to "../plugin" as a hack to link correctly to the plugin URL in course tool.
314
        // Otherwise, the link en the course tool will link to "/main/" URL.
315
        $this->createLinkToCourseTool(
316
            $this->get_lang('ToolTinCan'),
317
            $courseId,
318
            'sessions_category.png',
319
            '../plugin/xapi/start.php',
320
            0,
321
            'authoring'
322
        );
323
    }
324
325
    /**
326
     * @param string $language
327
     *
328
     * @return mixed|string
329
     */
330
    public static function extractVerbInLanguage(Xabbuh\XApi\Model\LanguageMap $languageMap, $language)
331
    {
332
        $iso = self::findLanguageIso($languageMap->languageTags(), $language);
333
334
        $text = current($languageMap);
335
336
        if (isset($languageMap[$iso])) {
337
            $text = trim($languageMap[$iso]);
338
        } elseif (isset($languageMap['und'])) {
339
            $text = $languageMap['und'];
340
        }
341
342
        return $text;
343
    }
344
345
    /**
346
     * @param string $needle
347
     *
348
     * @return string
349
     */
350
    public static function findLanguageIso(array $haystack, $needle)
351
    {
352
        if (in_array($needle, $haystack)) {
353
            return $needle;
354
        }
355
356
        foreach ($haystack as $language) {
357
            if (strpos($language, $needle) === 0) {
358
                return $language;
359
            }
360
        }
361
362
        return $haystack[0];
363
    }
364
365
    public function generateLaunchUrl(
366
        $type,
367
        $launchUrl,
368
        $activityId,
369
        Agent $actor,
370
        $attemptId,
371
        $customLrsUrl = null,
372
        $customLrsUsername = null,
373
        $customLrsPassword = null,
374
        $viewSessionId = null
375
    ) {
376
        $lrsUrl = $customLrsUrl ?: $this->get(self::SETTING_LRS_URL);
377
        $lrsAuthUsername = $customLrsUsername ?: $this->get(self::SETTING_LRS_AUTH_USERNAME);
378
        $lrsAuthPassword = $customLrsPassword ?: $this->get(self::SETTING_LRS_AUTH_PASSWORD);
379
380
        $queryData = [
381
            'endpoint' => trim($lrsUrl, "/ \t\n\r\0\x0B"),
382
            'actor' => Serializer::createSerializer()->serialize($actor, 'json'),
383
            'registration' => $attemptId,
384
        ];
385
386
        if ('tincan' === $type) {
387
            $queryData['auth'] = 'Basic '.base64_encode(trim($lrsAuthUsername).':'.trim($lrsAuthPassword));
388
            $queryData['activity_id'] = $activityId;
389
        } elseif ('cmi5' === $type) {
390
            $queryData['fetch'] = api_get_path(WEB_PLUGIN_PATH).'xapi/cmi5/token.php?session='.$viewSessionId;
391
            $queryData['activityId'] = $activityId;
392
        }
393
394
        return $launchUrl.'?'.http_build_query($queryData, null, '&', PHP_QUERY_RFC3986);
395
    }
396
397
    /**
398
     * @return \Doctrine\ORM\EntityManager|null
399
     */
400
    public static function getEntityManager()
401
    {
402
        $em = Database::getManager();
403
404
        $prefixes = [
405
            __DIR__.'/../php-xapi/repository-doctrine-orm/metadata' => 'XApi\Repository\Doctrine\Mapping',
406
        ];
407
408
        $driver = new SimplifiedXmlDriver($prefixes);
409
        $driver->setGlobalBasename('global');
410
411
        $config = Database::getDoctrineConfig(api_get_configuration_value('root_sys'));
412
        $config->setMetadataDriverImpl($driver);
413
414
        try {
415
            return EntityManager::create($em->getConnection()->getParams(), $config);
416
        } catch (ORMException $e) {
417
            api_not_allowed(true, $e->getMessage());
418
        }
419
420
        return null;
421
    }
422
423
    /**
424
     * {@inheritdoc}
425
     */
426
    public function getAdminUrl()
427
    {
428
        $webPath = api_get_path(WEB_PLUGIN_PATH).$this->get_name();
429
430
        return "$webPath/admin.php";
431
    }
432
433
    public function getLpResourceBlock(int $lpId)
434
    {
435
        $cidReq = api_get_cidreq(true, true, 'lp');
436
        $webPath = api_get_path(WEB_PLUGIN_PATH).'xapi/';
437
        $course = api_get_course_entity();
438
        $session = api_get_session_entity();
439
440
        $tools = Database::getManager()
441
            ->getRepository(XApiToolLaunch::class)
442
            ->findByCourseAndSession($course, $session);
443
444
        $importIcon = Display::return_icon('import_scorm.png');
445
        $moveIcon = Display::url(
446
            Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY),
447
            '#',
448
            ['class' => 'moved']
449
        );
450
451
        $return = '<ul class="lp_resource"><li class="lp_resource_element">'
452
            .$importIcon
453
            .Display::url(
454
                get_lang('Import'),
455
                $webPath."tool_import.php?$cidReq&".http_build_query(['lp_id' => $lpId])
456
            )
457
            .'</li>';
458
459
        foreach ($tools as $tool) {
460
            $toolAnchor = Display::url(
461
                Security::remove_XSS($tool->getTitle()),
462
                api_get_self()."?$cidReq&"
463
                    .http_build_query(
464
                        ['action' => 'add_item', 'type' => TOOL_XAPI, 'file' => $tool->getId(), 'lp_id' => $lpId]
465
                    ),
466
                ['class' => 'moved']
467
            );
468
469
            $return .= Display::tag(
470
                'li',
471
                $moveIcon.$importIcon.$toolAnchor,
472
                [
473
                    'class' => 'lp_resource_element',
474
                    'data_id' => $tool->getId(),
475
                    'data_type' => TOOL_XAPI,
476
                    'title' => $tool->getTitle(),
477
                ]
478
            );
479
        }
480
481
        $return .= '</ul>';
482
483
        return $return;
484
    }
485
486
    /**
487
     * @throws \Exception
488
     */
489
    private function installInitialConfig()
490
    {
491
        $uuidNamespace = Uuid::v1()->toRfc4122();
492
493
        $pluginName = $this->get_name();
494
        $urlId = api_get_current_access_url_id();
495
496
        api_add_setting(
497
            $uuidNamespace,
498
            $pluginName.'_'.self::SETTING_UUID_NAMESPACE,
499
            $pluginName,
500
            'setting',
501
            'Plugins',
502
            $pluginName,
503
            '',
504
            '',
505
            '',
506
            $urlId,
507
            1
508
        );
509
510
        api_add_setting(
511
            api_get_path(WEB_PATH).'plugin/xapi/lrs.php',
512
            $pluginName.'_'.self::SETTING_LRS_URL,
513
            $pluginName,
514
            'setting',
515
            'Plugins',
516
            $pluginName,
517
            '',
518
            '',
519
            '',
520
            $urlId,
521
            1
522
        );
523
    }
524
525
    /**
526
     * @param string|null $lrsUrl
527
     * @param string|null $lrsAuthUsername
528
     * @param string|null $lrsAuthPassword
529
     *
530
     * @return \Xabbuh\XApi\Client\XApiClientInterface
531
     */
532
    private function createXApiClient($lrsUrl = null, $lrsAuthUsername = null, $lrsAuthPassword = null)
533
    {
534
        $baseUrl = $lrsUrl ?: $this->get(self::SETTING_LRS_URL);
535
        $lrsAuthUsername = $lrsAuthUsername ?: $this->get(self::SETTING_LRS_AUTH_USERNAME);
536
        $lrsAuthPassword = $lrsAuthPassword ?: $this->get(self::SETTING_LRS_AUTH_PASSWORD);
537
538
        $clientBuilder = new XApiClientBuilder();
539
        $clientBuilder
540
            ->setHttpClient(Client::createWithConfig([RequestOptions::VERIFY => false]))
541
            ->setRequestFactory(new GuzzleMessageFactory())
542
            ->setBaseUrl(trim($baseUrl, "/ \t\n\r\0\x0B"))
543
            ->setAuth(trim($lrsAuthUsername), trim($lrsAuthPassword));
544
545
        return $clientBuilder->build();
546
    }
547
548
    private function addCourseTools()
549
    {
550
        $courses = Database::getManager()
551
            ->createQuery('SELECT c.id FROM ChamiloCoreBundle:Course c')
552
            ->getResult();
553
554
        foreach ($courses as $course) {
555
            $this->addCourseToolForTinCan($course['id']);
556
        }
557
    }
558
559
    private function deleteCourseTools()
560
    {
561
        Database::getManager()
562
            ->createQuery('DELETE FROM ChamiloCourseBundle:CTool t WHERE t.category = :category AND t.link LIKE :link')
563
            ->execute(['category' => 'authoring', 'link' => '../plugin/xapi/start.php%']);
564
    }
565
}
566