Passed
Push — master ( 212654...8aa330 )
by Angel Fernando Quiroz
11:04
created

XApiPlugin::findLanguageIso()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 6
nc 4
nop 2
dl 0
loc 13
rs 10
c 0
b 0
f 0
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
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
    }
92
93
    /**
94
     * Process to uninstall plugin.
95
     */
96
    public function uninstall()
97
    {
98
        $this->deleteCourseTools();
99
    }
100
101
    /**
102
     * @param string|null $lrsUrl
103
     * @param string|null $lrsAuthUsername
104
     * @param string|null $lrsAuthPassword
105
     *
106
     * @return \Xabbuh\XApi\Client\Api\StateApiClientInterface
107
     */
108
    public function getXApiStateClient($lrsUrl = null, $lrsAuthUsername = null, $lrsAuthPassword = null)
109
    {
110
        return $this
111
            ->createXApiClient($lrsUrl, $lrsAuthUsername, $lrsAuthPassword)
112
            ->getStateApiClient();
113
    }
114
115
    public function getXApiStatementClient(): StatementsApiClientInterface
116
    {
117
        return $this->createXApiClient()->getStatementsApiClient();
118
    }
119
120
    public function getXapiStatementCronClient(): StatementsApiClientInterface
121
    {
122
        $lrsUrl = $this->get(self::SETTING_CRON_LRS_URL);
123
        $lrsUsername = $this->get(self::SETTING_CRON_LRS_AUTH_USERNAME);
124
        $lrsPassword = $this->get(self::SETTING_CRON_LRS_AUTH_PASSWORD);
125
126
        return $this
127
            ->createXApiClient(
128
                empty($lrsUrl) ? null : $lrsUrl,
129
                empty($lrsUsername) ? null : $lrsUsername,
130
                empty($lrsPassword) ? null : $lrsPassword
131
            )
132
            ->getStatementsApiClient();
133
    }
134
135
    /**
136
     * @param string $variable
137
     *
138
     * @return array
139
     */
140
    public function getLangMap($variable)
141
    {
142
        $platformLanguage = api_get_setting('platformLanguage');
143
        $platformLanguageIso = api_get_language_isocode($platformLanguage);
144
145
        $map = [];
146
        $map[$platformLanguageIso] = $this->getLangFromFile($variable, $platformLanguage);
147
148
        try {
149
            $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

149
            $interfaceLanguage = /** @scrutinizer ignore-call */ api_get_interface_language();
Loading history...
150
        } catch (Exception $e) {
151
            return $map;
152
        }
153
154
        if (!empty($interfaceLanguage) && $platformLanguage !== $interfaceLanguage) {
155
            $interfaceLanguageIso = api_get_language_isocode($interfaceLanguage);
156
157
            $map[$interfaceLanguageIso] = $this->getLangFromFile($variable, $interfaceLanguage);
158
        }
159
160
        return $map;
161
    }
162
163
    /**
164
     * @param string $value
165
     * @param string $type
166
     *
167
     * @return \Xabbuh\XApi\Model\IRI
168
     */
169
    public function generateIri($value, $type)
170
    {
171
        return IRI::fromString(
172
            api_get_path(WEB_PATH)."xapi/$type/$value"
173
        );
174
    }
175
176
    /**
177
     * @param int $courseId
178
     */
179
    public function addCourseToolForTinCan($courseId)
180
    {
181
        // The $link param is set to "../plugin" as a hack to link correctly to the plugin URL in course tool.
182
        // Otherwise, the link en the course tool will link to "/main/" URL.
183
        $this->createLinkToCourseTool(
184
            $this->get_lang('ToolTinCan'),
185
            $courseId,
186
            'sessions_category.png',
187
            '../plugin/xapi/start.php',
188
            0,
189
            'authoring'
190
        );
191
    }
192
193
    /**
194
     * @param string $language
195
     *
196
     * @return mixed|string
197
     */
198
    public static function extractVerbInLanguage(Xabbuh\XApi\Model\LanguageMap $languageMap, $language)
