Passed
Push — 1.11.x ( 1b83a3...5737d7 )
by Angel Fernando Quiroz
09:49
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\PluginBundle\Entity\XApi\ActivityProfile;
6
use Chamilo\PluginBundle\Entity\XApi\ActivityState;
7
use Chamilo\PluginBundle\Entity\XApi\Cmi5Item;
8
use Chamilo\PluginBundle\Entity\XApi\InternalLog;
9
use Chamilo\PluginBundle\Entity\XApi\LrsAuth;
10
use Chamilo\PluginBundle\Entity\XApi\SharedStatement;
11
use Chamilo\PluginBundle\Entity\XApi\ToolLaunch;
12
use Doctrine\ORM\EntityManager;
13
use Doctrine\ORM\Mapping\Driver\SimplifiedXmlDriver;
14
use Doctrine\ORM\ORMException;
15
use Doctrine\ORM\Tools\SchemaTool;
16
use GuzzleHttp\RequestOptions;
17
use Http\Adapter\Guzzle6\Client;
18
use Http\Message\MessageFactory\GuzzleMessageFactory;
19
use Ramsey\Uuid\Uuid;
20
use Xabbuh\XApi\Client\XApiClientBuilder;
21
use Xabbuh\XApi\Model\Agent;
22
use Xabbuh\XApi\Model\IRI;
23
use Xabbuh\XApi\Serializer\Symfony\Serializer;
24
25
/**
26
 * Class XApiPlugin.
27
 */
