Passed
Push — master ( 67ecc2...c9dfd5 )
by
unknown
10:48 queued 16s
created

buildCertificateBlock()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 21
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 13
c 0
b 0
f 0
nc 5
nop 1
dl 0
loc 21
rs 9.5222
1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Entity\GradebookCategory;
6
use Chamilo\CoreBundle\Entity\Skill;
7
use Chamilo\CoreBundle\Framework\Container;
8
9
/**
10
 * LP Final Item: muestra certificado y skills al completar el LP.
11
 */
12
$_in_course = true;
13
14
require_once __DIR__.'/../inc/global.inc.php';
15
16
$current_course_tool = TOOL_GRADEBOOK;
17
api_protect_course_script(true);
18
19
$courseCode = api_get_course_id();
20
$courseId   = api_get_course_int_id();
21
$userId     = api_get_user_id();
22
$sessionId  = api_get_session_id();
23
$id         = isset($_GET['id']) ? (int) $_GET['id'] : 0;
24
$lpId       = isset($_GET['lp_id']) ? (int) $_GET['lp_id'] : 0;
25
26
// This page can only be shown from inside a learning path
27
if (!$id && !$lpId) {
28
    echo Display::return_message(get_lang('The file was not found'));
29
    exit;
30
}
31
32
// Certificate and Skills Premium with Service check
33
$plugin  = BuyCoursesPlugin::create();
34
$checker = $plugin->isEnabled() && $plugin->get('include_services');
35
36
if ($checker) {
37
    $userServiceSale = $plugin->getServiceSales(
38
        $userId,
39
        BuyCoursesPlugin::SERVICE_STATUS_COMPLETED,
40
        BuyCoursesPlugin::SERVICE_TYPE_LP_FINAL_ITEM,
41
        $lpId
42
    );
43
44
    if (empty($userServiceSale)) {
45
        // Instance a new template : No page tittle, No header, No footer
46
        $tpl = new Template(null, false, false);
47
        $url = api_get_path(WEB_PLUGIN_PATH).'BuyCourses/src/service_catalog.php';
48
        $content = sprintf(
49
            Display::return_message(
50
                get_lang('If you want to get the certificate and/or skills associated with this course, you need to buy the certificate service. You can go to the services catalog by clicking this link: %s'),
51
                'normal',
52
                false
53
            ),
54
            '<a href="'.$url.'">'.$url.'</a>'
55
        );
56
        $tpl->assign('content', $content);
57
        $tpl->display_blank_template();
58
        exit;
59
    }
60
}
61
62
$lpEntity = api_get_lp_entity($lpId);
63
$lp       = new Learnpath($lpEntity, [], $userId);
64
65
$count              = $lp->getTotalItemsCountWithoutDirs();
66
$completed          = $lp->get_complete_items_count(true);
67
$currentItemId      = $lp->get_current_item_id();
68
$currentItem        = $lp->items[$currentItemId] ?? null;
69
$currentItemStatus  = $currentItem ? $currentItem->get_status() : 'not attempted';
70
71
$lpItemRepo   = Container::getLpItemRepository();
72
$isFinalThere = false;
73
$isFinalDone  = false;
74
try {
75
    $finalItem = $lpItemRepo->findOneBy(['lp' => $lpEntity, 'itemType' => TOOL_LP_FINAL_ITEM]);
76
    if ($finalItem) {
77
        $isFinalThere = true;
78
        $fid = $finalItem->getIid();
79
        if (isset($lp->items[$fid])) {
80
            $st = $lp->items[$fid]->get_status();
81
            $isFinalDone = in_array($st, ['completed','passed','succeeded'], true);
82
        }
83
    }
84
} catch (\Throwable $e) {
85
    error_log('[LP_FINAL] final_item lookup error: '.$e->getMessage());
86
}
87
$countAdj     = max(0, $count    - ($isFinalThere ? 1 : 0));
88
$completedAdj = max(0, $completed - ($isFinalDone  ? 1 : 0));
89
$diff         = $countAdj - $completedAdj;
90
$accessGranted = false;
91
if ($diff === 0 || ($diff === 1 && (('incomplete' === $currentItemStatus) || ('not attempted' === $currentItemStatus)))) {
92
    if ($lp->prerequisites_match($currentItemId)) {
93
        $accessGranted = true;
94
    }
95
}
96
$lp->save_last();
97
unset($lp, $currentItem);
98
99
if (!$accessGranted) {
100
    echo Display::return_message(
101
        get_lang('This learning object cannot display because the course prerequisites are not completed. This happens when a course imposes that you follow it step by step or get a minimum score in tests before you reach the next steps.'),
102
        'warning'
103
    );
104
    $finalHtml = '';
105
} else {
106
    $downloadBlock = '';
107
    $badgeBlock    = '';
108
    $gbRepo        = Container::getGradeBookCategoryRepository();
109
    $courseEntity  = api_get_course_entity();
110
    $sessionEntity = api_get_session_entity();
111
112
    /* @var GradebookCategory $gbCat */
113
    $gbCat = $gbRepo->findOneBy(['course' => $courseEntity, 'session' => $sessionEntity]);
114
115
    if (!$gbCat) {
0 ignored issues
show
introduced by
$gbCat is of type GradebookCategory, thus it always evaluated to true.
Loading history...
116
        $gbCat = $gbRepo->findOneBy(['course' => $courseEntity, 'session' => null]);
117
    }
118
119
    if ($gbCat && !api_is_allowed_to_edit() && !api_is_excluded_user_type()) {
120
        $cert = safeGenerateCertificateForCategory($gbCat, $userId);
121
        $downloadBlock = buildCertificateBlock($cert);
122
        $badgeBlock = generateBadgePanel($userId, $courseId, $sessionId);
123
    }
124
125
    $finalHtml = renderFinalItemDocument($id, $downloadBlock, $badgeBlock);
126
}
127
128
$tpl = new Template(null, false, false);
129
$tpl->assign('content', $finalHtml);
130
$tpl->display_blank_template();
131
132
/**
133
 * Generates/ensures the certificate via Doctrine repositories and returns minimal link data.
134
 */
