Passed
Pull Request — master (#6127)
by Angel Fernando Quiroz
08:00
created

XApiPlugin::generateLaunchUrl()   A

Complexity

Conditions 6
Paths 3

Size

Total Lines 30
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

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

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 Chamilo\CoreBundle\Event\Interfaces\PluginEventSubscriberInterface;
7
use Doctrine\ORM\EntityManager;
8
use Doctrine\ORM\Mapping\Driver\SimplifiedXmlDriver;
9
use Doctrine\ORM\ORMException;
10
use GuzzleHttp\RequestOptions;
11
use Http\Adapter\Guzzle6\Client;
12
use Http\Message\MessageFactory\GuzzleMessageFactory;
13
use Symfony\Component\Uid\Uuid;
14
use Xabbuh\XApi\Client\Api\StatementsApiClientInterface;
15
use Xabbuh\XApi\Client\XApiClientBuilder;
16
use Xabbuh\XApi\Model\Agent;
17
use Xabbuh\XApi\Model\IRI;
18
use Xabbuh\XApi\Serializer\Symfony\Serializer;
19
20
/**
21
 * Class XApiPlugin.
22
 */
23
class XApiPlugin extends Plugin implements PluginEventSubscriberInterface
24
{
25
    public const SETTING_LRS_URL = 'lrs_url';
26
    public const SETTING_LRS_AUTH_USERNAME = 'lrs_auth_username';
27
    public const SETTING_LRS_AUTH_PASSWORD = 'lrs_auth_password';
28
    public const SETTING_CRON_LRS_URL = 'cron_lrs_url';
29
    public const SETTING_CRON_LRS_AUTH_USERNAME = 'cron_lrs_auth_username';
30
    public const SETTING_CRON_LRS_AUTH_PASSWORD = 'cron_lrs_auth_password';
31
    public const SETTING_UUID_NAMESPACE = 'uuid_namespace';
32
    public const SETTING_LRS_LP_ITEM_ACTIVE = 'lrs_lp_item_viewed_active';
33
    public const SETTING_LRS_LP_ACTIVE = 'lrs_lp_end_active';
34
    public const SETTING_LRS_QUIZ_ACTIVE = 'lrs_quiz_active';
35
    public const SETTING_LRS_QUIZ_QUESTION_ACTIVE = 'lrs_quiz_question_active';
36
    public const SETTING_LRS_PORTFOLIO_ACTIVE = 'lrs_portfolio_active';
37
38
    public const STATE_FIRST_LAUNCH = 'first_launch';
39
    public const STATE_LAST_LAUNCH = 'last_launch';
40
41
    /**
42
     * XApiPlugin constructor.
43
     */
44
    protected function __construct()
45
    {
46
        $version = '0.3 (beta)';
47
        $author = [
48
            'Angel Fernando Quiroz Campos <[email protected]>',
49
        ];
50
        $settings = [
51
            self::SETTING_UUID_NAMESPACE => 'text',
52
53
            self::SETTING_LRS_URL => 'text',
54
            self::SETTING_LRS_AUTH_USERNAME => 'text',
55
            self::SETTING_LRS_AUTH_PASSWORD => 'text',
56
57
            self::SETTING_CRON_LRS_URL => 'text',
58
            self::SETTING_CRON_LRS_AUTH_USERNAME => 'text',
59
            self::SETTING_CRON_LRS_AUTH_PASSWORD => 'text',
60
61
            self::SETTING_LRS_LP_ITEM_ACTIVE => 'boolean',
62
            self::SETTING_LRS_LP_ACTIVE => 'boolean',
63
            self::SETTING_LRS_QUIZ_ACTIVE => 'boolean',
64
            self::SETTING_LRS_QUIZ_QUESTION_ACTIVE => 'boolean',
65
            self::SETTING_LRS_PORTFOLIO_ACTIVE => 'boolean',
66
        ];
67
68
        parent::__construct(
69
            $version,
70
            implode(', ', $author),
71
            $settings
72
        );
73
    }
74
75
    /**
76
     * @return \XApiPlugin
77
     */
78
    public static function create()
79
    {
80
        static $result = null;
81
82
        return $result ? $result : $result = new self();
83
    }
84
85
    /**
86
     * Process to install plugin.
87
     */
88
    public function install()
89
    {
90
        $this->installInitialConfig();
91
        $this->addCourseTools();
92
        $this->installEventSubscribers();
93
    }
94
95
    /**
96
     * Process to uninstall plugin.
97
     */
98
    public function uninstall()
99
    {
100
        $this->uninstallEventSubscribers();
101
        $this->deleteCourseTools();
102
    }
103
104
    /**
105
     * {@inheritdoc}
106
     */
107
    public function uninstallEventSubscribers(): void
108
    {
109
        //@todo detach XApiCreateCourseEventSubscriber
110
        //@todo detach XApiEventSubscriber::onLpItemViewed
111
        //@todo detach XApiEventSubscriber::onLpEnded
112
        //@todo detach XApiEventSubscriber::onExerciseQuestionAnswered
113
        //@todo detach XApiEventSubscriber::onExerciseEnded
114
        //@todo detach XApiEventSubscriber::onPortfolioItemAdded
115
        //@todo detach XApiEventSubscriber::onPortfolioItemEdited
116
        //@todo detach XApiEventSubscriber::onPortfolioItemViewed
117
        //@todo detach XApiEventSubscriber::onPortfolioItemCommented
118
        //@todo detach XApiEventSubscriber::onPortfolioItemHighlighted
119
        //@todo detach XApiEventSubscriber::onPortfolioItemDownloaded
120
        //@todo detach XApiEventSubscriber::onPortfolioItemScored
121
        //@todo detach XApiEventSubscriber::onPortfolioCommentScored
122
        //@todo detach XApiEventSubscriber::onPortfolioCommentEdited
123
    }
124
125
    /**
126
     * @param string|null $lrsUrl
127
     * @param string|null $lrsAuthUsername
128
     * @param string|null $lrsAuthPassword
129
     *
130
     * @return \Xabbuh\XApi\Client\Api\StateApiClientInterface
131
     */
132
    public function getXApiStateClient($lrsUrl = null, $lrsAuthUsername = null, $lrsAuthPassword = null)
133
    {
134
        return $this
135
            ->createXApiClient($lrsUrl, $lrsAuthUsername, $lrsAuthPassword)
136
            ->getStateApiClient();
137
    }
138
139
    public function getXApiStatementClient(): StatementsApiClientInterface
140
    {
141
        return $this->createXApiClient()->getStatementsApiClient();
142
    }
143
144
    public function getXapiStatementCronClient(): StatementsApiClientInterface
145
    {
146
        $lrsUrl = $this->get(self::SETTING_CRON_LRS_URL);
147
        $lrsUsername = $this->get(self::SETTING_CRON_LRS_AUTH_USERNAME);
148
        $lrsPassword = $this->get(self::SETTING_CRON_LRS_AUTH_PASSWORD);
149
150
        return $this
151
            ->createXApiClient(
152
                empty($lrsUrl) ? null : $lrsUrl,
153
                empty($lrsUsername) ? null : $lrsUsername,
154
                empty($lrsPassword) ? null : $lrsPassword
155
            )
156
            ->getStatementsApiClient();
157
    }
158
159
    /**
160
     * Perform actions after save the plugin configuration.
161
     *
162
     * @return \XApiPlugin
163
     */
164
    public function performActionsAfterConfigure()
165
    {
166
        if ('true' === $this->get(self::SETTING_LRS_LP_ITEM_ACTIVE)) {
167
            //@todo attach XApiEventSubscriber::onLpItemViewed
168
        } else {
169
            //@todo detach XApiEventSubscriber::onLpItemViewed
170
        }
171
172
        if ('true' === $this->get(self::SETTING_LRS_LP_ACTIVE)) {
173
            //@todo attach XApiEventSubscriber::onLpEnded
174
        } else {
175
            //@todo detach XApiEventSubscriber::onLpEnded
176
        }
177
178
        if ('true' === $this->get(self::SETTING_LRS_QUIZ_ACTIVE)) {
179
            //@todo attach XApiEventSubscriber::onExerciseQuestionAnswered
180
        } else {
181
            //@todo detach XApiEventSubscriber::onExerciseQuestionAnswered
182
        }
183
184
        if ('true' === $this->get(self::SETTING_LRS_QUIZ_QUESTION_ACTIVE)) {
185
            //@todo attach XApiEventSubscriber::onExerciseEnded
186
        } else {
187
            //@todo detach XApiEventSubscriber::onExerciseEnded
188
        }
189
190
        if ('true' === $this->get(self::SETTING_LRS_PORTFOLIO_ACTIVE)) {
191
            //@todo attach XApiEventSubscriber::onPortfolioItemAdded
192
            //@todo attach XApiEventSubscriber::onPortfolioItemEdited
193
            //@todo attach XApiEventSubscriber::onPortfolioItemViewed
194
            //@todo attach XApiEventSubscriber::onPortfolioItemCommented
195
            //@todo attach XApiEventSubscriber::onPortfolioItemHighlighted
196
            //@todo attach XApiEventSubscriber::onPortfolioItemDownloaded
197
            //@todo attach XApiEventSubscriber::onPortfolioItemScored
198
            //@todo attach XApiEventSubscriber::onPortfolioCommentScored
199
            //@todo attach XApiEventSubscriber::onPortfolioCommentEdited
200
        } else {
201
            //@todo detach XApiEventSubscriber::onPortfolioItemAdded
202
            //@todo detach XApiEventSubscriber::onPortfolioItemEdited
203
            //@todo detach XApiEventSubscriber::onPortfolioItemViewed
204
            //@todo detach XApiEventSubscriber::onPortfolioItemCommented
205
            //@todo detach XApiEventSubscriber::onPortfolioItemHighlighted
206
            //@todo detach XApiEventSubscriber::onPortfolioItemDownloaded
207
            //@todo detach XApiEventSubscriber::onPortfolioItemScored
208
            //@todo detach XApiEventSubscriber::onPortfolioCommentScored
209
            //@todo detach XApiEventSubscriber::onPortfolioCommentEdited
210
        }
211
212
        return $this;
213
    }
214
215
    /**
216
     * {@inheritdoc}
217
     */
218
    public function installEventSubscribers(): void
219
    {
220
        //@todo attach XApiCreateCourseEventSubscriber
221
    }
222
223
    /**
224
     * @param string $variable
225
     *
226
     * @return array
227
     */
228
    public function getLangMap($variable)
229
    {
230
        $platformLanguage = api_get_setting('platformLanguage');
231
        $platformLanguageIso = api_get_language_isocode($platformLanguage);
232
233
        $map = [];
234
        $map[$platformLanguageIso] = $this->getLangFromFile($variable, $platformLanguage);
235
236
        try {
237
            $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

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