PageSourceSearch

https://ta-tum.com/js/modules/student/profile/section/Profile.js

js ta-tum.com collected 2026-09-24 11:28:51 UTC 34,341 bytes, 722 lines download raw bytes

1/**
2 * Module Profile Profile
3 * @description 
4 * @author Alejandro Micheloud
5 */
6
7var StudentProfileProfile = function (sco) {
8    var scope = sco;
9    var builded = false;
10
11    /**
12     * Se invoca cuando el módulo recibe foco
13     * @returns {undefined}
14     */
15    function show() {
16        if (!builded) {
17            build(doStart);
18        } else {
19            doStart();
20        }
21    }
22
23    function doStart() {
24
25        $(".d-profile .d-avatar").html(getHtmlStudentAvatar(mDiscover.getUser(), ""));
26        $(".d-profile .d-username.input").html(mDiscover.getUser().username).attr("title", mDiscover.getUser().username);
27        $(".d-profile .d-name.input").html(mDiscover.getUser().name);
28        $(".d-profile .d-lastname.input").html(mDiscover.getUser().lastname);
29
30        scope.show();
31
32        getClassrooms();
33
34        mDiscover.showLoading();
35        mCom.ajax("profile", "GET", "",
36            function (data, status) {
37                mDiscover.hideLoading();
38                console.log(data)
39                if (data.created_at !== null && userLang === "eu") {
40                    if (typeof (data.created_at) === "string") {
41                        data.created_at = changeFormatDateToEU(data.created_at);
42                    }
43                }
44
45                scope.find(".d-datetime.modified_at").html((data.modified_at === null) ? "-" : data.modified_at);
46                scope.find(".d-datetime.created_at").html((data.created_at === null) ? "-" : data.created_at);
47                if (data.lang_id !== null) {
48                    scope.find(".simple-checkbox input").prop("checked", false);
49                    switch (data.lang_id) {
50                        case "ES":
51                            scope.find(".simple-checkbox input#lang-es").prop("checked", true);
52                            break;
53                        case "CA":
54                            scope.find(".simple-checkbox input#lang-ca").prop("checked", true);
55                            break;
56                        case "EU":
57                            scope.find(".simple-checkbox input#lang-eu").prop("checked", true);
58                            break;
59                        case "VA":
60                            scope.find(".simple-checkbox input#lang-va").prop("checked", true);
61                            break;
62                        case "GL":
63                            scope.find(".simple-checkbox input#lang-gl").prop("checked", true);
64                            break;
65                        case "AR":
66                            scope.find(".simple-checkbox input#lang-ar").prop("checked", true);
67                            break;
68                        case "MX":
69                            scope.find(".simple-checkbox input#lang-mx").prop("checked", true);
70                            break;
71                        case "EN":
72                            scope.find(".simple-checkbox input#lang-en").prop("checked", true);
73                            break;
74                    }
75                }
76                var lpHtml = '';
77                data.licenses.map(license => {
78                    // lpHtml += `<p><b class="license-code">${license.code}</b> Desde: ${formatDate(license.linked_at)} Hasta: ${formatDate(license.expires_at)}</p>`;
79                    lpHtml += `<p>
80                         <b class="license-code">${license.code}</b> 
81                         ${lang.profile_license_from || "Desde:"}
82                         ${formatDate(license.linked_at)} 
83                         ${lang.profile_license_to || "Hasta:"}
84                         ${formatDate(license.expires_at)}
85                         </p>`;
86                });
87                scope.find(".d-license-personal-info").html(lpHtml);
88                if (data.licenses && data.licenses.length === 0) {
89                    scope.find(".d-license-personal-info").html(`<b class="license-code">${lang.profile_register_license_off}</b>`);
90                }
91            }, function (err, status, msg) {
92                mDiscover.hideLoading();
93                mToast.toast("Problemas al recuperar datos", 3000);
94            });
95        var user = mDiscover.getUser();
96        if (user.is_verified && user.license && user.license.license_type === 'school') {
97            var info = String(lang.profile_register_license_on)
98                .split("#DATE#").join(user.license.expiration_date)
99                .split("#TYPE#").join(lang["profile_register_license_type_" + user.license.license_type]);
100            if (user.license.license_type === "school") {
101                info += "<br><span class='school'>" + user.license.name + "</span>";
102            }
103            scope.find(".d-license-info").html(info);
104        } else {
105            scope.find(".d-license-info").html(lang.profile_register_license_off);
106        }
107
108
109        refreshSsoStatus();
110    }
111    function refreshSsoStatus() {
112        var user = mDiscover.getUser();
113
114        scope.find(".btn-google-link").hide();
115        scope.find(".btn-microsoft-link").hide();
116
117        var emailGoogle = "";
118        var emailMicrosoft = "";
119
120        for (var i = 0; i < user.oauth_accounts.length; i++) {
121            if (user.oauth_accounts[i].provider == "google")
122                emailGoogle = user.oauth_accounts[i].email;
123
124            if (user.oauth_accounts[i].provider == "microsoft")
125                emailMicrosoft = user.oauth_accounts[i].email;
126        }
127        if (emailGoogle !== "") {
128            var unlinkText = lang.profile_sso_unlink + " <b>" + emailGoogle + "</b>";
129            scope.find("#btn-google-unlink-student .text").html(unlinkText);
130            scope.find("#btn-google-unlink-student").attr("title", unlinkText);
131            scope.find("#btn-google-unlink-student").show();
132            scope.find("#btn-google-unlink-student").data("email", emailGoogle);
133        } else {
134            scope.find("#btn-google-link-student .text").text(lang.profile_sso_link);
135            scope.find("#btn-google-link-student").show();
136        }
137        if (emailMicrosoft !== "") {
138            var unlinkText = lang.profile_sso_unlink + " <b>" + emailMicrosoft + "</b>";
139            scope.find("#btn-microsoft-unlink-student .text").html(unlinkText);
140            scope.find("#btn-microsoft-unlink-student").attr("title", unlinkText);
141            scope.find("#btn-microsoft-unlink-student").show();
142        } else {
143            scope.find("#btn-microsoft-link-student .text").text(lang.profile_sso_link_microsoft);
144            scope.find("#btn-microsoft-link-student").show();
145        }
146    }
147    function onGoogleClick() {
148        var user = mDiscover.getUser();
149        var action = "";
150        var email = "";
151        if (scope.find("#btn-google-unlink-student").is(":visible")) {
152            action = "unlink";
153            email = scope.find("#btn-google-unlink-student").data("email");
154        } else {
155            action = "link";
156            email = user.oauth_email;
157        }
158        mDiscover.showLoading();
159        mCom.ajax("change-oauth", "PUT", { token: sso_token, action: action, email: email, provider: "google" },
160            function (data, status) {
161                mDiscover.hideLoading();
162                console.log(data);
163                if (data.error) {
164                    var msg = String(lang[data.error_code]).split("{EMAIL}").join(data.email);
165                    mToast.toast(msg);
166                } else {
167                    if (action === "link") {
168                        mToast.toast(lang.profile_sso_link_success);
169                        user.oauth_accounts.push({ email: data.email, provider: "google", created_at: "" });
170                        mDiscover.setUser(user);
171                    } else {
172                        mToast.toast(lang.profile_sso_unlink_success);
173                        var oauthAccounts = [];
174                        for (var i = 0; i < user.oauth_accounts.length; i++) {
175                            if (user.oauth_accounts[i].email == data.email && user.oauth_accounts[i].provider == "google") {
176                                // queda afuera
177                            } else {
178                                oauthAccounts.push(user.oauth_accounts[i]);
179                            }
180                        }
181                        user.oauth_accounts = oauthAccounts;
182                        mDiscover.setUser(user);
183                    }
184                    setTimeout(refreshSsoStatus, 150);
185                }
186            },
187            function (err, status, msg) {
188                mDiscover.hideLoading();
189                if (action === "link") {
190                    mToast.toast(lang.profile_sso_link_error02);
191                } else {
192                    mToast.toast(lang.profile_sso_unlink_error02);
193                }
194                console.log(err);
195            });
196    }
197    function startGSSO() {
198        gapi.load('auth2', function () {
199            // Retrieve the singleton for the GoogleAuth library and set up the client.
200            auth2 = gapi.auth2.init({
201                client_id: config.GoogleClientID,
202                cookiepolicy: 'single_host_origin',
203                // Request scopes in addition to 'profile' and 'email'
204                //scope: 'additional_scope'
205            });
206            auth2.attachClickHandler(document.getElementById('btn-google-link-student'), {}, ssoSuccessTeacher, ssoError);
207        });
208    }
209    function ssoSuccessTeacher(googleUser) {
210        sso_token = googleUser.getAuthResponse().id_token;
211        console.log("ssoSuccessTeacher");
212        onGoogleClick();
213    }
214    function ssoError(error) {
215        console.log("ssoError register");
216        console.log(JSON.stringify(error, undefined, 2));
217    }
218
219    function updatePassword() {
220
221        var claveActual = $.trim($("#password-actual-student").val());
222        var claveNueva = $.trim($("#password-nuevo-student").val());
223        var claveNuevaConfirm = $.trim($("#password-nuevo-confirm-student").val());
224
225        if (claveNueva !== claveNuevaConfirm) {
226            mToast.toast(lang.profile_change_password_validation_1, 3000);
227            return;
228        }
229        if (claveNueva.length < 5) {
230            mToast.toast(lang.profile_change_password_validation_2, 3000);
231            return;
232        }
233        mDiscover.showLoading();
234        mCom.ajax("profile/change-password", "PUT", { passCurrent: claveActual, passNew: claveNueva, passNewConfirm: claveNuevaConfirm },
235            function (data, status) {
236                mDiscover.hideLoading();
237                mToast.toast(lang.profile_change_password_successful, 3000);
238                doStart();
239            }, function (err, status, msg) {
240                mDiscover.hideLoading();
241                if (err.status === 412) {
242                    // precondition failed
243                    mToast.toast(lang.profile_change_password_validation_3, 3000);
244                } else {
245                    mToast.toast(lang.profile_change_password_problem, 3000);
246                }
247            });
248    }
249
250    function updateLang() {
251        var lang_id = scope.find(".simple-checkbox input:checked").val();
252
253        if (lang_id && typeof lang_id === "string") {
254            mCom.ajax("profile/change-lang", "PUT", { lang_id: lang_id },
255                function (data, status) {
256                    mDiscover.hideLoading();
257                    mDiscover.updUserProfile({ lang_id: String(lang_id).toLowerCase() });
258                    $.cookie('planlector-user-lang', String(lang_id).toLowerCase(), { expires: 7, path: '/' });
259                    mToast.toast(lang.profile_lang_change_ok, 3000);
260                    setTimeout(function () {
261                        location.reload();
262                    }, 1000);
263                }, function (err, status, msg) {
264                    mDiscover.hideLoading();
265                    mToast.toast(lang.profile_lang_change_error, 3000);
266                });
267        } else {
268            mToast.toast(lang.profile_lang_change_check, 3000);
269        }
270    }
271
272    function saveChanges() {
273        mDiscover.showLoading();
274        mCom.ajax("profile", "PUT", { role: 'student', name: $(".d-profile .d-name.input").text(), lastname: $(".d-profile .d-lastname.input").text() },
275            function (data, status) {
276                mDiscover.hideLoading();
277                mToast.toast(lang.profile_save_ok, 3000);
278                mDiscover.updUserProfile(data);
279            }, function (err, status, msg) {
280                mDiscover.hideLoading();
281                mToast.toast(lang.profile_save_notok, 3000);
282            });
283    }
284
285    function onUpdateUser() {
286        if (mDiscover.getUser().avatar !== "") {
287            scope.find(".d-avatar .d-avatar-image").attr("style", "background-image:url(" + mDiscover.getUser().avatar + "?t=" + new Date().getTime() + ")");
288        }
289    }
290
291    function getClassrooms() {
292        scope.find(".d-classroom-container").html(getHtmlLoader());
293        mCom.ajax("student/classrooms", "GET", "", function (data, status) {
294            console.log("getClassrooms() result");
295            if (status === "success") {
296                renderClassrooms(data);
297            }
298        }, function (err, status, msg) {
299            mToast.toast(lang.student_get_classrooms_error, 3000);
300        });
301    }
302    function renderClassrooms(data) {
303
304        $cardcontainer = scope.find(".d-classroom-container");
305        $cardcontainer.html("");
306
307        if (typeof data !== "undefined") {
308            for (var i = 0; i < data.length; i++) {
309                var classroom = data[i];
310                $cardcontainer.append(getHtmlCardProject(classroom));
311                var card = $cardcontainer.find(".d-classroom").last();
312                card.data(classroom);
313                // image
314                if (classroom.avatar !== "") {
315                    card.find(".d-icon").css("background-image", "url(" + classroom.avatar + "?t=" + new Date().getTime() + ")");
316                }
317
318            }
319        }
320        $cardcontainer.find(".d-unlink").click(availableSoon);
321
322        // add new class
323        $cardcontainer.append(getHtmlCardNew());
324
325        $cardcontainer.find(".d-input-code").on("keypress", function (e) {
326            if (e.which === 13) {
327                linkToClass();
328            }
329            return e.which !== 13;
330        });
331        $cardcontainer.find(".d-submit").on("click", linkToClass);
332    }
333
334    function linkToClass() {
335
336        var data = {};
337        data.student_link_code = $.trim(scope.find(".d-input-code").text());
338        //$fCard = getLinkClassForm();
339
340        mDiscover.showLoading();
341        mCom.ajax("student/join-classroom", "PUT", data, function (data, status) {
342            console.log("addStudentToClassroom() result");
343
344            mDiscover.hideLoading();
345            if (status === "success") {
346                // form reset
347                scope.find(".d-input-code").text("");
348                mToast.toast(lang.student_join_classroom_successful, 3000);
349                // close modal
350                getClassrooms();
351            }
352        }, function (err, status, msg) {
353            mDiscover.hideLoading();
354
355            switch (err.status) {
356                case 403:
357                    //Forbidden
358                    mToast.toast(lang.student_join_classroom_error1, 3000);
359                    break;
360                case 400:
361                    // Bad Request
362                    mToast.toast(lang.student_join_classroom_error2, 3000);
363                    break;
364                case 409:
365                    // Conflict
366                    mToast.toast(lang.student_join_classroom_error4, 3000);
367                    break;
368                case 303:
369                    // See other
370                    mToast.toast(lang.student_join_classroom_error5, 10000);
371                    break;
372                default:
373                    mToast.toast(lang.student_join_classroom_error3, 3000);
374                    break;
375            }
376            console.log(err);
377            console.log(status);
378            console.log(msg);
379        });
380    }
381
382    function printCarnet() {
383        window.open(config.api + "student/pdf/" + mDiscover.getSession() + "/carnet-alumno.pdf", "_blank");
384    }
385
386    function onMicrosoftLinkClick() {
387        console.log("onMicrosoftLinkClick");
388        signInMicrosoft();
389        $("body").off("MicrosoftLogin").on("MicrosoftLogin", fun
389ction (e) {
390            if (!scope.is(":visible")) return false;
391
392            var mAccount = e.originalEvent.detail;
393
394            var sso_token = mAccount.token;
395            var email = mAccount.email;
396
397            mDiscover.showLoading();
398            mCom.ajax("change-oauth", "PUT", { token: sso_token, action: "link", email: email, provider: "microsoft" },
399                function (data, status) {
400                    mDiscover.hideLoading();
401                    console.log(data);
402                    if (data.error) {
403                        var msg = String(lang[data.error_code]).split("{EMAIL}").join(data.email);
404                        mToast.toast(msg);
405                    } else {
406                        mToast.toast(lang.profile_sso_link_success);
407                        var user = mDiscover.getUser();
408                        user.oauth_accounts.push({ email: data.email, provider: "microsoft", created_at: "" });
409                        mDiscover.setUser(user);
410
411                        setTimeout(refreshSsoStatus, 150);
412                    }
413                },
414                function (err, status, msg) {
415                    mDiscover.hideLoading();
416                    mToast.toast(lang.profile_sso_link_error02);
417                    console.log(err);
418                });
419        });
420    }
421
422    function onMicrosoftUnlinkClick() {
423        console.log("onMicrosoftUnlinkClick");
424        signInMicrosoft();
425        $("body").off("MicrosoftLogin").on("MicrosoftLogin", function (e) {
426            if (!scope.is(":visible")) return false;
427
428            var mAccount = e.originalEvent.detail;
429
430            var sso_token = mAccount.token;
431            var email = mAccount.email;
432
433            mDiscover.showLoading();
434            mCom.ajax("change-oauth", "PUT", { token: sso_token, action: "unlink", email: email, provider: "microsoft" },
435                function (data, status) {
436                    mDiscover.hideLoading();
437                    console.log(data);
438                    if (data.error) {
439                        var msg = String(lang[data.error_code]).split("{EMAIL}").join(data.email);
440                        mToast.toast(msg);
441                    } else {
442                        mToast.toast(lang.profile_sso_unlink_success);
443                        var oauthAccounts = [];
444                        var user = mDiscover.getUser();
445                        for (var i = 0; i < user.oauth_accounts.length; i++) {
446                            if (user.oauth_accounts[i].email == data.email && user.oauth_accounts[i].provider == "microsoft") {
447                                // queda afuera el que acabamos de desvincular
448                            } else {
449                                oauthAccounts.push(user.oauth_accounts[i]);
450                            }
451                        }
452                        user.oauth_accounts = oauthAccounts;
453                        mDiscover.setUser(user);
454
455                        setTimeout(refreshSsoStatus, 150);
456                    }
457                },
458                function (err, status, msg) {
459                    mDiscover.hideLoading();
460                    mToast.toast(lang.profile_sso_unlink_error02);
461                    console.log(err);
462                });
463        });
464    }
465
466    function activeLicense() {
467        mDiscover.showSubscribe(function () { 
468            mDiscover.hideSubscribe(); 
469            doStart();
470        });
471    }
472
473    /**
474     * Constructor de módulo
475     * @returns {undefined}
476     */
477    function build(callback) {
478
479        builded = true;
480
481        scope.html(getHtmlModule());
482
483        scope.find(".d-profile .d-print").off("click").on("click", printCarnet);
484        scope.find(".d-profile .d-submit").off("click").on("click", saveChanges);
485
486        scope.find(".d-account .d-submit-password").off("click").on("click", updatePassword);
487
488        scope.find(".d-account .d-active-license").off("click").on("click", activeLicense);
489
490        $("body").off("on_update_user", onUpdateUser).on("on_update_user", onUpdateUser);
491
492        // lang
493        scope.find(".simple-checkbox input").off("change").on("change", function () {
494            scope.find(".simple-checkbox input").prop("checked", false);
495            $(this).prop("checked", true);
496        });
497        scope.find(".d-submit-change-lang").click(updateLang);
498
499        if (isFunction(callback)) {
500            callback();
501        }
502
503        $("#btn-google-unlink-student").off("click").on("click", onGoogleClick);
504        $("#btn-microsoft-link-student").off("click").on("click", onMicrosoftLinkClick);
505        $("#btn-microsoft-unlink-student").off("click").on("click", onMicrosoftUnlinkClick);
506
507        startGSSO();
508    }
509
510    /**
511     * Constructor
512     * @returns {undefined}
513     */
514    function init() {
515        // nada, latente hasta show
516    }
517    init();
518
519    // helpers
520    /* html of module */
521    function getHtmlModule() {
522        return '<div class="d-student-m-profile">' +
523            '   <div class="d-profile standard-shadow-1"> ' +
524            '       <div class="d-card-profile-title">' + lang.profile_my_personal_dates + '</div>' +
525            '       <div class="d-card-profile"> ' +
526            '           <div class="d-title-depto">' + lang.profile_escuela_de_detectives + '</div>' +
527            '           <div class="d-title">' + lang.profile_label_detective + '</div>' +
528            '           <div class="d-username label">' + lang.profile_label_username + '</div>' +
529            '           <div class="d-username input truncate"></div>' +
530            '           <div class="d-name label">' + lang.profile_label_name + '</div>' +
531            '           <div class="d-name input" contenteditable="true">Agustín J.</div>' +
532            '           <div class="d-lastname label">' + lang.profile_label_lastname + '</div>' +
533            '           <div class="d-lastname input" contenteditable="true">Blanco García</div>' +
534            '           <div class="d-avatar"></div>' +
535            '       </div>' +
536            '       <div class="d-footer">' +
537            '           <div class="d-print btn brown">' + lang.profile_btn_print + '</div>' +
538            '           <div class="d-submit btn btn orange">' + lang.profile_btn_save + '</div>' +
539            '       </div>' +
540            '   </div> ' +
541            '   <div class="d-separator"></div>' +
542            '   <div class="d-account standard-shadow-1"> ' +
543            '       <div class="d-card-profile-title">' + lang.profile_section_my_account + '</div>' +
544            '       <div class="d-card-account"> ' +
545            '           <table width="100%" cellpadding="8">' +
546            '               <tr>' +
547            '                   <td align="right">' + lang.profile_register_from + '</td>' +
548            '                   <td><div class="d-datetime created_at"></div></td>' +
549            '               </tr>' +
550            '               <tr>' +
551            '                   <td align="center" colspan="2">' +
552            '                   <div class="sso-container">' +
553            '                       <div class="btn-google-link btn btn-bitmap standard-bisel-shadow-1" id="btn-google-link-student"><div class="logo left"></div><div class="text">Conectar con google</div></div>' +
554            '                       <div class="btn-google-link btn btn-bitmap standard-bisel-shadow-1" id="btn-google-unlink-student"><div class="logo left"></div><div class="text">Conectar con google</div></div>' +
555            '                   </div>' +
556            '                   <div class="sso-container">' +
557            '                       <div class="btn-microsoft-link btn btn-bitmap standard-bisel-shadow-1" id="btn-microsoft-link-student"><div class="logo left"></div><div class="text">Conectar con Microsoft</div></div>' +
558            '                       <div class="btn-microsoft-link btn btn-bitmap standard-bisel-shadow-1" id="btn-microsoft-unlink-student"><div class="logo left"></div><div class="text">Conectar con Microsoft</div></div>' +
559            '                   </div>' +
560            '                   </td>' +
561            '               </tr>' +
562            '               <tr>' +
563            '                   <td align="center" colspan="2"><div class="subtitle separador">' + lang.profile_lang_title + '</div></td>' +
564            '               </tr>' +
565            '               <tr>' +
566            '                   <td align="right"></td>' +
567            '                   <td>' +
568            '                           <div class="simple-checkbox circle"> ' +
569            '                               <input id="lang-es" type="checkbox" class="" value="ES"><label for="lang-es">' + lang.profile_lang_es + '</label> ' +
570            '                           </div> ' +
571            '                   </td>' +
572            '               </tr>' +
573            '               <tr>' +
574            '                   <td align="right"></td>' +
575            '                   <td>' +
576            '                           <div class="simple-checkbox circle"> ' +
577            '                               <input id="lang-ca" type="checkbox" class="" value="CA"><label for="lang-ca">' + lang.profile_lang_ca + '</label> ' +
578            '                           </div> ' +
579            '                   </td>' +
580            '               </tr>' +
581            '               <tr>' +
582            '                   <td align="right"></td>' +
583            '                   <td>' +
584            '                           <div class="simple-checkbox circle"> ' +
585            '                               <input id="lang-eu" type="checkbox" class="" value="EU"><label for="lang-eu">' + lang.profile_lang_eu + '</label> ' +
586            '                           </div> ' +
587            '                   </td>' +
588            '               </tr>' +
589            '               <tr>' +
590            '                   <td align="right"></td>' +
591            '                   <td>' +
592            '                           <div class="simple-checkbox circle"> ' +
593            '                               <input id="lang-va" type="checkbox" class="" value="VA"><label for="lang-va">' + lang.profile_lang_va + '</label> ' +
594            '                           </div> ' +
595            '                   </td>' +
596            '               </tr>' +
597            '               <tr>' +
598            '                   <td align="right"></td>' +
599            '                   <td>' +
600            '                           <div class="simple-checkbox circle"> ' +
601            '                               <input id="lang-gl" type="checkbox" class="" value="GL"><label for="lang-gl">' + lang.profile_lang_gl + '</label> ' +
602            '                           </div> ' +
603            '                   </td>' +
604            '               </tr>' +
605            '               <tr>' +
606            '                   <td align="right"></td>' +
607            '                   <td>' +
608            '                           <div class="simple-checkbox circle"> ' +
609            '                               <input id="lang-ar" type="checkbox" class="" value="AR"><label for="lang-ar">' + lang.profile_lang_ar + '</label> ' +
610            '                           </div> ' +
611            '                   </td>' +
612            '               </tr>' +
613            '               <tr>' +
614            '                   <td align="right"></td>' +
615            '                   <td>' +
616            '                           <div class="simple-checkbox circle"> ' +
617            '                               <input id="lang-mx" type="checkbox" class="" value="MX"><label for="lang-mx">' + lang.profile_lang_mx + '</label> ' +
618            '                           </div> ' +
619            '                   </td>' +
620            '               </tr>' +
621            (config.enableLangEnglish ?
622                `               <tr>
623                                   <td align="right"></td>
624                                   <td>
625                                           <div class="simple-checkbox circle"> 
626                                               <input id="lang-en" type="checkbox" class="" value="EN"><label for="lang-en">${lang.profile_lang_en}</label> 
627                                           </div> 
628                                   </td>
629                               </tr>` : '') +
630            '               <tr>' +
631            '                   <td align="right"></td>' +
632            '                   <td>' +
633            '                      <div class="d-submit-change-lang btn btn orange">' + lang.profile_lang_submit + '</div>' +
634            '                   </td>' +
635            '               </tr>' +
636            '               <tr>' +
637            '                   <td align="center" colspan="2"><div class="subtitle separador">' + (lang.profile_license_school_title || 'Licencia de colegio') + '</div></td>' +
638            '               </tr>' +
639            '               <tr>' +
640            '                   <td align="right">' + lang.profile_register_license + '</td>' +
641            '                   <td><div class="d-license-info"></div></td>' +
642            '               </tr>' +
643            '               <tr>' +
644            '                   <td align="center" colspan="2"><div class="subtitle separador">' + (lang.profile_license_title || 'Licencia Personal') + '</div></td>' +
645            '               </tr>' +
646            '               <tr>' +
647            '                   <td align="right"></td>' +
648            '                   <td><div class="d-license-personal-info"></div></td>' +
649            '               </tr>' +
650            '               <tr>' +
651            '                   <td align="right"></td>' +
652            '                   <td>' + (config.hasOwnProperty("ENABLE_PERSONAL_LICENSES") && config.ENABLE_PERSONAL_LICENSES ? `
653                <div class="d-active-license btn btn orange">${lang.profile_license_add || 'Añadir licencia'}</div>
654                `: '') + '</td>' +
655            '               </tr>' +
656            '               <tr>' +
657            '                   <td align="center" colspan="2"><div class="subtitle separador">' + lang.profile_change_password_title + '</div></td>' +
658            '               </tr>' +
659            '               <tr>' +
660            '                   <td align="right">' + lang.profile_change_password_last_update + '</td>' +
661            '                   <td><div class="d-datetime modified_at"></div></td>' +
662            '               </tr>' +
663            '               <tr>' +
664            '                   <td align="right">' + lang.profile_change_password_current + '</td>' +
665            '                   <td><input type="password" value="" id="password-actual-student" placeholder="' + lang.profile_change_password_current + '"></td>' +
666            '               </tr>' +
667            '               <tr>' +
668            '                   <td align="right">' + lang.profile_change_password_new + '</td>' +
669            '                   <td><input type="password" value="" id="password-nuevo-student" placeholder="' + lang.profile_change_password_new + '"></td>' +
670            '               </tr>' +
671            '               <tr>' +
672            '                   <td align="right">' + lang.profile_change_password_new_confirm + '</td>' +
673            '                   <td><input type="password" value="" id="password-nuevo-confirm-student" placeholder="' + lang.profile_change_password_confirm + '"></td>' +
674            '               </tr>' +
675            '           </table>' +
676            '       </div>' +
677            '       <div class="d-footer">' +
678            '           <div class="d-submit-password btn btn orange">' + lang.profile_change_password_confirm + '</div>' +
679            '       </div>' +
680            '   </div> ' +
681            '   <div class="d-separator"></div>' +
682            '   <div class="d-classrooms standard-shadow-1"> ' +
683            '       <div class="d-title">' + lang.profile_section_my_classrooms + '</div>' +
684            '       <div class="d-classroom-container"></div>' +
685            '   </div> ' +
686            '   <div class="d-separator"></div>' +
687            '</div>';
688    }
689    function getHtmlCardProject(classroom) {
690        var students = '';
691        if (classroom.amount_students === 0) {
692            students = " " + lang.classroom_info_wo_researcher;
693        } else if (classroom.amount_students === 1) {
694            students = classroom.amount_students + " " + lang.classroom_info_researcher;
695        } else {
696            students = classroom.amount_students + " " + lang.classroom_info_researchers;
697        }
698        return '<div class="d-classroom">' +
699            '   <div class="d-icon"></div>' +
700            '   <div class="d-icon-bg"></div>' +
701            '   <div class="d-name">' + classroom.name + '</div>' +
702            '   <div class="d-level">' + classroom.education_level + '</div>' +
703            '   <div class="d-students">' + students + '</div>' +
704            '   <div class="d-unlink btn orange-white">Desvincular</div>' +
705            '</div>';
706    }
707    function getHtmlCardNew() {
708        return '<div class="d-classroom add">' +
709            '   <div class="d-icon-add"></div>' +
710            '   <div class="d-title">' + lang.profile_section_link + '</div>' +
711            '   <div class="d-title-code">' + lang.profile_section_link_msg + '</div>' +
712            '   <div class="d-input-code" contenteditable="true"></div>' +
713            '   <div class="d-submit btn brown">' + lang.profile_section_link_btn + '</div>' +
714            '</div>';
715    }
716    function getHtmlLoader() {
717        return '<div class="d-loader"><img src="css/img/loading_gears.gif" /></div>';
718    }
719
720    // public methods
721    this.show = show;
722};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.