Skip to content

Commit 1627e1b

Browse files
Merge pull request #8516 from christianbeeznest/fixes-plugin-mobidico01
Plugin: Adapt Mobidico launcher to new version
2 parents 531256b + c4438be commit 1627e1b

11 files changed

Lines changed: 548 additions & 77 deletions

File tree

public/main/admin/settings.lib.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,7 @@ function getStablePluginAllowList(): array
351351
'CleanDeletedFiles',
352352
'Dashboard',
353353
'ExtraMenuFromWebservice',
354+
'Mobidico',
354355
];
355356
}
356357

Lines changed: 205 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,234 @@
11
<?php
22

3+
/* For licensing terms, see /license.txt */
4+
5+
use Chamilo\CourseBundle\Entity\CTool;
6+
use Symfony\Component\HttpClient\HttpClient;
7+
use Symfony\Contracts\HttpClient\Exception\ExceptionInterface;
8+
39
class Mobidico extends Plugin
410
{
511
public $isCoursePlugin = true;
612

7-
// When creating a new course this settings are added to the course
13+
public $addCourseTool = true;
14+
815
public $course_settings = [];
916

1017
protected function __construct()
1118
{
1219
parent::__construct(
13-
'0.1',
20+
'0.2',
1421
'Julio Montoya',
1522
[
16-
'api_key' => 'text',
1723
'mobidico_url' => 'text',
24+
'api_key' => 'text',
25+
'request_timeout' => 'text',
26+
'verify_ssl' => 'boolean',
1827
]
1928
);
2029
}
2130

22-
/**
23-
* @return Mobidico|null
24-
*/
25-
public static function create()
31+
public static function create(): self
2632
{
2733
static $result = null;
2834

29-
return $result ? $result : $result = new self();
35+
return $result ?: $result = new self();
36+
}
37+
38+
public function install(): void
39+
{
40+
$this->syncCourseTools();
41+
}
42+
43+
public function uninstall(): void
44+
{
45+
$this->removeInvalidCourseToolShortcuts();
46+
$this->uninstall_course_fields_in_all_courses();
47+
}
48+
49+
public function performActionsAfterConfigure()
50+
{
51+
if ($this->isEnabled()) {
52+
$this->syncCourseTools();
53+
}
54+
55+
return $this;
56+
}
57+
58+
public function syncCourseTools(): void
59+
{
60+
try {
61+
/*
62+
* Rebuild the Mobidico course-tool rows instead of only adding missing rows.
63+
* Some older plugin iterations could leave a c_tool row without a valid
64+
* resource link, which makes it invisible in the Chamilo 2 course home.
65+
* Reinstalling the plugin course fields through the base Plugin API keeps
66+
* the same pattern used by other legacy course plugins.
67+
*/
68+
$this->removeInvalidCourseToolShortcuts();
69+
$this->uninstall_course_fields_in_all_courses();
70+
$this->install_course_fields_in_all_courses(true);
71+
} catch (Throwable $exception) {
72+
error_log('[Mobidico] Course tool synchronization failed: '.$exception->getMessage());
73+
}
74+
}
75+
76+
public function getLaunchUrl(int $userId): ?string
77+
{
78+
$baseUrl = $this->getBaseUrl();
79+
$apiKey = $this->getApiKey();
80+
81+
if ('' === $baseUrl || '' === $apiKey || 0 >= $userId) {
82+
return null;
83+
}
84+
85+
$session = $this->requestRemoteSession($baseUrl, $apiKey, $userId);
86+
87+
if (null === $session) {
88+
return null;
89+
}
90+
91+
return $baseUrl.'/app/index.html?session='.rawurlencode($session);
92+
}
93+
94+
public function getBaseUrl(): string
95+
{
96+
$url = trim((string) $this->get('mobidico_url'));
97+
$url = rtrim($url, "/ \t\n\r\0\x0B");
98+
99+
if ('' === $url) {
100+
return '';
101+
}
102+
103+
$parts = parse_url($url);
104+
105+
if (
106+
false === $parts
107+
|| empty($parts['scheme'])
108+
|| empty($parts['host'])
109+
|| !in_array(strtolower((string) $parts['scheme']), ['http', 'https'], true)
110+
) {
111+
return '';
112+
}
113+
114+
return $url;
115+
}
116+
117+
public function getApiKey(): string
118+
{
119+
return trim((string) $this->get('api_key'));
120+
}
121+
122+
public function getRequestTimeout(): float
123+
{
124+
$timeout = (float) $this->get('request_timeout');
125+
126+
if (0.0 >= $timeout) {
127+
return 5.0;
128+
}
129+
130+
return min($timeout, 30.0);
131+
}
132+
133+
public function shouldVerifySsl(): bool
134+
{
135+
$value = $this->get('verify_ssl');
136+
137+
if (null === $value || '' === $value) {
138+
return true;
139+
}
140+
141+
return in_array($value, ['1', 1, true, 'true', 'yes', 'on'], true);
30142
}
31143

32-
public function install()
144+
private function removeInvalidCourseToolShortcuts(): void
33145
{
146+
try {
147+
$connection = Database::getManager()->getConnection();
148+
149+
/*
150+
* Older development builds wrongly created CShortcut rows pointing to the
151+
* Mobidico CTool resource node. Those rows render as /r/course_tool/links/{id}/link
152+
* and fail because course-tool resources are not CLink resources.
153+
*
154+
* Delete by relation, not only by title, because some rows can have a
155+
* translated or suffixed title while still pointing to the Mobidico CTool.
156+
*/
157+
$deletedRows = $connection->executeStatement(
158+
<<<'SQL'
159+
DELETE s
160+
FROM c_shortcut s
161+
LEFT JOIN c_tool ct ON ct.resource_node_id = s.shortcut_node_id
162+
LEFT JOIN tool t ON t.id = ct.tool_id
163+
WHERE LOWER(TRIM(s.title)) LIKE 'mobidico%'
164+
OR LOWER(TRIM(ct.title)) LIKE 'mobidico%'
165+
OR LOWER(TRIM(t.title)) = 'mobidico'
166+
SQL
167+
);
168+
169+
if ($deletedRows > 0) {
170+
error_log('[Mobidico] Removed '.$deletedRows.' invalid course-tool shortcut(s).');
171+
}
172+
} catch (Throwable $exception) {
173+
error_log('[Mobidico] Invalid shortcut cleanup failed: '.$exception->getMessage());
174+
}
34175
}
35176

36-
public function uninstall()
177+
private function requestRemoteSession(string $baseUrl, string $apiKey, int $userId): ?string
37178
{
179+
$authenticateUrl = $baseUrl.'/app/desktop/php/authenticate.php';
180+
181+
try {
182+
$client = HttpClient::create([
183+
'timeout' => $this->getRequestTimeout(),
184+
'verify_peer' => $this->shouldVerifySsl(),
185+
'verify_host' => $this->shouldVerifySsl(),
186+
]);
187+
188+
$response = $client->request('POST', $authenticateUrl, [
189+
'body' => [
190+
'chamiloid' => $userId,
191+
'API_KEY' => $apiKey,
192+
],
193+
]);
194+
195+
if (200 !== $response->getStatusCode()) {
196+
error_log('[Mobidico] Authentication failed with HTTP status '.$response->getStatusCode());
197+
198+
return null;
199+
}
200+
201+
$payload = json_decode($response->getContent(false), true);
202+
203+
if (!is_array($payload)) {
204+
error_log('[Mobidico] Authentication response is not valid JSON.');
205+
206+
return null;
207+
}
208+
209+
if ('OK' !== ($payload['status'] ?? null)) {
210+
error_log('[Mobidico] Authentication response status is not OK.');
211+
212+
return null;
213+
}
214+
215+
$session = trim((string) ($payload['session'] ?? ''));
216+
217+
if ('' === $session) {
218+
error_log('[Mobidico] Authentication response does not contain a session token.');
219+
220+
return null;
221+
}
222+
223+
return $session;
224+
} catch (ExceptionInterface $exception) {
225+
error_log('[Mobidico] Authentication request failed: '.$exception->getMessage());
226+
227+
return null;
228+
} catch (Throwable $exception) {
229+
error_log('[Mobidico] Unexpected authentication error: '.$exception->getMessage());
230+
231+
return null;
232+
}
38233
}
39234
}

0 commit comments

Comments
 (0)