135
function safeGenerateCertificateForCategory(GradebookCategory $category, int $userId): array
136
{
137
    $course   = $category->getCourse();
138
    $session  = $category->getSession();
139
    $courseId = $course ? $course->getId() : 0;
0 ignored issues
show
introduced by
$course is of type Chamilo\CoreBundle\Entity\Course, thus it always evaluated to true.
Loading history...
140
    $sessId   = $session ? $session->getId() : 0;
141
    $catId    = (int) $category->getId();
142
143
    $gb = GradebookUtils::get_user_certificate_content($userId, $courseId, $sessId);
144
    $html = is_array($gb) && isset($gb['content']) ? $gb['content'] : '';
145
146
    $fileName = hash('sha256', $userId.$catId).'.html';
147
    $certRepo = Container::getGradeBookCertificateRepository();
148
    $pf = $certRepo->generateCertificatePersonalFile($userId, $fileName, $html);
149
    try {
150
        $score = isset($gb['score']) ? (float) $gb['score'] : 100.0;
151
        $certRepo->registerUserInfoAboutCertificate($catId, $userId, $score, $fileName);
152
    } catch (\Throwable $e) {
153
        error_log('[LP_FINAL] register cert error: '.$e->getMessage());
154
    }
155
156
    $hash    = pathinfo($fileName, PATHINFO_FILENAME);
157
    $htmlUrl = api_get_path(WEB_PATH).'certificates/'.$hash.'.html';
0 ignored issues
show
Bug introduced by
Are you sure $hash of type array|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

157
    $htmlUrl = api_get_path(WEB_PATH).'certificates/'./** @scrutinizer ignore-type */ $hash.'.html';
Loading history...
158
    $pdfUrl  = api_get_path(WEB_PATH).'certificates/'.$hash.'.pdf';
159
160
    return [
161
        'path_certificate' => $fileName,
162
        'html_url'         => $htmlUrl,
163
        'pdf_url'          => $pdfUrl,
164
    ];
165
}
166
167
/**
168
 * Builds the certificate download/view HTML block (if available).
169
 */