28
class XApiPlugin extends Plugin implements HookPluginInterface
29
{
30
    const SETTING_LRS_URL = 'lrs_url';
31
    const SETTING_LRS_AUTH_USERNAME = 'lrs_auth_username';
32
    const SETTING_LRS_AUTH_PASSWORD = 'lrs_auth_password';
33
    const SETTING_UUID_NAMESPACE = 'uuid_namespace';
34
    const SETTING_LRS_LP_ITEM_ACTIVE = 'lrs_lp_item_viewed_active';
35
    const SETTING_LRS_LP_ACTIVE = 'lrs_lp_end_active';
36
    const SETTING_LRS_QUIZ_ACTIVE = 'lrs_quiz_active';
37
    const SETTING_LRS_QUIZ_QUESTION_ACTIVE = 'lrs_quiz_question_active';
38
    const SETTING_LRS_PORTFOLIO_ACTIVE = 'lrs_portfolio_active';
39
40
    const STATE_FIRST_LAUNCH = 'first_launch';
41
    const STATE_LAST_LAUNCH = 'last_launch';
42
43
    /**
44
     * XApiPlugin constructor.
45
     */
46
    protected function __construct()
47
    {
48
        $version = '0.2 (beta)';
49
        $author = [
50
            'Angel Fernando Quiroz Campos <[email protected]>',
51
        ];
52
        $settings = [
53
            self::SETTING_UUID_NAMESPACE => 'text',
54
55
            self::SETTING_LRS_URL => 'text',
56
            self::SETTING_LRS_AUTH_USERNAME => 'text',
57
            self::SETTING_LRS_AUTH_PASSWORD => 'text',
58
59
            self::SETTING_LRS_LP_ITEM_ACTIVE => 'boolean',
60
            self::SETTING_LRS_LP_ACTIVE => 'boolean',
61
            self::SETTING_LRS_QUIZ_ACTIVE => 'boolean',
62
            self::SETTING_LRS_QUIZ_QUESTION_ACTIVE => 'boolean',
63
            self::SETTING_LRS_PORTFOLIO_ACTIVE => 'boolean',
64
        ];
65
66
        parent::__construct(
67
            $version,
68
            implode(', ', $author),
69
            $settings
70
        );
71
    }
72
73
    /**
74
     * @return \XApiPlugin
75
     */
76
    public static function create()
77
    {
78
        static $result = null;
79
80
        return $result ? $result : $result = new self();
81
    }
82
83
    /**
84
     * Process to install plugin.
85
     */
86
    public function install()
87
    {
88
        $em = Database::getManager();
89
90
        $tablesExists = $em->getConnection()->getSchemaManager()->tablesExist(
91
            [
92
                'xapi_shared_statement',
93
                'xapi_tool_launch',
94
                'xapi_lrs_auth',
95
                'xapi_cmi5_item',
96
                'xapi_activity_state',
97
                'xapi_activity_profile',
98
                'xapi_internal_log',
99
100
                'xapi_attachment',
101
                'xapi_object',
102
                'xapi_result',
103
                'xapi_verb',
104
                'xapi_extensions',
105
                'xapi_context',
106
                'xapi_actor',
107
                'xapi_statement',
108
            ]
109
        );
110
111
        if ($tablesExists) {
112
            return;
113
        }
114
115
        $this->installPluginDbTables();
116
        $this->installInitialConfig();
117
        $this->addCourseTools();
118
        $this->installHook();
119
    }
120
121
    /**
122
     * Process to uninstall plugin.
123
     */
124
    public function uninstall()
125
    {
126
        $this->uninstallHook();
127
        $this->uninstallPluginDbTables();
128
        $this->deleteCourseTools();
129
    }
130
131
    /**
132
     * {@inheritdoc}
133
     */
134
    public function uninstallHook()
135
    {
136
        $learningPathItemViewedHook = XApiLearningPathItemViewedHookObserver::create();
137
        $learningPathEndHook = XApiLearningPathEndHookObserver::create();
138
        $quizQuestionAnsweredHook = XApiQuizQuestionAnsweredHookObserver::create();
139
        $quizEndHook = XApiQuizEndHookObserver::create();
140
        $createCourseHook = XApiCreateCourseHookObserver::create();
141
        $portfolioItemAddedHook = XApiPortfolioItemAddedHookObserver::create();
142
        $portfolioItemCommentedHook = XApiPortfolioItemCommentedHookObserver::create();
143
144
        HookLearningPathItemViewed::create()->detach($learningPathItemViewedHook);
145
        HookLearningPathEnd::create()->detach($learningPathEndHook);
146
        HookQuizQuestionAnswered::create()->detach($quizQuestionAnsweredHook);
147
        HookQuizEnd::create()->detach($quizEndHook);
148
        HookCreateCourse::create()->detach($createCourseHook);
149
        HookPortfolioItemAdded::create()->detach($portfolioItemAddedHook);
150
        HookPortfolioItemCommented::create()->detach($portfolioItemCommentedHook);
151
152
        return 1;
153
    }
154
155
    public function uninstallPluginDbTables()
156
    {
157
        $em = Database::getManager();
158
        $pluginEm = self::getEntityManager();
159
160
        $schemaTool = new SchemaTool($em);
161
        $schemaTool->dropSchema(
162
            [
163
                $em->getClassMetadata(ActivityProfile::class),
164
                $em->getClassMetadata(ActivityState::class),
165
                $em->getClassMetadata(SharedStatement::class),
166
                $em->getClassMetadata(ToolLaunch::class),
167
                $em->getClassMetadata(LrsAuth::class),
168
                $em->getClassMetadata(Cmi5Item::class),
169
                $em->getClassMetadata(InternalLog::class),
170
            ]
171
        );
172
173
        $pluginSchemaTool = new SchemaTool($pluginEm);
174
        $pluginSchemaTool->dropSchema(
175
            [
176
                $pluginEm->getClassMetadata(\XApi\Repository\Doctrine\Mapping\Attachment::class),
177
                $pluginEm->getClassMetadata(\XApi\Repository\Doctrine\Mapping\StatementObject::class),
178
                $pluginEm->getClassMetadata(\XApi\Repository\Doctrine\Mapping\Result::class),
179
                $pluginEm->getClassMetadata(\XApi\Repository\Doctrine\Mapping\Verb::class),
180
                $pluginEm->getClassMetadata(\XApi\Repository\Doctrine\Mapping\Extensions::class),
181
                $pluginEm->getClassMetadata(\XApi\Repository\Doctrine\Mapping\Context::class),
182
                $pluginEm->getClassMetadata(\XApi\Repository\Doctrine\Mapping\Actor::class),
183
                $pluginEm->getClassMetadata(\XApi\Repository\Doctrine\Mapping\Statement::class),
184
            ]
185
        );
186
    }
187
188
    /**
189
     * @param string|null $lrsUrl
190
     * @param string|null $lrsAuthUsername
191
     * @param string|null $lrsAuthPassword
192
     *
193
     * @return \Xabbuh\XApi\Client\Api\StateApiClientInterface
194
     */
195
    public function getXApiStateClient($lrsUrl = null, $lrsAuthUsername = null, $lrsAuthPassword = null)
196
    {
197
        return $this
198
            ->createXApiClient($lrsUrl, $lrsAuthUsername, $lrsAuthPassword)
199
            ->getStateApiClient();
200
    }
201
202
    /**
203
     * @return \Xabbuh\XApi\Client\Api\StatementsApiClientInterface
204
     */
205
    public function getXApiStatementClient()
206
    {
207
        return $this->createXApiClient()->getStatementsApiClient();
208
    }
209
210
    /**
211
     * Perform actions after save the plugin configuration.
212
     *
213
     * @return \XApiPlugin
214
     */
215
    public function performActionsAfterConfigure()
216
    {
217
        $learningPathItemViewedHook = XApiLearningPathItemViewedHookObserver::create();
218
        $learningPathEndHook = XApiLearningPathEndHookObserver::create();
219
        $quizQuestionAnsweredHook = XApiQuizQuestionAnsweredHookObserver::create();
220
        $quizEndHook = XApiQuizEndHookObserver::create();
221
        $portfolioItemAddedHook = XApiPortfolioItemAddedHookObserver::create();
222
        $portfolioItemCommentedHook = XApiPortfolioItemCommentedHookObserver::create();
223
224
        $learningPathItemViewedEvent = HookLearningPathItemViewed::create();
225
        $learningPathEndEvent = HookLearningPathEnd::create();
226
        $quizQuestionAnsweredEvent = HookQuizQuestionAnswered::create();
227
        $quizEndEvent = HookQuizEnd::create();
228
        $portfolioItemAddedEvent = HookPortfolioItemAdded::create();
229
        $portfolioItemCommentedEvent = HookPortfolioItemCommented::create();
230
231
        if ('true' === $this->get(self::SETTING_LRS_LP_ITEM_ACTIVE)) {
232
            $learningPathItemViewedEvent->attach($learningPathItemViewedHook);
233
        } else {
234
            $learningPathItemViewedEvent->detach($learningPathItemViewedHook);
235
        }
236
237
        if ('true' === $this->get(self::SETTING_LRS_LP_ACTIVE)) {
238
            $learningPathEndEvent->attach($learningPathEndHook);
239
        } else {
240
            $learningPathEndEvent->detach($learningPathEndHook);
241
        }
242
243
        if ('true' === $this->get(self::SETTING_LRS_QUIZ_ACTIVE)) {
244
            $quizQuestionAnsweredEvent->attach($quizQuestionAnsweredHook);
245
        } else {
246
            $quizQuestionAnsweredEvent->detach($quizQuestionAnsweredHook);
247
        }
248
249
        if ('true' === $this->get(self::SETTING_LRS_QUIZ_QUESTION_ACTIVE)) {
250
            $quizEndEvent->attach($quizEndHook);
251
        } else {
252
            $quizEndEvent->detach($quizEndHook);
253
        }
254
255
        if ('true' === $this->get(self::SETTING_LRS_PORTFOLIO_ACTIVE)) {
256
            $portfolioItemAddedEvent->attach($portfolioItemAddedHook);
257
            $portfolioItemCommentedEvent->attach($portfolioItemCommentedHook);
258
        } else {
259
            $portfolioItemAddedEvent->detach($portfolioItemAddedHook);
260
            $portfolioItemCommentedEvent->attach($portfolioItemCommentedHook);
261
        }
262
263
        return $this;
264
    }
265
266
    /**
267
     * {@inheritdoc}
268
     */
269
    public function installHook()
270
    {
271
        $createCourseHook = XApiCreateCourseHookObserver::create();
272
273
        HookCreateCourse::create()->attach($createCourseHook);
274
    }
275
276
    /**
277
     * @param string $variable
278
     *
279
     * @return array
280
     */
281
    public function getLangMap($variable)
282
    {
283
        $platformLanguage = api_get_setting('platformLanguage');
284
        $platformLanguageIso = api_get_language_isocode($platformLanguage);
285
286
        $map = [];
287
        $map[$platformLanguageIso] = $this->getLangFromFile($variable, $platformLanguage);
288
289
        try {
290
            $interfaceLanguage = api_get_interface_language();
291
        } catch (Exception $e) {
292
            return $map;
293
        }
294
295
        if (!empty($interfaceLanguage) && $platformLanguage !== $interfaceLanguage) {
296
            $interfaceLanguageIso = api_get_language_isocode($interfaceLanguage);
297
298
            $map[$interfaceLanguageIso] = $this->getLangFromFile($variable, $interfaceLanguage);
299
        }
300
301
        return $map;
302
    }
303
304
    /**
305
     * @param string $value
306
     * @param string $type
307
     *
308
     * @return \Xabbuh\XApi\Model\IRI
309
     */
310
    public function generateIri($value, $type)
311
    {
312
        return IRI::fromString(
313
            api_get_path(WEB_PATH)."xapi/$type/$value"
314
        );
315
    }
316
317
    /**
318
     * @param int $courseId
319
     */
320
    public function addCourseToolForTinCan($courseId)
321
    {
322
        $this->createLinkToCourseTool(
323
            $this->get_lang('ToolTinCan'),
324
            $courseId,
325
            null,
326
            'xapi/start.php'
327
        );
328
    }
329
330
    /**
331
     * @param string $language
332
     *
333
     * @return mixed|string
334
     */
335
    public static function extractVerbInLanguage(Xabbuh\XApi\Model\LanguageMap $languageMap, $language)
336
    {
337
        $iso = self::findLanguageIso($languageMap->languageTags(), $language);
338
339
        $text = current($languageMap);
340
341
        if (isset($languageMap[$iso])) {
342
            $text = trim($languageMap[$iso]);
343
        } elseif (isset($languageMap['und'])) {
344
            $text = $languageMap['und'];
345
        }
346
347
        return $text;
348
    }
349
350
    /**
351
     * @param string $needle
352
     *
353
     * @return string
354
     */
355
    public static function findLanguageIso(array $haystack, $needle)
356
    {
357
        if (in_array($needle, $haystack)) {
358
            return $needle;
359
        }
360
361
        foreach ($haystack as $language) {
362
            if (strpos($language, $needle) === 0) {
363
                return $language;
364
            }
365
        }
366
367
        return $haystack[0];
368
    }
369
370
    public function generateLaunchUrl(
371
        $type,
372
        $launchUrl,
373
        $activityId,
374
        Agent $actor,
375
        $attemptId,
376
        $customLrsUrl = null,
377
        $customLrsUsername = null,
378
        $customLrsPassword = null,
379
        $viewSessionId = null
380
    ) {
381
        $lrsUrl = $customLrsUrl ?: $this->get(self::SETTING_LRS_URL);
382
        $lrsAuthUsername = $customLrsUsername ?: $this->get(self::SETTING_LRS_AUTH_USERNAME);
383
        $lrsAuthPassword = $customLrsPassword ?: $this->get(self::SETTING_LRS_AUTH_PASSWORD);
384
385
        $queryData = [
386
            'endpoint' => trim($lrsUrl, "/ \t\n\r\0\x0B"),
387
            'actor' => Serializer::createSerializer()->serialize($actor, 'json'),
388
            'registration' => $attemptId,
389
        ];
390
391
        if ('tincan' === $type) {
392
            $queryData['auth'] = 'Basic '.base64_encode(trim($lrsAuthUsername).':'.trim($lrsAuthPassword));
393
            $queryData['activity_id'] = $activityId;
394
        } elseif ('cmi5' === $type) {
395
            $queryData['fetch'] = api_get_path(WEB_PLUGIN_PATH).'xapi/cmi5/token.php?session='.$viewSessionId;
396
            $queryData['activityId'] = $activityId;
397
        }
398
399
        return $launchUrl.'?'.http_build_query($queryData, null, '&', PHP_QUERY_RFC3986);
400
    }
401
402
    /**
403
     * @return \Doctrine\ORM\EntityManager|null
404
     */
405
    public static function getEntityManager()
406
    {
407
        $em = Database::getManager();
408
409
        $prefixes = [
410
            __DIR__.'/../php-xapi/repository-doctrine-orm/metadata' => 'XApi\Repository\Doctrine\Mapping',
411
        ];
412
413
        $driver = new SimplifiedXmlDriver($prefixes);
414
        $driver->setGlobalBasename('global');
415
416
        $config = Database::getDoctrineConfig(api_get_configuration_value('root_sys'));
417
        $config->setMetadataDriverImpl($driver);
418
419
        try {
420
            return EntityManager::create($em->getConnection()->getParams(), $config);
421
        } catch (ORMException $e) {
422
            api_not_allowed(true, $e->getMessage());
423
        }
424
425
        return null;
426
    }
427
428
    /**
429
     * {@inheritdoc}
430
     */
431
    public function getAdminUrl()
432
    {
433
        $webPath = api_get_path(WEB_PLUGIN_PATH).$this->get_name();
434
435
        return "$webPath/admin.php";
436
    }
437
438
    public function getLpResourceBlock(int $lpId)
439
    {
440
        $cidReq = api_get_cidreq(true, true, 'lp');
441
        $webPath = api_get_path(WEB_PLUGIN_PATH).'xapi/';
442
        $course = api_get_course_entity();
443
        $session = api_get_session_entity();
444
445
        $tools = Database::getManager()
446
            ->getRepository(ToolLaunch::class)
447
            ->findByCourseAndSession($course, $session);
448
449
        $importIcon = Display::return_icon('import_scorm.png');
450
        $moveIcon = Display::url(
451
            Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY),
452
            '#',
453
            ['class' => 'moved']
454
        );
455
456
        $return = '<ul class="lp_resource"><li class="lp_resource_element">'
457
            .$importIcon
458
            .Display::url(
459
                get_lang('Import'),
460
                $webPath."tool_import.php?$cidReq&".http_build_query(['lp_id' => $lpId])
461
            )
462
            .'</li>';
463
464
        /** @var ToolLaunch $tool */
465
        foreach ($tools as $tool) {
466
            $toolAnchor = Display::url(
467
                Security::remove_XSS($tool->getTitle()),
468
                api_get_self()."?$cidReq&"
469
                    .http_build_query(
470
                        ['action' => 'add_item', 'type' => TOOL_XAPI, 'file' => $tool->getId(), 'lp_id' => $lpId]
471
                    ),
472
                ['class' => 'moved']
473
            );
474
475
            $return .= Display::tag(
476
                'li',
477
                $moveIcon.$importIcon.$toolAnchor,
478
                [
479
                    'class' => 'lp_resource_element',
480
                    'data_id' => $tool->getId(),
481
                    'data_type' => TOOL_XAPI,
482
                    'title' => $tool->getTitle(),
483
                ]
484
            );
485
        }
486
487
        $return .= '</ul>';
488
489
        return $return;
490
    }
