Passed
Pull Request — master (#6127)
by Angel Fernando Quiroz
08:24
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 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
        //@todo detach XApiCreateCourseEventSubscriber
109
        //@todo detach XApiEventSubscriber::onLpItemViewed
110
        //@todo detach XApiEventSubscriber::onLpEnded
111
        //@todo detach XApiEventSubscriber::onExerciseQuestionAnswered
112
        //@todo detach XApiEventSubscriber::onExerciseEnded
113
        //@todo detach XApiEventSubscriber::onPortfolioItemAdded
114
        //@todo detach XApiEventSubscriber::onPortfolioItemEdited
115
        //@todo detach XApiEventSubscriber::onPortfolioItemViewed
116
        //@todo detach XApiEventSubscriber::onPortfolioItemCommented
117
        //@todo detach XApiEventSubscriber::onPortfolioItemHighlighted
118
        //@todo detach XApiEventSubscriber::onPortfolioItemDownloaded
119
        //@todo detach XApiEventSubscriber::onPortfolioItemScored
120
        //@todo detach XApiEventSubscriber::onPortfolioCommentScored
121
        //@todo detach XApiEventSubscriber::onPortfolioCommentEdited
122
123
        return 1;
124
    }
125
126
    /**
127
     * @param string|null $lrsUrl
128
     * @param string|null $lrsAuthUsername
129
     * @param string|null $lrsAuthPassword
130
     *
131
     * @return \Xabbuh\XApi\Client\Api\StateApiClientInterface
132
     */
133
    public function getXApiStateClient($lrsUrl = null, $lrsAuthUsername = null, $lrsAuthPassword = null)
134
    {
135
        return $this
136
            ->createXApiClient($lrsUrl, $lrsAuthUsername, $lrsAuthPassword)
137
            ->getStateApiClient();
138
    }
139
140
    public function getXApiStatementClient(): StatementsApiClientInterface
141
    {
142
        return $this->createXApiClient()->getStatementsApiClient();
143
    }
144
145
    public function getXapiStatementCronClient(): StatementsApiClientInterface
146
    {
147
        $lrsUrl = $this->get(self::SETTING_CRON_LRS_URL);
148
        $lrsUsername = $this->get(self::SETTING_CRON_LRS_AUTH_USERNAME);
149
        $lrsPassword = $this->get(self::SETTING_CRON_LRS_AUTH_PASSWORD);
150
151
        return $this
152
            ->createXApiClient(
153
                empty($lrsUrl) ? null : $lrsUrl,
154
                empty($lrsUsername) ? null : $lrsUsername,
155
                empty($lrsPassword) ? null : $lrsPassword
156
            )
157
            ->getStatementsApiClient();
158
    }
159
160
    /**
161
     * Perform actions after save the plugin configuration.
162
     *
163
     * @return \XApiPlugin
164
     */
165
    public function performActionsAfterConfigure()
166
    {
167
        if ('true' === $this->get(self::SETTING_LRS_LP_ITEM_ACTIVE)) {
168
            //@todo attach XApiEventSubscriber::onLpItemViewed
169
        } else {
170
            //@todo detach XApiEventSubscriber::onLpItemViewed
171
        }
172
173
        if ('true' === $this->get(self::SETTING_LRS_LP_ACTIVE)) {
174
            //@todo attach XApiEventSubscriber::onLpEnded
175
        } else {
176
            //@todo detach XApiEventSubscriber::onLpEnded
177
        }
178
179
        if ('true' === $this->get(self::SETTING_LRS_QUIZ_ACTIVE)) {
180
            //@todo attach XApiEventSubscriber::onExerciseQuestionAnswered
181
        } else {
182
            //@todo detach XApiEventSubscriber::onExerciseQuestionAnswered
183
        }
184
185
        if ('true' === $this->get(self::SETTING_LRS_QUIZ_QUESTION_ACTIVE)) {
186
            //@todo attach XApiEventSubscriber::onExerciseEnded
187
        } else {
188
            //@todo detach XApiEventSubscriber::onExerciseEnded
189
        }
190
191
        if ('true' === $this->get(self::SETTING_LRS_PORTFOLIO_ACTIVE)) {
192
            //@todo attach XApiEventSubscriber::onPortfolioItemAdded
193
            //@todo attach XApiEventSubscriber::onPortfolioItemEdited
194
            //@todo attach XApiEventSubscriber::onPortfolioItemViewed
195
            //@todo attach XApiEventSubscriber::onPortfolioItemCommented
196
            //@todo attach XApiEventSubscriber::onPortfolioItemHighlighted
197
            //@todo attach XApiEventSubscriber::onPortfolioItemDownloaded
198
            //@todo attach XApiEventSubscriber::onPortfolioItemScored
199
            //@todo attach XApiEventSubscriber::onPortfolioCommentScored
200
            //@todo attach XApiEventSubscriber::onPortfolioCommentEdited
201
        } else {
202
            //@todo detach XApiEventSubscriber::onPortfolioItemAdded
203
            //@todo detach XApiEventSubscriber::onPortfolioItemEdited
204
            //@todo detach XApiEventSubscriber::onPortfolioItemViewed
205
            //@todo detach XApiEventSubscriber::onPortfolioItemCommented
206
            //@todo detach XApiEventSubscriber::onPortfolioItemHighlighted
207
            //@todo detach XApiEventSubscriber::onPortfolioItemDownloaded
208
            //@todo detach XApiEventSubscriber::onPortfolioItemScored
209
            //@todo detach XApiEventSubscriber::onPortfolioCommentScored
210
            //@todo detach XApiEventSubscriber::onPortfolioCommentEdited
211
        }
212
213
        return $this;
214
    }
215
216
    /**
217
     * {@inheritdoc}
218
     */
219
    public function installHook()
220
    {
221
        //@todo attach XApiCreateCourseEventSubscriber
222
    }
223
224
    /**
225
     * @param string $variable
226
     *
227
     * @return array
228
     */
229
    public function getLangMap($variable)
230
    {
231
        $platformLanguage = api_get_setting('platformLanguage');
232
        $platformLanguageIso = api_get_language_isocode($platformLanguage);
233
234
        $map = [];
235
        $map[$platformLanguageIso] = $this->getLangFromFile($variable, $platformLanguage);
236
237
        try {
238
            $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

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