199
    {
200
        $iso = self::findLanguageIso($languageMap->languageTags(), $language);
201
202
        $text = current($languageMap);
203
204
        if (isset($languageMap[$iso])) {
205
            $text = trim($languageMap[$iso]);
206
        } elseif (isset($languageMap['und'])) {
207
            $text = $languageMap['und'];
208
        }
209
210
        return $text;
211
    }
212
213
    /**
214
     * @param string $needle
215
     *
216
     * @return string
217
     */
218
    public static function findLanguageIso(array $haystack, $needle)
219
    {
220
        if (in_array($needle, $haystack)) {
221
            return $needle;
222
        }
223
224
        foreach ($haystack as $language) {
225
            if (strpos($language, $needle) === 0) {
226
                return $language;
227
            }
228
        }
229
230
        return $haystack[0];
231
    }
232
233
    public function generateLaunchUrl(
234
        $type,
235
        $launchUrl,
236
        $activityId,
237
        Agent $actor,
238
        $attemptId,
239
        $customLrsUrl = null,
240
        $customLrsUsername = null,
241
        $customLrsPassword = null,
242
        $viewSessionId = null
243
    ) {
244
        $lrsUrl = $customLrsUrl ?: $this->get(self::SETTING_LRS_URL);
245
        $lrsAuthUsername = $customLrsUsername ?: $this->get(self::SETTING_LRS_AUTH_USERNAME);
246
        $lrsAuthPassword = $customLrsPassword ?: $this->get(self::SETTING_LRS_AUTH_PASSWORD);
247
248
        $queryData = [
249
            'endpoint' => trim($lrsUrl, "/ \t\n\r\0\x0B"),
250
            'actor' => Serializer::createSerializer()->serialize($actor, 'json'),
251
            'registration' => $attemptId,
252
        ];
253
254
        if ('tincan' === $type) {
255
            $queryData['auth'] = 'Basic '.base64_encode(trim($lrsAuthUsername).':'.trim($lrsAuthPassword));
256
            $queryData['activity_id'] = $activityId;
257
        } elseif ('cmi5' === $type) {
258
            $queryData['fetch'] = api_get_path(WEB_PLUGIN_PATH).'xapi/cmi5/token.php?session='.$viewSessionId;
259
            $queryData['activityId'] = $activityId;
260
        }
261
262
        return $launchUrl.'?'.http_build_query($queryData, null, '&', PHP_QUERY_RFC3986);
263
    }
264
265
    /**
266
     * @return \Doctrine\ORM\EntityManager|null
267
     */
268
    public static function getEntityManager()
269
    {
270
        $em = Database::getManager();
271
272
        $prefixes = [
273
            __DIR__.'/../php-xapi/repository-doctrine-orm/metadata' => 'XApi\Repository\Doctrine\Mapping',
274
        ];
275
276
        $driver = new SimplifiedXmlDriver($prefixes);
277
        $driver->setGlobalBasename('global');
278
279
        $config = Database::getDoctrineConfig(api_get_configuration_value('root_sys'));
280
        $config->setMetadataDriverImpl($driver);
281
282
        try {
283
            return EntityManager::create($em->getConnection()->getParams(), $config);
284
        } catch (ORMException $e) {
285
            api_not_allowed(true, $e->getMessage());
286
        }
287
288
        return null;
289
    }
290
291
    /**
292
     * {@inheritdoc}
293
     */
294
    public function getAdminUrl()
295
    {
296
        $webPath = api_get_path(WEB_PLUGIN_PATH).$this->get_name();
297
298
        return "$webPath/admin.php";
299
    }
300
301
    public function getLpResourceBlock(int $lpId)
302
    {
303
        $cidReq = api_get_cidreq(true, true, 'lp');
304
        $webPath = api_get_path(WEB_PLUGIN_PATH).'xapi/';
305
        $course = api_get_course_entity();
306
        $session = api_get_session_entity();
307
308
        $tools = Database::getManager()
309
            ->getRepository(XApiToolLaunch::class)
310
            ->findByCourseAndSession($course, $session);
311
312
        $importIcon = Display::return_icon('import_scorm.png');
313
        $moveIcon = Display::url(
314
            Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY),
315
            '#',
316
            ['class' => 'moved']
317
        );