170
function buildCertificateBlock(array $cert): string
171
{
172
    $htmlUrl = $cert['html_url'] ?? '';
173
    $pdfUrl  = $cert['pdf_url']  ?? '';
174
    if (!$htmlUrl && !$pdfUrl) {
175
        return '';
176
    }
177
178
    $downloadBtn = $pdfUrl
179
        ? Display::toolbarButton(get_lang('Download certificate in PDF'), $pdfUrl, 'file-pdf-box')
180
        : '';
181
182
    $viewBtn = $htmlUrl
183
        ? Display::url(get_lang('View certificate'), $htmlUrl, ['class' => 'btn btn-default'])
184
        : '';
185
186
    return "
187
        <div class='panel panel-default'>
188
            <div class='panel-body'>
189
                <h3 class='text-center'>".get_lang('You can now download your certificate by clicking here')."</h3>
190
                <div class='text-center'>{$downloadBtn} {$viewBtn}</div>
191
            </div>
192
        </div>
193
    ";
194
}
195
196
/**
197
 * Returns the user's skills panel HTML (empty if none).
198
 */
199
function generateBadgePanel(int $userId, int $courseId, int $sessionId = 0): string
200
{
201
    $em           = Database::getManager();
202
    $skillRelUser = new SkillRelUserModel();
203
    $userSkills   = $skillRelUser->getUserSkills($userId, $courseId, $sessionId);
204
    if (!$userSkills) {
205
        return '';
206
    }
207
208
    $items = '';
209
    foreach ($userSkills as $row) {
210
        $skill = $em->find(Skill::class, $row['skill_id']);
211
        if (!$skill) {
212
            continue;
213
        }
214
        $items .= "
215
            <div class='row'>
216
                <div class='col-md-2 col-xs-4'>
217
                    <div class='thumbnail'>
218
                        <img class='skill-badge-img' src='".SkillModel::getWebIconPath($skill)."' >
219
                    </div>
220
                </div>
221
                <div class='col-md-8 col-xs-8'>
222
                    <h5><b>".$skill->getTitle()."</b></h5>
223
                    ".$skill->getDescription()."
224
                </div>
225
                <div class='col-md-2 col-xs-12'>
226
                    <h5><b>".get_lang('Share with your friends')."</b></h5>
227
                    <a href='http://www.facebook.com/sharer.php?u=".api_get_path(WEB_PATH)."badge/".$skill->getId()."/user/".$userId."' target='_new'>
228
                        <em class='fa fa-facebook-square fa-3x text-info' aria-hidden='true'></em>
229
                    </a>
230
                    <a href='https://twitter.com/home?status=".sprintf(get_lang('I have achieved skill %s on %s'), '"'.$skill->getTitle().'"', api_get_setting('siteName')).' - '.api_get_path(WEB_PATH).'badge/'.$skill->getId().'/user/'.$userId."' target='_new'>
231
                        <em class='fa fa-twitter-square fa-3x text-light' aria-hidden='true'></em>
232
                    </a>
233
                </div>
234
            </div>";
235
    }
236
237
    if (!$items) {
238
        return '';
239
    }
240
241
    return "
242
        <div class='panel panel-default'>
243
            <div class='panel-body'>
244
                <h3 class='text-center'>".get_lang('Additionally, you have achieved the following skills')."</h3>
245
                {$items}
246
            </div>
247
        </div>
248
    ";
249
}
250
251
/**
252
 * Render the Learning Path final-item document.
253
 */
254
function renderFinalItemDocument(int $lpItemOrDocId, string $certificateBlock, string $badgeBlock): string
255
{
256
    $docRepo    = Container::getDocumentRepository();
257
    $lpItemRepo = Container::getLpItemRepository();
258
259
    $document = null;
260
    try { $document = $docRepo->find($lpItemOrDocId); } catch (\Throwable $e) {}
0 ignored issues
show
introduced by
Consider moving this CATCH statement to a new line.
Loading history...
261
    if (!$document) {
262
        try {
263
            $lpItem = $lpItemRepo->find($lpItemOrDocId);
264
            if ($lpItem) {
265
                $document = $docRepo->find((int) $lpItem->getPath());
266
            }
267
        } catch (\Throwable $e) {}
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
268
    }
269
270
    if (!$document) {
271
        return '';
272
    }
273
274
    try {
275
        $content = $docRepo->getResourceFileContent($document);
276
    } catch (\Throwable $e) {
277
        error_log('[LP_FINAL] read doc error: '.$e->getMessage());
278
        return '';
279
    }
280
281
    $hasCert  = str_contains($content, '((certificate))');
282
    $hasSkill = str_contains($content, '((skill))');
283
284
    if ($hasCert)  { $content = str_replace('((certificate))', $certificateBlock, $content); }
285
    if ($hasSkill) { $content = str_replace('((skill))', $badgeBlock, $content); }
286
287
    return $content;
288
}
289