491
492
    /**
493
     * @throws \Doctrine\ORM\Tools\ToolsException
494
     */
495
    private function installPluginDbTables()
496
    {
497
        $em = Database::getManager();
498
        $pluginEm = self::getEntityManager();
499
500
        $schemaTool = new SchemaTool($em);
501
        $schemaTool->createSchema(
502
            [
503
                $em->getClassMetadata(SharedStatement::class),
504
                $em->getClassMetadata(ToolLaunch::class),
505
                $em->getClassMetadata(LrsAuth::class),
506
                $em->getClassMetadata(Cmi5Item::class),
507
                $em->getClassMetadata(ActivityState::class),
508
                $em->getClassMetadata(ActivityProfile::class),
509
                $em->getClassMetadata(InternalLog::class),
510
            ]
511
        );
512
513
        $pluginSchemaTool = new SchemaTool($pluginEm);
514
        $pluginSchemaTool->createSchema(
515
            [
516
                $pluginEm->getClassMetadata(\XApi\Repository\Doctrine\Mapping\Attachment::class),
517
                $pluginEm->getClassMetadata(\XApi\Repository\Doctrine\Mapping\StatementObject::class),
518
                $pluginEm->getClassMetadata(\XApi\Repository\Doctrine\Mapping\Result::class),
519
                $pluginEm->getClassMetadata(\XApi\Repository\Doctrine\Mapping\Verb::class),
520
                $pluginEm->getClassMetadata(\XApi\Repository\Doctrine\Mapping\Extensions::class),
521
                $pluginEm->getClassMetadata(\XApi\Repository\Doctrine\Mapping\Context::class),
522
                $pluginEm->getClassMetadata(\XApi\Repository\Doctrine\Mapping\Actor::class),
523
                $pluginEm->getClassMetadata(\XApi\Repository\Doctrine\Mapping\Statement::class),
524
            ]
525
        );
526
    }