318
319
        $return = '<ul class="lp_resource"><li class="lp_resource_element">'
320
            .$importIcon
321
            .Display::url(
322
                get_lang('Import'),
323
                $webPath."tool_import.php?$cidReq&".http_build_query(['lp_id' => $lpId])
324
            )
325
            .'</li>';
326
327
        foreach ($tools as $tool) {
328
            $toolAnchor = Display::url(
329
                Security::remove_XSS($tool->getTitle()),
330
                api_get_self()."?$cidReq&"
331
                    .http_build_query(
332
                        ['action' => 'add_item', 'type' => TOOL_XAPI, 'file' => $tool->getId(), 'lp_id' => $lpId]
333
                    ),
334
                ['class' => 'moved']
335
            );
336
337
            $return .= Display::tag(
338
                'li',
339
                $moveIcon.$importIcon.$toolAnchor,
340
                [
341
                    'class' => 'lp_resource_element',
342
                    'data_id' => $tool->getId(),
343
                    'data_type' => TOOL_XAPI,
344
                    'title' => $tool->getTitle(),
345
                ]
346
            );
347
        }
348
349
        $return .= '</ul>';
350
351
        return $return;
352
    }
353
354
    /**
355
     * @throws \Exception
356
     */
357
    private function installInitialConfig()
358
    {
359
        $uuidNamespace = Uuid::v1()->toRfc4122();
360
361
        $pluginName = $this->get_name();
362
        $urlId = api_get_current_access_url_id();
363
364
        api_add_setting(
365
            $uuidNamespace,
366
            $pluginName.'_'.self::SETTING_UUID_NAMESPACE,
367
            $pluginName,
368
            'setting',
369
            'Plugins',
370
            $pluginName,
371
            '',
372
            '',
373
            '',
374
            $urlId,
375
            1
376
        );
377
378
        api_add_setting(
379
            api_get_path(WEB_PATH).'plugin/xapi/lrs.php',
380
            $pluginName.'_'.self::SETTING_LRS_URL,
381
            $pluginName,
382
            'setting',
383
            'Plugins',
384
            $pluginName,
385
            '',
386
            '',
387
            '',
388
            $urlId,
389
            1
390
        );
391
    }
392
393
    /**
394
     * @param string|null $lrsUrl
395
     * @param string|null $lrsAuthUsername
396
     * @param string|null $lrsAuthPassword
397
     *
398
     * @return \Xabbuh\XApi\Client\XApiClientInterface
399
     */
400
    private function createXApiClient($lrsUrl = null, $lrsAuthUsername = null, $lrsAuthPassword = null)
401
    {
402
        $baseUrl = $lrsUrl ?: $this->get(self::SETTING_LRS_URL);
403
        $lrsAuthUsername = $lrsAuthUsername ?: $this->get(self::SETTING_LRS_AUTH_USERNAME);
404
        $lrsAuthPassword = $lrsAuthPassword ?: $this->get(self::SETTING_LRS_AUTH_PASSWORD);
405
406
        $clientBuilder = new XApiClientBuilder();
407
        $clientBuilder
408
            ->setHttpClient(Client::createWithConfig([RequestOptions::VERIFY => false]))
409
            ->setRequestFactory(new GuzzleMessageFactory())
410
            ->setBaseUrl(trim($baseUrl, "/ \t\n\r\0\x0B"))
411
            ->setAuth(trim($lrsAuthUsername), trim($lrsAuthPassword));
412
413
        return $clientBuilder->build();
414
    }
415
416
    private function addCourseTools()
417
    {
418
        $courses = Database::getManager()
419
            ->createQuery('SELECT c.id FROM ChamiloCoreBundle:Course c')
420
            ->getResult();
421
422
        foreach ($courses as $course) {
423
            $this->addCourseToolForTinCan($course['id']);
424
        }
425
    }
426
427
    private function deleteCourseTools()
428
    {
429
        Database::getManager()
430
            ->createQuery('DELETE FROM ChamiloCourseBundle:CTool t WHERE t.category = :category AND t.link LIKE :link')
431
            ->execute(['category' => 'authoring', 'link' => '../plugin/xapi/start.php%']);
432
    }
433
}
434