527
528
    /**
529
     * @throws \Exception
530
     */
531
    private function installInitialConfig()
532
    {
533
        $uuidNamespace = Uuid::uuid1();
534
535
        $pluginName = $this->get_name();
536
        $urlId = api_get_current_access_url_id();
537
538
        api_add_setting(
539
            $uuidNamespace,
540
            $pluginName.'_'.self::SETTING_UUID_NAMESPACE,
541
            $pluginName,
542
            'setting',
543
            'Plugins',
544
            $pluginName,
545
            '',
546
            '',
547
            '',
548
            $urlId,
549
            1
550
        );
551
552
        api_add_setting(
553
            api_get_path(WEB_PATH).'plugin/xapi/lrs.php',
554
            $pluginName.'_'.self::SETTING_LRS_URL,
555
            $pluginName,
556
            'setting',
557
            'Plugins',
558
            $pluginName,
559
            '',
560
            '',
561
            '',
562
            $urlId,
563
            1
564
        );
565
    }
566
567
    /**
568
     * @param string|null $lrsUrl
569
     * @param string|null $lrsAuthUsername
570
     * @param string|null $lrsAuthPassword
571
     *
572
     * @return \Xabbuh\XApi\Client\XApiClientInterface
573
     */
574
    private function createXApiClient($lrsUrl = null, $lrsAuthUsername = null, $lrsAuthPassword = null)
575
    {
576
        $baseUrl = $lrsUrl ?: $this->get(self::SETTING_LRS_URL);
577
        $lrsAuthUsername = $lrsAuthUsername ?: $this->get(self::SETTING_LRS_AUTH_USERNAME);
578
        $lrsAuthPassword = $lrsAuthPassword ?: $this->get(self::SETTING_LRS_AUTH_PASSWORD);
579
580
        $clientBuilder = new XApiClientBuilder();
581
        $clientBuilder
582
            ->setHttpClient(Client::createWithConfig([RequestOptions::VERIFY => false]))
583
            ->setRequestFactory(new GuzzleMessageFactory())
584
            ->setBaseUrl(trim($baseUrl, "/ \t\n\r\0\x0B"))
585
            ->setAuth(trim($lrsAuthUsername), trim($lrsAuthPassword));
586
587
        return $clientBuilder->build();
588
    }
589
590
    private function addCourseTools()
591
    {
592
        $courses = Database::getManager()
593
            ->createQuery('SELECT c.id FROM ChamiloCoreBundle:Course c')
594
            ->getResult();
595
596
        foreach ($courses as $course) {
597
            $this->addCourseToolForTinCan($course['id']);
598
        }
599
    }
600
601
    private function deleteCourseTools()
602
    {
603
        Database::getManager()
604
            ->createQuery('DELETE FROM ChamiloCourseBundle:CTool t WHERE t.category = :category AND t.link LIKE :link')
605
            ->execute(['category' => 'plugin', 'link' => 'xapi/start.php%']);
606
    }
607
}
608