From aa68e4b11703559ae4f6fb24953bcdf3f993daf2 Mon Sep 17 00:00:00 2001 From: "andres.sanjuan" Date: Tue, 23 Dec 2025 09:23:49 -0600 Subject: [PATCH] Initial commit --- .../profile360/CertificatesInfo.php | 229 +++++++ .../profile360/LICENSE.md | 596 ++++++++++++++++++ .../profile360/README.md | 123 ++++ .../profile360/UserInfo.php | 400 ++++++++++++ .../profile360/amd/build/profile360.min.js | 3 + .../amd/build/profile360.min.js.map | 1 + .../profile360/amd/src/profile360.js | 103 +++ .../profile360/block_profile360.php | 299 +++++++++ .../classes/external/get_courses_data.php | 95 +++ .../profile360/classes/observer.php | 66 ++ .../profile360/classes/privacy/provider.php | 44 ++ .../profile360/db/access.php | 48 ++ .../profile360/db/caches.php | 34 + .../profile360/db/events.php | 44 ++ .../profile360/db/services.php | 14 + .../profile360/lang/en/block_profile360.php | 46 ++ .../lang/es_mx/block_profile360.php | 44 ++ .../profile360/lang/fil/block_profile360.php | 44 ++ .../profile360/lang/id/block_profile360.php | 44 ++ .../profile360/lang/vi/block_profile360.php | 44 ++ .../profile360/lib.php | 69 ++ .../profile360/pix/01.png | Bin 0 -> 84591 bytes .../profile360/settings.php | 38 ++ .../profile360/styles.css | 182 ++++++ .../profile360/styles/profile360.css | 186 ++++++ .../profile360/styles/profile360.min.css | 1 + .../profile360/styles/profile360.scss | 129 ++++ .../profile360/templates/profile360.mustache | 198 ++++++ .../profile360/version.php | 31 + 29 files changed, 3155 insertions(+) create mode 100644 block_profile360_multilenguage/profile360/CertificatesInfo.php create mode 100644 block_profile360_multilenguage/profile360/LICENSE.md create mode 100644 block_profile360_multilenguage/profile360/README.md create mode 100644 block_profile360_multilenguage/profile360/UserInfo.php create mode 100644 block_profile360_multilenguage/profile360/amd/build/profile360.min.js create mode 100644 block_profile360_multilenguage/profile360/amd/build/profile360.min.js.map create mode 100644 block_profile360_multilenguage/profile360/amd/src/profile360.js create mode 100644 block_profile360_multilenguage/profile360/block_profile360.php create mode 100644 block_profile360_multilenguage/profile360/classes/external/get_courses_data.php create mode 100644 block_profile360_multilenguage/profile360/classes/observer.php create mode 100644 block_profile360_multilenguage/profile360/classes/privacy/provider.php create mode 100644 block_profile360_multilenguage/profile360/db/access.php create mode 100644 block_profile360_multilenguage/profile360/db/caches.php create mode 100644 block_profile360_multilenguage/profile360/db/events.php create mode 100644 block_profile360_multilenguage/profile360/db/services.php create mode 100644 block_profile360_multilenguage/profile360/lang/en/block_profile360.php create mode 100644 block_profile360_multilenguage/profile360/lang/es_mx/block_profile360.php create mode 100644 block_profile360_multilenguage/profile360/lang/fil/block_profile360.php create mode 100644 block_profile360_multilenguage/profile360/lang/id/block_profile360.php create mode 100644 block_profile360_multilenguage/profile360/lang/vi/block_profile360.php create mode 100644 block_profile360_multilenguage/profile360/lib.php create mode 100644 block_profile360_multilenguage/profile360/pix/01.png create mode 100644 block_profile360_multilenguage/profile360/settings.php create mode 100644 block_profile360_multilenguage/profile360/styles.css create mode 100644 block_profile360_multilenguage/profile360/styles/profile360.css create mode 100644 block_profile360_multilenguage/profile360/styles/profile360.min.css create mode 100644 block_profile360_multilenguage/profile360/styles/profile360.scss create mode 100644 block_profile360_multilenguage/profile360/templates/profile360.mustache create mode 100644 block_profile360_multilenguage/profile360/version.php diff --git a/block_profile360_multilenguage/profile360/CertificatesInfo.php b/block_profile360_multilenguage/profile360/CertificatesInfo.php new file mode 100644 index 0000000..6ab9a65 --- /dev/null +++ b/block_profile360_multilenguage/profile360/CertificatesInfo.php @@ -0,0 +1,229 @@ +. + +/** + * Class to get the user certificates + * + * @package block_mycertificates + * @copyright 2020 Willian Mano - http://conecti.me + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + + +defined('MOODLE_INTERNAL') || die(); + +/** + * Class to get the user certificates. + * + * @copyright 2020 Willian Mano - http://conecti.me + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class CertificatesInfo { + + /** + * @var \stdClass $user The target user. + */ + protected $user; + + /** + * @var int $courseid The course id. + */ + protected $courseid; + + /** + * Certificates constructor. + * + * @param \stdClass $user + * @param int $courseid + */ + public function __construct($user, $courseid = null) { + $this->user = $user; + $this->courseid = $courseid; + } + + /** + * Returns all issued certificates from all certificates modules. + * + * @return array + * + * @throws \dml_exception + * @throws \moodle_exception + */ + public function get_all_certificates() { + $simplecertificate = $this->get_from_simplecertificate(); + $customcert = $this->get_from_customcert(); + + $allcerts = array_merge($simplecertificate, $customcert); + + if (!empty($allcerts)) { + return array_values($this->group_certificates_by_course($allcerts)); + } + + return []; + } + + /** + * Get all issued certificates from simplecertificate module. + * + * @return array + * + * @throws \dml_exception + * @throws \moodle_exception + */ + public function get_from_simplecertificate() { + global $DB; + + $simplecertificate = \core_plugin_manager::instance()->get_plugin_info('mod_simplecertificate'); + + if (is_null($simplecertificate)) { + return []; + } + + $sql = "SELECT + sci.code, + sci.pathnamehash, + sc.name, + c.id as courseid, + c.fullname, + c.shortname, + 'simplecertificate' as module + FROM {simplecertificate_issues} sci + INNER JOIN {simplecertificate} sc ON sc.id = sci.certificateid + INNER JOIN {course} c ON sc.course = c.id + WHERE sci.timedeleted IS NULL AND sci.userid = :userid"; + $params = ['userid' => $this->user->id]; + + if ($this->courseid) { + $sql .= ' AND c.id = :courseid'; + $params['courseid'] = $this->courseid; + } + + $sql .= ' ORDER BY c.fullname, sci.timecreated'; + + $certificates = $DB->get_records_sql($sql, $params); + + if (empty($certificates)) { + return []; + } + + $fs = get_file_storage(); + + $returndata = []; + foreach ($certificates as $certificate) { + if (!$fs->file_exists_by_hash($certificate->pathnamehash)) { + continue; + } + + $url = new \moodle_url('/mod/simplecertificate/wmsendfile.php', [ + 'code' => $certificate->code + ]); + + $certificate->downloadurl = $url->out(false); + + $returndata[] = $certificate; + } + + return $returndata; + } + + /** + * Get issued certificates from customcert module. + * + * @return array + * + * @throws \dml_exception + * @throws \moodle_exception + */ + public function get_from_customcert() { + global $DB; + + $customcert = \core_plugin_manager::instance()->get_plugin_info('mod_customcert'); + + if (is_null($customcert)) { + return []; + } + + $sql = "SELECT + ci.customcertid, + cc.name, + c.id as courseid, + c.fullname, + c.shortname, + 'customcert' as module + FROM {customcert_issues} ci + INNER JOIN {customcert} cc ON cc.id = ci.customcertid + INNER JOIN {course} c ON c.id = cc.course + WHERE ci.userid = :userid"; + + $params = ['userid' => $this->user->id]; + + if ($this->courseid) { + $sql .= ' AND c.id = :courseid'; + $params['courseid'] = $this->courseid; + } + + $sql .= ' ORDER BY c.fullname, ci.timecreated'; + + $certificates = $DB->get_records_sql($sql, $params); + + if (empty($certificates)) { + return []; + } + + foreach ($certificates as $key => $certificate) { + $url = new \moodle_url('/mod/customcert/my_certificates.php', [ + 'downloadcert' => true, + 'userid' => $this->user->id, + 'certificateid' => $certificate->customcertid + ]); + + $certificates[$key]->downloadurl = $url->out(false); + } + + return $certificates; + } + + /** + * Group certificates by course. + * + * @param array $certificates + * + * @return array + */ + public static function group_certificates_by_course($certificates) { + $returndata = []; + + foreach ($certificates as $certificate) { + $certs = [$certificate]; + if (isset($returndata[$certificate->courseid])) { + $certs = array_merge($certs, $returndata[$certificate->courseid]['certificates']); + + $returndata[$certificate->courseid]['certificates'] = $certs; + + continue; + } + + $returndata[$certificate->courseid] = [ + 'courseid' => $certificate->courseid, + 'shortname' => $certificate->shortname, + 'fullname' => $certificate->fullname, + 'certificates' => $certs + ]; + } + + return $returndata; + } +} diff --git a/block_profile360_multilenguage/profile360/LICENSE.md b/block_profile360_multilenguage/profile360/LICENSE.md new file mode 100644 index 0000000..16d89e0 --- /dev/null +++ b/block_profile360_multilenguage/profile360/LICENSE.md @@ -0,0 +1,596 @@ +GNU GENERAL PUBLIC LICENSE +========================== + +Version 3, 29 June 2007 + +Copyright © 2007 Free Software Foundation, Inc. <> + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + +## Preamble + +The GNU General Public License is a free, copyleft license for software and other +kinds of works. + +The licenses for most software and other practical works are designed to take away +your freedom to share and change the works. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change all versions of a +program--to make sure it remains free software for all its users. We, the Free +Software Foundation, use the GNU General Public License for most of our software; it +applies also to any other work released this way by its authors. You can apply it to +your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our General +Public Licenses are designed to make sure that you have the freedom to distribute +copies of free software (and charge for them if you wish), that you receive source +code or can get it if you want it, that you can change the software or use pieces of +it in new free programs, and that you know you can do these things. + +To protect your rights, we need to prevent others from denying you these rights or +asking you to surrender the rights. Therefore, you have certain responsibilities if +you distribute copies of the software, or if you modify it: responsibilities to +respect the freedom of others. + +For example, if you distribute copies of such a program, whether gratis or for a fee, +you must pass on to the recipients the same freedoms that you received. You must make +sure that they, too, receive or can get the source code. And you must show them these +terms so they know their rights. + +Developers that use the GNU GPL protect your rights with two steps: (1) assert +copyright on the software, and (2) offer you this License giving you legal permission +to copy, distribute and/or modify it. + +For the developers' and authors' protection, the GPL clearly explains that there is +no warranty for this free software. For both users' and authors' sake, the GPL +requires that modified versions be marked as changed, so that their problems will not +be attributed erroneously to authors of previous versions. + +Some devices are designed to deny users access to install or run modified versions of +the software inside them, although the manufacturer can do so. This is fundamentally +incompatible with the aim of protecting users' freedom to change the software. The +systematic pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we have designed +this version of the GPL to prohibit the practice for those products. If such problems +arise substantially in other domains, we stand ready to extend this provision to +those domains in future versions of the GPL, as needed to protect the freedom of +users. + +Finally, every program is threatened constantly by software patents. States should +not allow patents to restrict development and use of software on general-purpose +computers, but in those that do, we wish to avoid the special danger that patents +applied to a free program could make it effectively proprietary. To prevent this, the +GPL assures that patents cannot be used to render the program non-free. + +The precise terms and conditions for copying, distribution and modification follow. + +## TERMS AND CONDITIONS + +### 0. Definitions. + +“This License” refers to version 3 of the GNU General Public License. + +“Copyright” also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + +“The Program” refers to any copyrightable work licensed under this +License. Each licensee is addressed as “you”. “Licensees” and +“recipients” may be individuals or organizations. + +To “modify” a work means to copy from or adapt all or part of the work in +a fashion requiring copyright permission, other than the making of an exact copy. The +resulting work is called a “modified version” of the earlier work or a +work “based on” the earlier work. + +A “covered work” means either the unmodified Program or a work based on +the Program. + +To “propagate” a work means to do anything with it that, without +permission, would make you directly or secondarily liable for infringement under +applicable copyright law, except executing it on a computer or modifying a private +copy. Propagation includes copying, distribution (with or without modification), +making available to the public, and in some countries other activities as well. + +To “convey” a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through a computer +network, with no transfer of a copy, is not conveying. + +An interactive user interface displays “Appropriate Legal Notices” to the +extent that it includes a convenient and prominently visible feature that (1) +displays an appropriate copyright notice, and (2) tells the user that there is no +warranty for the work (except to the extent that warranties are provided), that +licensees may convey the work under this License, and how to view a copy of this +License. If the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + +### 1. Source Code. + +The “source code” for a work means the preferred form of the work for +making modifications to it. “Object code” means any non-source form of a +work. + +A “Standard Interface” means an interface that either is an official +standard defined by a recognized standards body, or, in the case of interfaces +specified for a particular programming language, one that is widely used among +developers working in that language. + +The “System Libraries” of an executable work include anything, other than +the work as a whole, that (a) is included in the normal form of packaging a Major +Component, but which is not part of that Major Component, and (b) serves only to +enable use of the work with that Major Component, or to implement a Standard +Interface for which an implementation is available to the public in source code form. +A “Major Component”, in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system (if any) on which +the executable work runs, or a compiler used to produce the work, or an object code +interpreter used to run it. + +The “Corresponding Source” for a work in object code form means all the +source code needed to generate, install, and (for an executable work) run the object +code and to modify the work, including scripts to control those activities. However, +it does not include the work's System Libraries, or general-purpose tools or +generally available free programs which are used unmodified in performing those +activities but which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for the work, and +the source code for shared libraries and dynamically linked subprograms that the work +is specifically designed to require, such as by intimate data communication or +control flow between those subprograms and other parts of the work. + +The Corresponding Source need not include anything that users can regenerate +automatically from other parts of the Corresponding Source. + +The Corresponding Source for a work in source code form is that same work. + +### 2. Basic Permissions. + +All rights granted under this License are granted for the term of copyright on the +Program, and are irrevocable provided the stated conditions are met. This License +explicitly affirms your unlimited permission to run the unmodified Program. The +output from running a covered work is covered by this License only if the output, +given its content, constitutes a covered work. This License acknowledges your rights +of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not convey, without +conditions so long as your license otherwise remains in force. You may convey covered +works to others for the sole purpose of having them make modifications exclusively +for you, or provide you with facilities for running those works, provided that you +comply with the terms of this License in conveying all material for which you do not +control copyright. Those thus making or running the covered works for you must do so +exclusively on your behalf, under your direction and control, on terms that prohibit +them from making any copies of your copyrighted material outside their relationship +with you. + +Conveying under any other circumstances is permitted solely under the conditions +stated below. Sublicensing is not allowed; section 10 makes it unnecessary. + +### 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + +No covered work shall be deemed part of an effective technological measure under any +applicable law fulfilling obligations under article 11 of the WIPO copyright treaty +adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention +of such measures. + +When you convey a covered work, you waive any legal power to forbid circumvention of +technological measures to the extent such circumvention is effected by exercising +rights under this License with respect to the covered work, and you disclaim any +intention to limit operation or modification of the work as a means of enforcing, +against the work's users, your or third parties' legal rights to forbid circumvention +of technological measures. + +### 4. Conveying Verbatim Copies. + +You may convey verbatim copies of the Program's source code as you receive it, in any +medium, provided that you conspicuously and appropriately publish on each copy an +appropriate copyright notice; keep intact all notices stating that this License and +any non-permissive terms added in accord with section 7 apply to the code; keep +intact all notices of the absence of any warranty; and give all recipients a copy of +this License along with the Program. + +You may charge any price or no price for each copy that you convey, and you may offer +support or warranty protection for a fee. + +### 5. Conveying Modified Source Versions. + +You may convey a work based on the Program, or the modifications to produce it from +the Program, in the form of source code under the terms of section 4, provided that +you also meet all of these conditions: + +* **a)** The work must carry prominent notices stating that you modified it, and giving a +relevant date. +* **b)** The work must carry prominent notices stating that it is released under this +License and any conditions added under section 7. This requirement modifies the +requirement in section 4 to “keep intact all notices”. +* **c)** You must license the entire work, as a whole, under this License to anyone who +comes into possession of a copy. This License will therefore apply, along with any +applicable section 7 additional terms, to the whole of the work, and all its parts, +regardless of how they are packaged. This License gives no permission to license the +work in any other way, but it does not invalidate such permission if you have +separately received it. +* **d)** If the work has interactive user interfaces, each must display Appropriate Legal +Notices; however, if the Program has interactive interfaces that do not display +Appropriate Legal Notices, your work need not make them do so. + +A compilation of a covered work with other separate and independent works, which are +not by their nature extensions of the covered work, and which are not combined with +it such as to form a larger program, in or on a volume of a storage or distribution +medium, is called an “aggregate” if the compilation and its resulting +copyright are not used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work in an aggregate +does not cause this License to apply to the other parts of the aggregate. + +### 6. Conveying Non-Source Forms. + +You may convey a covered work in object code form under the terms of sections 4 and +5, provided that you also convey the machine-readable Corresponding Source under the +terms of this License, in one of these ways: + +* **a)** Convey the object code in, or embodied in, a physical product (including a +physical distribution medium), accompanied by the Corresponding Source fixed on a +durable physical medium customarily used for software interchange. +* **b)** Convey the object code in, or embodied in, a physical product (including a +physical distribution medium), accompanied by a written offer, valid for at least +three years and valid for as long as you offer spare parts or customer support for +that product model, to give anyone who possesses the object code either (1) a copy of +the Corresponding Source for all the software in the product that is covered by this +License, on a durable physical medium customarily used for software interchange, for +a price no more than your reasonable cost of physically performing this conveying of +source, or (2) access to copy the Corresponding Source from a network server at no +charge. +* **c)** Convey individual copies of the object code with a copy of the written offer to +provide the Corresponding Source. This alternative is allowed only occasionally and +noncommercially, and only if you received the object code with such an offer, in +accord with subsection 6b. +* **d)** Convey the object code by offering access from a designated place (gratis or for +a charge), and offer equivalent access to the Corresponding Source in the same way +through the same place at no further charge. You need not require recipients to copy +the Corresponding Source along with the object code. If the place to copy the object +code is a network server, the Corresponding Source may be on a different server +(operated by you or a third party) that supports equivalent copying facilities, +provided you maintain clear directions next to the object code saying where to find +the Corresponding Source. Regardless of what server hosts the Corresponding Source, +you remain obligated to ensure that it is available for as long as needed to satisfy +these requirements. +* **e)** Convey the object code using peer-to-peer transmission, provided you inform +other peers where the object code and Corresponding Source of the work are being +offered to the general public at no charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded from the +Corresponding Source as a System Library, need not be included in conveying the +object code work. + +A “User Product” is either (1) a “consumer product”, which +means any tangible personal property which is normally used for personal, family, or +household purposes, or (2) anything designed or sold for incorporation into a +dwelling. In determining whether a product is a consumer product, doubtful cases +shall be resolved in favor of coverage. For a particular product received by a +particular user, “normally used” refers to a typical or common use of +that class of product, regardless of the status of the particular user or of the way +in which the particular user actually uses, or expects or is expected to use, the +product. A product is a consumer product regardless of whether the product has +substantial commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + +“Installation Information” for a User Product means any methods, +procedures, authorization keys, or other information required to install and execute +modified versions of a covered work in that User Product from a modified version of +its Corresponding Source. The information must suffice to ensure that the continued +functioning of the modified object code is in no case prevented or interfered with +solely because modification has been made. + +If you convey an object code work under this section in, or with, or specifically for +use in, a User Product, and the conveying occurs as part of a transaction in which +the right of possession and use of the User Product is transferred to the recipient +in perpetuity or for a fixed term (regardless of how the transaction is +characterized), the Corresponding Source conveyed under this section must be +accompanied by the Installation Information. But this requirement does not apply if +neither you nor any third party retains the ability to install modified object code +on the User Product (for example, the work has been installed in ROM). + +The requirement to provide Installation Information does not include a requirement to +continue to provide support service, warranty, or updates for a work that has been +modified or installed by the recipient, or for the User Product in which it has been +modified or installed. Access to a network may be denied when the modification itself +materially and adversely affects the operation of the network or violates the rules +and protocols for communication across the network. + +Corresponding Source conveyed, and Installation Information provided, in accord with +this section must be in a format that is publicly documented (and with an +implementation available to the public in source code form), and must require no +special password or key for unpacking, reading or copying. + +### 7. Additional Terms. + +“Additional permissions” are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. Additional +permissions that are applicable to the entire Program shall be treated as though they +were included in this License, to the extent that they are valid under applicable +law. If additional permissions apply only to part of the Program, that part may be +used separately under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option remove any +additional permissions from that copy, or from any part of it. (Additional +permissions may be written to require their own removal in certain cases when you +modify the work.) You may place additional permissions on material, added by you to a +covered work, for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you add to a +covered work, you may (if authorized by the copyright holders of that material) +supplement the terms of this License with terms: + +* **a)** Disclaiming warranty or limiting liability differently from the terms of +sections 15 and 16 of this License; or +* **b)** Requiring preservation of specified reasonable legal notices or author +attributions in that material or in the Appropriate Legal Notices displayed by works +containing it; or +* **c)** Prohibiting misrepresentation of the origin of that material, or requiring that +modified versions of such material be marked in reasonable ways as different from the +original version; or +* **d)** Limiting the use for publicity purposes of names of licensors or authors of the +material; or +* **e)** Declining to grant rights under trademark law for use of some trade names, +trademarks, or service marks; or +* **f)** Requiring indemnification of licensors and authors of that material by anyone +who conveys the material (or modified versions of it) with contractual assumptions of +liability to the recipient, for any liability that these contractual assumptions +directly impose on those licensors and authors. + +All other non-permissive additional terms are considered “further +restrictions” within the meaning of section 10. If the Program as you received +it, or any part of it, contains a notice stating that it is governed by this License +along with a term that is a further restriction, you may remove that term. If a +license document contains a further restriction but permits relicensing or conveying +under this License, you may add to a covered work material governed by the terms of +that license document, provided that the further restriction does not survive such +relicensing or conveying. + +If you add terms to a covered work in accord with this section, you must place, in +the relevant source files, a statement of the additional terms that apply to those +files, or a notice indicating where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the form of a +separately written license, or stated as exceptions; the above requirements apply +either way. + +### 8. Termination. + +You may not propagate or modify a covered work except as expressly provided under +this License. Any attempt otherwise to propagate or modify it is void, and will +automatically terminate your rights under this License (including any patent licenses +granted under the third paragraph of section 11). + +However, if you cease all violation of this License, then your license from a +particular copyright holder is reinstated (a) provisionally, unless and until the +copyright holder explicitly and finally terminates your license, and (b) permanently, +if the copyright holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + +Moreover, your license from a particular copyright holder is reinstated permanently +if the copyright holder notifies you of the violation by some reasonable means, this +is the first time you have received notice of violation of this License (for any +work) from that copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + +Termination of your rights under this section does not terminate the licenses of +parties who have received copies or rights from you under this License. If your +rights have been terminated and not permanently reinstated, you do not qualify to +receive new licenses for the same material under section 10. + +### 9. Acceptance Not Required for Having Copies. + +You are not required to accept this License in order to receive or run a copy of the +Program. Ancillary propagation of a covered work occurring solely as a consequence of +using peer-to-peer transmission to receive a copy likewise does not require +acceptance. However, nothing other than this License grants you permission to +propagate or modify any covered work. These actions infringe copyright if you do not +accept this License. Therefore, by modifying or propagating a covered work, you +indicate your acceptance of this License to do so. + +### 10. Automatic Licensing of Downstream Recipients. + +Each time you convey a covered work, the recipient automatically receives a license +from the original licensors, to run, modify and propagate that work, subject to this +License. You are not responsible for enforcing compliance by third parties with this +License. + +An “entity transaction” is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an organization, or +merging organizations. If propagation of a covered work results from an entity +transaction, each party to that transaction who receives a copy of the work also +receives whatever licenses to the work the party's predecessor in interest had or +could give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if the predecessor +has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the rights granted or +affirmed under this License. For example, you may not impose a license fee, royalty, +or other charge for exercise of rights granted under this License, and you may not +initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging +that any patent claim is infringed by making, using, selling, offering for sale, or +importing the Program or any portion of it. + +### 11. Patents. + +A “contributor” is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The work thus +licensed is called the contributor's “contributor version”. + +A contributor's “essential patent claims” are all patent claims owned or +controlled by the contributor, whether already acquired or hereafter acquired, that +would be infringed by some manner, permitted by this License, of making, using, or +selling its contributor version, but do not include claims that would be infringed +only as a consequence of further modification of the contributor version. For +purposes of this definition, “control” includes the right to grant patent +sublicenses in a manner consistent with the requirements of this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free patent license +under the contributor's essential patent claims, to make, use, sell, offer for sale, +import and otherwise run, modify and propagate the contents of its contributor +version. + +In the following three paragraphs, a “patent license” is any express +agreement or commitment, however denominated, not to enforce a patent (such as an +express permission to practice a patent or covenant not to sue for patent +infringement). To “grant” such a patent license to a party means to make +such an agreement or commitment not to enforce a patent against the party. + +If you convey a covered work, knowingly relying on a patent license, and the +Corresponding Source of the work is not available for anyone to copy, free of charge +and under the terms of this License, through a publicly available network server or +other readily accessible means, then you must either (1) cause the Corresponding +Source to be so available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner consistent with +the requirements of this License, to extend the patent license to downstream +recipients. “Knowingly relying” means you have actual knowledge that, but +for the patent license, your conveying the covered work in a country, or your +recipient's use of the covered work in a country, would infringe one or more +identifiable patents in that country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or arrangement, you +convey, or propagate by procuring conveyance of, a covered work, and grant a patent +license to some of the parties receiving the covered work authorizing them to use, +propagate, modify or convey a specific copy of the covered work, then the patent +license you grant is automatically extended to all recipients of the covered work and +works based on it. + +A patent license is “discriminatory” if it does not include within the +scope of its coverage, prohibits the exercise of, or is conditioned on the +non-exercise of one or more of the rights that are specifically granted under this +License. You may not convey a covered work if you are a party to an arrangement with +a third party that is in the business of distributing software, under which you make +payment to the third party based on the extent of your activity of conveying the +work, and under which the third party grants, to any of the parties who would receive +the covered work from you, a discriminatory patent license (a) in connection with +copies of the covered work conveyed by you (or copies made from those copies), or (b) +primarily for and in connection with specific products or compilations that contain +the covered work, unless you entered into that arrangement, or that patent license +was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting any implied +license or other defenses to infringement that may otherwise be available to you +under applicable patent law. + +### 12. No Surrender of Others' Freedom. + +If conditions are imposed on you (whether by court order, agreement or otherwise) +that contradict the conditions of this License, they do not excuse you from the +conditions of this License. If you cannot convey a covered work so as to satisfy +simultaneously your obligations under this License and any other pertinent +obligations, then as a consequence you may not convey it at all. For example, if you +agree to terms that obligate you to collect a royalty for further conveying from +those to whom you convey the Program, the only way you could satisfy both those terms +and this License would be to refrain entirely from conveying the Program. + +### 13. Use with the GNU Affero General Public License. + +Notwithstanding any other provision of this License, you have permission to link or +combine any covered work with a work licensed under version 3 of the GNU Affero +General Public License into a single combined work, and to convey the resulting work. +The terms of this License will continue to apply to the part which is the covered +work, but the special requirements of the GNU Affero General Public License, section +13, concerning interaction through a network will apply to the combination as such. + +### 14. Revised Versions of this License. + +The Free Software Foundation may publish revised and/or new versions of the GNU +General Public License from time to time. Such new versions will be similar in spirit +to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program specifies that +a certain numbered version of the GNU General Public License “or any later +version” applies to it, you have the option of following the terms and +conditions either of that numbered version or of any later version published by the +Free Software Foundation. If the Program does not specify a version number of the GNU +General Public License, you may choose any version ever published by the Free +Software Foundation. + +If the Program specifies that a proxy can decide which future versions of the GNU +General Public License can be used, that proxy's public statement of acceptance of a +version permanently authorizes you to choose that version for the Program. + +Later license versions may give you additional or different permissions. However, no +additional obligations are imposed on any author or copyright holder as a result of +your choosing to follow a later version. + +### 15. Disclaimer of Warranty. + +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER +EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE +QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE +DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +### 16. Limitation of Liability. + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY +COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS +PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, +INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE +OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE +WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + +### 17. Interpretation of Sections 15 and 16. + +If the disclaimer of warranty and limitation of liability provided above cannot be +given local legal effect according to their terms, reviewing courts shall apply local +law that most closely approximates an absolute waiver of all civil liability in +connection with the Program, unless a warranty or assumption of liability accompanies +a copy of the Program in return for a fee. + +END OF TERMS AND CONDITIONS + +## How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest possible use to +the public, the best way to achieve this is to make it free software which everyone +can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest to attach them +to the start of each source file to most effectively state the exclusion of warranty; +and each file should have at least the “copyright” line and a pointer to +where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + +If the program does terminal interaction, make it output a short notice like this +when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type 'show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type 'show c' for details. + +The hypothetical commands 'show w' and 'show c' should show the appropriate parts of +the General Public License. Of course, your program's commands might be different; +for a GUI interface, you would use an “about box”. + +You should also get your employer (if you work as a programmer) or school, if any, to +sign a “copyright disclaimer” for the program, if necessary. For more +information on this, and how to apply and follow the GNU GPL, see +<>. + +The GNU General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may consider it +more useful to permit linking proprietary applications with the library. If this is +what you want to do, use the GNU Lesser General Public License instead of this +License. But first, please read +<>. \ No newline at end of file diff --git a/block_profile360_multilenguage/profile360/README.md b/block_profile360_multilenguage/profile360/README.md new file mode 100644 index 0000000..8b906d9 --- /dev/null +++ b/block_profile360_multilenguage/profile360/README.md @@ -0,0 +1,123 @@ +# User Profile 360 Block # + +A comprehensive user profile block for Moodle that displays user statistics, course progress, certifications, and custom profile fields in an elegant card-based interface. + +## Features ## + +- **User Statistics**: Total enrolled courses, completed courses, courses in progress, and certifications +- **Course Progress**: Interactive modals showing detailed course information +- **Custom Profile Fields**: Configurable display of user custom fields +- **Banner Customization**: Upload custom banner images or use default gradient +- **Responsive Design**: Mobile-friendly interface with Bootstrap 4.6.2 compatibility +- **Dark Mode Support**: Full dark mode compatibility with proper contrast ratios +- **Internationalization**: Complete multilingual support (English and Spanish Mexican included) +- **Performance Optimized**: Caching system and optimized SQL queries +- **FontAwesome Integration**: Free FontAwesome icons with CDN fallback + +## Configuration ## + +### Global Settings ### +1. Go to **Site administration > Plugins > Blocks > User Profile 360** +2. Configure: + - **Banner Image**: Upload custom banner background image + - **Custom Fields**: Select which user profile fields to display + +### Block Settings ### +1. Add the block to any page +2. Configure block title (leave empty to hide header) +3. Position and customize as needed + +## Technical Specifications ## + +- **Moodle Version**: 4.1+ compatible +- **PHP Version**: 7.4+ +- **Database**: MySQL/PostgreSQL compatible +- **Cache**: 5-minute user data caching for performance +- **File Support**: JPG, JPEG, PNG, GIF for banner images + +## Installation ## + +### Via ZIP Upload ### +1. Log in as admin and go to **Site administration > Plugins > Install plugins** +2. Upload the ZIP file +3. Follow the installation wizard + +### Manual Installation ### +1. Extract files to `{moodle}/blocks/profile360/` +2. Go to **Site administration > Notifications** +3. Complete the installation + +### CLI Installation ### +```bash +php admin/cli/upgrade.php +``` + +## Usage ## + +1. **Add Block**: Add "User Profile 360" block to dashboard or course pages +2. **Configure Fields**: Select custom fields to display in global settings +3. **Upload Banner**: Customize banner image in plugin settings +4. **Customize Title**: Set block title or leave empty to hide header + +## Permissions ## + +- `block/profile360:view` - View the block content +- `block/profile360:addinstance` - Add block to pages +- `block/profile360:myaddinstance` - Add block to dashboard + +## Troubleshooting ## + +### Common Issues ### +- **Images not loading**: Check file permissions and pluginfile.php access +- **Styles not applying**: Clear Moodle cache and update plugin version +- **Performance issues**: Verify cache is enabled and working + +### Cache Management ### +The plugin uses application cache for user data. To clear cache: +```bash +php admin/cli/purge_caches.php +``` + +## Changelog ## + +### Version 1.4.1 (2025-01-01) ### +- Fixed Mustache template conversion errors +- Improved performance with caching system +- Added configurable block header +- Enhanced dark mode support +- Complete internationalization +- FontAwesome free icons integration + +### Version 1.3.x ### +- Added banner image configuration +- Implemented proper AMD module loading +- Fixed CSS loading issues +- Added Spanish Mexican translation + +### Version 1.2.x ### +- Removed inline JavaScript +- Added dark mode support +- Updated to FontAwesome 5.15.4 +- Performance optimizations + +## Support ## + +For support and bug reports, please contact: +- **Email**: salvador.martinez@espacio360.com.mx +- **Organization**: Espacio360 + +## License ## + +Copyright 2023 Espacio360 + +This program is free software: you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation, either version 3 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program. If not, see . \ No newline at end of file diff --git a/block_profile360_multilenguage/profile360/UserInfo.php b/block_profile360_multilenguage/profile360/UserInfo.php new file mode 100644 index 0000000..13227ee --- /dev/null +++ b/block_profile360_multilenguage/profile360/UserInfo.php @@ -0,0 +1,400 @@ +user = $USER; + profile_load_custom_fields($this->user); + //var_dump($this->user); + } + + /** + * Obtiene el nombre de usuario del usuario en sesión. + * + * @return string El nombre de usuario. + */ + public function getUsername() + { + return $this->user->username; + } + + /** + * Obtiene el nombre completo del usuario en sesión. + * + * @return string El nombre completo del usuario. + */ + public function getFullName() + { + $firstname = isset($this->user->firstname) ? $this->user->firstname : ''; + $lastname = isset($this->user->lastname) ? $this->user->lastname : ''; + + return trim($firstname . ' ' . $lastname); + } + + /** + * Obtiene la dirección de correo electrónico del usuario en sesión. + * + * @return string La dirección de correo electrónico del usuario. + */ + public function getEmail() + { + return $this->user->email; + } + + /** + * Obtiene la URL de la imagen de perfil del usuario en sesión. + * + * @return string La URL de la imagen de perfil. + */ + public function getProfileImage() + { + $imageUrl = $this->getprofilepictureurl(); + return $imageUrl; + } + + /** + * Obtiene el total de cursos en los que está inscrito el usuario en sesión. + * + * @return int El número total de cursos inscritos. + */ + public function getTotalEnrolledCourses() + { + global $USER, $DB; + + if (!empty($USER->id)) { + $totalEnrolledCourses = $DB->count_records('user_enrolments', array('userid' => $USER->id)); + return $totalEnrolledCourses; + } + + return 0; + } + + + + +/** + * Obtiene la lista de cursos completados por el usuario en sesión o que su avance sea del 100% + * + * @return array La lista de cursos completados. + */ +public function getCompletedCourses() +{ + global $USER, $CFG, $DB; + require_once("$CFG->libdir/completionlib.php"); + + if (empty($USER->id)) { + return []; + } + + $completedCourses = []; + $courses = enrol_get_all_users_courses($USER->id); + + foreach ($courses as $course) { + $isCompleted = false; + + // Método 1: Verificar completion oficial de Moodle + if (isset($course->enablecompletion) && $course->enablecompletion) { + $completionInfo = new \completion_info($course); + if ($completionInfo->is_enabled() && $completionInfo->is_course_complete($USER->id)) { + $isCompleted = true; + } + } + + // Método 2: Verificar progreso del 100% + if (!$isCompleted) { + $courseProgressPercentage = \core_completion\progress::get_course_progress_percentage($course, $USER->id); + if ($courseProgressPercentage !== null && $courseProgressPercentage >= 100) { + $isCompleted = true; + } + } + + // Método 3: Verificar completions en la base de datos + if (!$isCompleted) { + $sql = "SELECT COUNT(*) + FROM {course_completions} cc + WHERE cc.userid = :userid + AND cc.course = :courseid + AND cc.timecompleted IS NOT NULL"; + + $completionCount = $DB->count_records_sql($sql, [ + 'userid' => $USER->id, + 'courseid' => $course->id + ]); + + if ($completionCount > 0) { + $isCompleted = true; + } + } + + if ($isCompleted) { + $completedCourses[] = $course; + } + } + + return $completedCourses; +} + + + + + + /** + * Obtiene la lista de cursos en progreso del usuario en sesión. + * + * @return array La lista de cursos en progreso. + */ + public function getInProgressCourses() + { + global $USER; + + if (!empty($USER->id)) { + $inProgressCourses = []; + + // Obtener todos los cursos + $courses = enrol_get_all_users_courses($USER->id); + + foreach ($courses as $course) { + $courseProgressPercentage = \core_completion\progress::get_course_progress_percentage($course, $USER->id); + if ($courseProgressPercentage > 0 && $courseProgressPercentage < 100) { + $inProgressCourses[] = $course; + } + } + + return $inProgressCourses; + } + + return []; + } + + + + /** + * Obtiene la lista de cursos en los que el usuario en sesión ha obtenido un certificado. + * + * @return array La lista de cursos con certificado. + */ + public function getCertifiedCourses() + { + $certificatesInfo = new CertificatesInfo($this->user); + $certifiedCourses = $certificatesInfo->get_all_certificates(); + return $certifiedCourses; + } + + /** + * Obtiene el número de cursos en los que el usuario en sesión ha obtenido un certificado. + * + * @return int El número de cursos con certificado. + */ + public function getCertifiedCoursesCount() + { + $certificatesInfo = new CertificatesInfo($this->user); + + $certifiedCourses = $certificatesInfo->get_all_certificates(); + + return count($certifiedCourses); + } + + /** + * Retrieves the URL for the user's profile picture, if one is available. + * + * + * @return string URL to the photo image file but with $1 for the size. + */ + private function getprofilepictureurl() + { + if (isloggedin() && !isguestuser() && $this->user->picture > 0) { + $usercontext = context_user::instance($this->user->id, IGNORE_MISSING); + $url = moodle_url::make_pluginfile_url($usercontext->id, 'user', 'icon', null, '/', "f$1") + . '?rev=' . $this->user->picture; + } else { + // If the user does not have a profile picture, use the default faceless picture. + global $PAGE, $CFG; + $renderer = $PAGE->get_renderer('core'); + if ($CFG->branch >= 33) { + $url = $renderer->image_url('u/f$1'); + } else { + $url = $renderer->pix_url('u/f$1'); // Deprecated as of Moodle 3.3. + } + } + return str_replace('/f%24', '/f$', $url); + } + + /** + * Obtiene la URL para editar el perfil del usuario en sesión. + * + * @return string La URL para editar el perfil. + */ + public function getEditProfileUrl() + { + global $CFG; + + // Verificar si el usuario está autenticado y no es un usuario invitado + if (isloggedin() && !isguestuser()) { + // Crear el contexto del usuario + $userContext = context_user::instance($this->user->id, IGNORE_MISSING); + + // Construir la URL para editar el perfil + $editProfileUrl = new moodle_url($CFG->wwwroot . '/user/edit.php'); + $editProfileUrl->param('id', $this->user->id); + $editProfileUrl->param('course', SITEID); + $editProfileUrl->param('userprofile', $userContext->id); + + return $editProfileUrl->out(); + } + // Devolver una URL predeterminada o un mensaje de error si el usuario no está autenticado + return (new moodle_url($CFG->wwwroot . '/user/edit.php'))->out(); + } + + public function getCompletedCoursesAsString() + { + $completedCourses = $this->getCompletedCourses(); + $count = count($completedCourses); + return $count > 0 ? (string)$count : "0"; + } + + public function getInProgressCoursesAsString() + { + $inProgressCourses = $this->getInProgressCourses(); + + return !empty($inProgressCourses) ? sizeof($inProgressCourses) : "0"; + } + + /** + * Obtiene la lista de cursos vencidos del usuario en sesión. + * + * @return array La lista de cursos vencidos. + */ + public function getExpiredCourses() + { + global $USER, $DB; + + if (!empty($USER->id)) { + $expiredCourses = []; + $currentDate = new DateTime(); + + // Obtener todos los cursos + $courses = enrol_get_all_users_courses($USER->id); + + foreach ($courses as $course) { + $courseEndDate = $this->getEndDateByCourseId($course->id); + + if ($courseEndDate !== null) { + $endDate = new DateTime(date('Y-m-d', strtotime($courseEndDate))); + + if ($currentDate > $endDate) { + // El curso ha vencido + $expiredCourses[] = $course; + } + } + } + + return $expiredCourses; + } + + return []; + } + + /** + * Obtiene la lista de cursos por vencer del usuario en sesión. + * + * @param int $daysThreshold Umbral de días para considerar cursos por vencer. + * @return array La lista de cursos por vencer. + */ + public function getEndingSoonCourses($daysThreshold = 5) + { + global $USER; + + if (!empty($USER->id)) { + $endingSoonCourses = []; + $currentDate = new DateTime(); + + // Obtener todos los cursos + $courses = enrol_get_all_users_courses($USER->id); + + foreach ($courses as $course) { + $courseEndDate = $this->getEndDateByCourseId($course->id); + + if ($courseEndDate !== null) { + $endDate = new DateTime($courseEndDate); + $difference = $currentDate->diff($endDate); + + // Verificar si el curso vence pronto + if ($difference->days <= $daysThreshold) { + $endingSoonCourses[] = $course; + } + } + } + + return $endingSoonCourses; + } + + return []; + } + + /** + * Obtiene la fecha de vencimiento por ID de curso. + * + * @param int $courseId ID del curso. + * @return string|null La fecha de vencimiento en formato dd-mm-Y o null si no está definida. + */ + public function getEndDateByCourseId($courseId) + { + global $USER, $DB; + + // Obtén el ID del usuario actual + $userId = $USER->id; + + // Consulta para obtener la fecha de vencimiento personalizada del usuario para el curso + $sql = "SELECT ue.timeend + FROM {user_enrolments} ue + WHERE ue.userid = :userid + AND ue.enrolid IN (SELECT id FROM {enrol} WHERE courseid = :courseid) + ORDER BY ue.timeend DESC + LIMIT 1"; + + $params = [ + 'userid' => $userId, + 'courseid' => $courseId, + ]; + + $userCourseEndDate = $DB->get_field_sql($sql, $params); + if ($userCourseEndDate !== null && $userCourseEndDate !== 0) { + // Convierte el timestamp a una cadena de fecha en formato dd-mm-Y + return date('d-m-Y', $userCourseEndDate); + } + + return null; + // Convierte el timestamp a una cadena de fecha en formato dd-mm-Y + + } + + /** + * Obtiene todos los campos personalizados del usuario en sesión. + * + * @return array El array asociativo de campos personalizados. + */ + public function getCustomFields() + { + // Obtén la sección de campos personalizados + $customFields = $this->user->profile; + return $customFields; + } + +} \ No newline at end of file diff --git a/block_profile360_multilenguage/profile360/amd/build/profile360.min.js b/block_profile360_multilenguage/profile360/amd/build/profile360.min.js new file mode 100644 index 0000000..fe34eb0 --- /dev/null +++ b/block_profile360_multilenguage/profile360/amd/build/profile360.min.js @@ -0,0 +1,3 @@ +define("block_profile360/profile360",["jquery","core/ajax"],(function($,Ajax){var Profile360={init:function(){this.initModals(),this.initEndingSoonModal()},initModals:function(){this.bindModalEvents()},bindModalEvents:function(){$(".completedCourses").on("click",(function(e){e.preventDefault(),Profile360.loadCoursesData("completed","#modalCompletedCourses")})),$(".inProgressCourses").on("click",(function(e){e.preventDefault(),Profile360.loadCoursesData("inprogress","#modalInProgressCourses")})),$(".certificatedCourses").on("click",(function(e){e.preventDefault(),Profile360.loadCoursesData("certified","#modalCertifiedCourses")}))},loadCoursesData:function(type,modalSelector){var modalBody=$(modalSelector).find("ol");modalBody.html("
  • Cargando...
  • "),Ajax.call([{methodname:"block_profile360_get_courses_data",args:{type:type}}])[0].done((function(data){modalBody.empty(),0!==data.length?data.forEach((function(course){var link;link="certified"===type&&course.certificates&&course.certificates[0]?''+course.fullname+"":''+course.fullname+"",modalBody.append("
  • "+link+"
  • ")})):modalBody.append("
  • No hay cursos disponibles
  • ")})).fail((function(){modalBody.html("
  • Error al cargar los datos
  • ")}))},initEndingSoonModal:function(){var modal=$("#ModalEndingSoonCourses");!0!==modal.data("showmodal")&&"1"!==modal.data("showmodal")||(this.loadEndingSoonCourses(),modal.addClass("show").css("display","block")),modal.on("click",".btnclose, .modal",(function(e){(e.target===this||$(e.target).hasClass("btnclose"))&&modal.removeClass("show").css("display","none")}))},loadEndingSoonCourses:function(){var modalBody=$("#ModalEndingSoonCourses .modal-body ol");Ajax.call([{methodname:"block_profile360_get_courses_data",args:{type:"ending"}}])[0].done((function(data){modalBody.empty(),data.forEach((function(course){var courseUrl="/course/view.php?id="+course.id;modalBody.append('
  • '+course.fullname+"
  • ")}))})).fail((function(){modalBody.html("
  • Error al cargar los cursos
  • ")}))}};return{init:function(){$(document).ready((function(){Profile360.init()}))}}})); + +//# sourceMappingURL=profile360.min.js.map \ No newline at end of file diff --git a/block_profile360_multilenguage/profile360/amd/build/profile360.min.js.map b/block_profile360_multilenguage/profile360/amd/build/profile360.min.js.map new file mode 100644 index 0000000..56cda5a --- /dev/null +++ b/block_profile360_multilenguage/profile360/amd/build/profile360.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"profile360.min.js","sources":["../src/profile360.js"],"sourcesContent":["define(['jquery', 'core/ajax'], function($, Ajax) {\n 'use strict';\n\n var Profile360 = {\n init: function() {\n this.initModals();\n this.initEndingSoonModal();\n },\n\n initModals: function() {\n this.bindModalEvents();\n },\n\n bindModalEvents: function() {\n $('.completedCourses').on('click', function(e) {\n e.preventDefault();\n Profile360.loadCoursesData('completed', '#modalCompletedCourses');\n });\n\n $('.inProgressCourses').on('click', function(e) {\n e.preventDefault();\n Profile360.loadCoursesData('inprogress', '#modalInProgressCourses');\n });\n\n $('.certificatedCourses').on('click', function(e) {\n e.preventDefault();\n Profile360.loadCoursesData('certified', '#modalCertifiedCourses');\n });\n },\n\n loadCoursesData: function(type, modalSelector) {\n var modal = $(modalSelector);\n var modalBody = modal.find('ol');\n\n modalBody.html('
  • Cargando...
  • ');\n\n Ajax.call([{\n methodname: 'block_profile360_get_courses_data',\n args: { type: type }\n }])[0].done(function(data) {\n modalBody.empty();\n\n if (data.length === 0) {\n modalBody.append('
  • No hay cursos disponibles
  • ');\n return;\n }\n\n data.forEach(function(course) {\n var link;\n if (type === 'certified' && course.certificates && course.certificates[0]) {\n link = '' + course.fullname + '';\n } else {\n link = '' + course.fullname + '';\n }\n modalBody.append('
  • ' + link + '
  • ');\n });\n }).fail(function() {\n modalBody.html('
  • Error al cargar los datos
  • ');\n });\n },\n\n initEndingSoonModal: function() {\n var modal = $('#ModalEndingSoonCourses');\n\n if (modal.data('showmodal') === true || modal.data('showmodal') === '1') {\n this.loadEndingSoonCourses();\n modal.addClass('show').css('display', 'block');\n }\n\n modal.on('click', '.btnclose, .modal', function(e) {\n if (e.target === this || $(e.target).hasClass('btnclose')) {\n modal.removeClass('show').css('display', 'none');\n }\n });\n },\n\n loadEndingSoonCourses: function() {\n var modalBody = $('#ModalEndingSoonCourses .modal-body ol');\n\n Ajax.call([{\n methodname: 'block_profile360_get_courses_data',\n args: { type: 'ending' }\n }])[0].done(function(data) {\n modalBody.empty();\n\n data.forEach(function(course) {\n var courseUrl = '/course/view.php?id=' + course.id;\n modalBody.append('
  • ' + course.fullname + '
  • ');\n });\n }).fail(function() {\n modalBody.html('
  • Error al cargar los cursos
  • ');\n });\n },\n };\n\n return {\n init: function() {\n $(document).ready(function() {\n Profile360.init();\n });\n }\n };\n});\n"],"names":["define","$","Ajax","Profile360","init","initModals","initEndingSoonModal","bindModalEvents","on","e","preventDefault","loadCoursesData","type","modalSelector","modalBody","find","html","call","methodname","args","done","data","empty","length","forEach","course","link","certificates","downloadurl","fullname","id","append","fail","modal","loadEndingSoonCourses","addClass","css","target","this","hasClass","removeClass","courseUrl","document","ready"],"mappings":"AAAAA,qCAAO,CAAC,SAAU,cAAc,SAASC,EAAGC,UAGpCC,WAAa,CACbC,KAAM,gBACGC,kBACAC,uBAGTD,WAAY,gBACHE,mBAGTA,gBAAiB,WACbN,EAAE,qBAAqBO,GAAG,SAAS,SAASC,GACxCA,EAAEC,iBACFP,WAAWQ,gBAAgB,YAAa,6BAG5CV,EAAE,sBAAsBO,GAAG,SAAS,SAASC,GACzCA,EAAEC,iBACFP,WAAWQ,gBAAgB,aAAc,8BAG7CV,EAAE,wBAAwBO,GAAG,SAAS,SAASC,GAC3CA,EAAEC,iBACFP,WAAWQ,gBAAgB,YAAa,8BAIhDA,gBAAiB,SAASC,KAAMC,mBAExBC,UADQb,EAAEY,eACQE,KAAK,MAE3BD,UAAUE,KAAK,wBAEfd,KAAKe,KAAK,CAAC,CACPC,WAAY,oCACZC,KAAM,CAAEP,KAAMA,SACd,GAAGQ,MAAK,SAASC,MACjBP,UAAUQ,QAEU,IAAhBD,KAAKE,OAKTF,KAAKG,SAAQ,SAASC,YACdC,KAEAA,KADS,cAATd,MAAwBa,OAAOE,cAAgBF,OAAOE,aAAa,GAC5D,YAAcF,OAAOE,aAAa,GAAGC,YAAc,qBAAuBH,OAAOI,SAAW,OAE5F,gCAAkCJ,OAAOK,GAAK,KAAOL,OAAOI,SAAW,OAElFf,UAAUiB,OAAO,OAASL,KAAO,YAXjCZ,UAAUiB,OAAO,yCAatBC,MAAK,WACJlB,UAAUE,KAAK,0CAIvBV,oBAAqB,eACb2B,MAAQhC,EAAE,4BAEkB,IAA5BgC,MAAMZ,KAAK,cAAqD,MAA5BY,MAAMZ,KAAK,oBAC1Ca,wBACLD,MAAME,SAAS,QAAQC,IAAI,UAAW,UAG1CH,MAAMzB,GAAG,QAAS,qBAAqB,SAASC,IACxCA,EAAE4B,SAAWC,MAAQrC,EAAEQ,EAAE4B,QAAQE,SAAS,cAC1CN,MAAMO,YAAY,QAAQJ,IAAI,UAAW,YAKrDF,sBAAuB,eACfpB,UAAYb,EAAE,0CAElBC,KAAKe,KAAK,CAAC,CACPC,WAAY,oCACZC,KAAM,CAAEP,KAAM,aACd,GAAGQ,MAAK,SAASC,MACjBP,UAAUQ,QAEVD,KAAKG,SAAQ,SAASC,YACdgB,UAAY,uBAAyBhB,OAAOK,GAChDhB,UAAUiB,OAAO,gBAAkBU,UAAY,KAAOhB,OAAOI,SAAW,mBAE7EG,MAAK,WACJlB,UAAUE,KAAK,kDAKpB,CACHZ,KAAM,WACFH,EAAEyC,UAAUC,OAAM,WACdxC,WAAWC"} \ No newline at end of file diff --git a/block_profile360_multilenguage/profile360/amd/src/profile360.js b/block_profile360_multilenguage/profile360/amd/src/profile360.js new file mode 100644 index 0000000..c07e050 --- /dev/null +++ b/block_profile360_multilenguage/profile360/amd/src/profile360.js @@ -0,0 +1,103 @@ +define(['jquery', 'core/ajax'], function($, Ajax) { + 'use strict'; + + var Profile360 = { + init: function() { + this.initModals(); + this.initEndingSoonModal(); + }, + + initModals: function() { + this.bindModalEvents(); + }, + + bindModalEvents: function() { + $('.completedCourses').on('click', function(e) { + e.preventDefault(); + Profile360.loadCoursesData('completed', '#modalCompletedCourses'); + }); + + $('.inProgressCourses').on('click', function(e) { + e.preventDefault(); + Profile360.loadCoursesData('inprogress', '#modalInProgressCourses'); + }); + + $('.certificatedCourses').on('click', function(e) { + e.preventDefault(); + Profile360.loadCoursesData('certified', '#modalCertifiedCourses'); + }); + }, + + loadCoursesData: function(type, modalSelector) { + var modal = $(modalSelector); + var modalBody = modal.find('ol'); + + modalBody.html('
  • Cargando...
  • '); + + Ajax.call([{ + methodname: 'block_profile360_get_courses_data', + args: { type: type } + }])[0].done(function(data) { + modalBody.empty(); + + if (data.length === 0) { + modalBody.append('
  • No hay cursos disponibles
  • '); + return; + } + + data.forEach(function(course) { + var link; + if (type === 'certified' && course.certificates && course.certificates[0]) { + link = '' + course.fullname + ''; + } else { + link = '' + course.fullname + ''; + } + modalBody.append('
  • ' + link + '
  • '); + }); + }).fail(function() { + modalBody.html('
  • Error al cargar los datos
  • '); + }); + }, + + initEndingSoonModal: function() { + var modal = $('#ModalEndingSoonCourses'); + + if (modal.data('showmodal') === true || modal.data('showmodal') === '1') { + this.loadEndingSoonCourses(); + modal.addClass('show').css('display', 'block'); + } + + modal.on('click', '.btnclose, .modal', function(e) { + if (e.target === this || $(e.target).hasClass('btnclose')) { + modal.removeClass('show').css('display', 'none'); + } + }); + }, + + loadEndingSoonCourses: function() { + var modalBody = $('#ModalEndingSoonCourses .modal-body ol'); + + Ajax.call([{ + methodname: 'block_profile360_get_courses_data', + args: { type: 'ending' } + }])[0].done(function(data) { + modalBody.empty(); + + data.forEach(function(course) { + var courseUrl = '/course/view.php?id=' + course.id; + modalBody.append('
  • ' + course.fullname + '
  • '); + }); + }).fail(function() { + modalBody.html('
  • Error al cargar los cursos
  • '); + }); + }, + }; + + return { + init: function() { + $(document).ready(function() { + Profile360.init(); + }); + } + }; +}); diff --git a/block_profile360_multilenguage/profile360/block_profile360.php b/block_profile360_multilenguage/profile360/block_profile360.php new file mode 100644 index 0000000..c19a84c --- /dev/null +++ b/block_profile360_multilenguage/profile360/block_profile360.php @@ -0,0 +1,299 @@ +. + +require_once ('UserInfo.php'); + +/** + * Block profile360 is defined here. + * + * @package block_profile360 + * @copyright 2023 Espacio360 + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class block_profile360 extends block_base +{ + + /** + * Initializes class member variables. + */ + public function init() + { + // Needed by Moodle to differentiate between blocks. + $this->title = get_string('pluginname', 'block_profile360'); + } + /** + * {@inheritdoc} + */ + public function instance_allow_multiple() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function instance_allow_config() + { + return true; + } + /** + * Returns the block contents. + * + * @return stdClass The block contents. + */ + public function get_content() + { + global $USER, $CFG, $OUTPUT, $PAGE; + + if ($this->content !== null) { + return $this->content; + } + + if (empty($this->instance)) { + $this->content = ''; + return $this->content; + } + + $this->content = new stdClass(); + $this->content->items = array(); + $this->content->icons = array(); + $this->content->footer = ''; + + // Get banner image hash for cache invalidation + $systemcontext = context_system::instance(); + $fs = get_file_storage(); + $files = $fs->get_area_files($systemcontext->id, 'block_profile360', 'bannerimage', 0, 'filename', false); + $bannerhash = ''; + if (!empty($files)) { + $file = reset($files); + $bannerhash = md5($file->get_contenthash() . $file->get_timemodified()); + } + + // Cache key for user data with profile and banner image hashes + $profileimagehash = md5($USER->picture . $USER->timemodified); + $cachekey = 'block_profile360_user_' . $USER->id . '_' . $profileimagehash . '_' . $bannerhash; + $cache = cache::make('block_profile360', 'userdata'); + $cacheddata = $cache->get($cachekey); + + if ($cacheddata !== false) { + $this->content->text = $OUTPUT->render_from_template('block_profile360/profile360', $cacheddata); + return $this->content; + } + // Crear una instancia de la clase UserInfo del espacio de nombres block_profile360 + $userInfo = new UserInfo(); + // Obtener información del usuario + $username = $userInfo->getUsername(); + $fullname = $userInfo->getFullName(); + $email = $userInfo->getEmail(); + $profileImage = $userInfo->getProfileImage(); + $totalEnrolledCourses = $userInfo->getTotalEnrolledCourses(); + $completedCoursesString = $userInfo->getCompletedCoursesAsString(); + $inProgressCoursesString = $userInfo->getInProgressCoursesAsString(); + $editProfileUrl = $userInfo->getEditProfileUrl(); + $certifiedCoursesCount = $userInfo->getCertifiedCoursesCount(); + $endingSoonCourses = $userInfo->getEndingSoonCourses(20); + // Obtén los datos personalizados + $customFields = $userInfo->getCustomFields(); + //Obtener la configuracion del bloque + $blockConfig = get_config('block_profile360'); + if ($blockConfig) { + + // Crear un array con los nombres de los campos personalizados configurados en el bloque + $configuredFields = []; + foreach ($blockConfig as $name => $value) { + if (strpos($name, 'customfield_') === 0 && $value) { + $fieldId = substr($name, strlen('customfield_')); + $configuredFields[$fieldId] = true; + } + } + global $DB; + // Consulta para obtener campos personalizados visibles + $sql = "SELECT shortname, name FROM {user_info_field} WHERE visible > :visible ORDER BY sortorder ASC"; + // Ejecutar la consulta + $namesCustomFields = $DB->get_records_sql_menu($sql, ['visible' => 0]); + // Filtrar los campos personalizados para mostrar solo los configurados en el bloque + $customFieldsToShow = []; + foreach ($customFields as $fieldName => $fieldValue) { + if (isset($configuredFields[$fieldName])) { + $fieldLabel = $namesCustomFields[$fieldName]; + //$fieldLabel = 'Campo'; + switch ($fieldName) { + case 'linkdc3': + $link = "Sin certificación"; + // Validar que el campo esté definido y no esté vacío + if (isset($fieldValue) && !empty(trim($fieldValue))) { + $link = "Descargar"; + } + $customFieldsToShow[] = array('key' => $fieldLabel, 'value' => $link); + break; + case 'ingreso': + $ingreso = ''; + if (isset($customFields[$fieldName]) && $customFields[$fieldName] !== null) { + // Verificar si la fecha está definida y no es 0 + if ($customFields[$fieldName] != 0) { + $ingreso = userdate($customFields[$fieldName], '%d/%m/%Y'); + } + } + $customFieldsToShow[] = array('key' => $fieldLabel, 'value' => $ingreso); + break; + default: + $customFieldsToShow[] = array('key' => $fieldLabel, 'value' => $fieldValue ?? ''); + break; + } + } + } + } + // Get banner image URL (reuse files from cache check) + $bannerImageUrl = ''; + if (!empty($files)) { + $file = reset($files); + $bannerImageUrl = moodle_url::make_pluginfile_url( + $systemcontext->id, + 'block_profile360', + 'bannerimage', + 0, + $file->get_filepath(), + $file->get_filename() + )->out(); + } + + // Configurar el objeto de datos para la plantilla + $data = new stdClass(); + $data->username = $username; + $data->fullname = $fullname; + $data->email = $email; + $data->profileImage = $profileImage; + $data->totalEnrolledCourses = $totalEnrolledCourses; + $data->completedCoursesString = $completedCoursesString; + $data->inProgressCoursesString = $inProgressCoursesString; + $data->editProfileUrl = $editProfileUrl; + $data->certifiedCoursesCount = $certifiedCoursesCount; + // Get courses data once and reuse + $completedCourses = $userInfo->getCompletedCourses(); + $inProgressCourses = $userInfo->getInProgressCourses(); + $certifiedCourses = $userInfo->getCertifiedCourses(); + + // Los datos ahora se cargan via AJAX, no necesitamos JSON embebido + $data->customFieldsToShow = $customFieldsToShow; + $data->bannerImageUrl = $bannerImageUrl; + // Verifica si hay cursos por expirar + global $SESSION; + // Verifica si hay cursos por expirar + if (sizeof($endingSoonCourses) > 0) { + // Verifica si el modal ya se ha mostrado en esta sesión + $modalShown = isset($SESSION->modalShown) ? $SESSION->modalShown : false; + // Si el modal no se ha mostrado y hay cursos por expirar, marca la sesión y agrega el dato al objeto $data + if (!$modalShown) { + $SESSION->modalShown = true; + $data->showModal = true; + // Asegúrate de que jQuery esté cargado antes de agregar scripts que dependan de él + //$PAGE->requires->jquery(); + //$PAGE->requires->js_call_amd('profile360/profile360', 'initialize_modal', [json_encode($endingSoonCourses, JSON_UNESCAPED_UNICODE)]); + } else { + $data->showModal = false; + $SESSION->modalShown = false; + } + } else { + $SESSION->modalShown = false; + $data->showModal = false; + } + + + // Load FontAwesome fallback if not available + $PAGE->requires->css(new moodle_url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css')); + + // Cache the data for 5 minutes + $cache->set($cachekey, $data, 300); + + // Agregar la información al contenido del bloque + $this->content->text = $OUTPUT->render_from_template('block_profile360/profile360', $data); + + + + return $this->content; + } + + /** + * Load required JS and CSS. + */ + public function get_required_javascript() + { + parent::get_required_javascript(); + + // Only load JS if block has content + if ($this->content && !empty($this->content->text)) { + $this->page->requires->js_call_amd('block_profile360/profile360', 'init'); + } + } + + /** + * Hide header if title is empty. + */ + public function hide_header() + { + return empty($this->config->title); + } + + /** + * Defines configuration data. + * + * The function is called immediately after init(). + */ + public function specialization() + { + // Load user defined title and make sure it's never empty. + if (empty($this->config->title)) { + $this->title = get_string('pluginname', 'block_profile360'); + } else { + $this->title = format_string($this->config->title, true, ['context' => $this->context]); + } + } + /** + * Enables global configuration of the block in settings.php. + * + * @return bool True if the global configuration is enabled. + */ + function has_config() + { + return true; + } + /** + * Sets the applicable formats for the block. + * + * @return string[] Array of pages and permissions. + */ + public function applicable_formats() + { + //return array('my' => true, 'site' => true); + return array( + 'admin' => false, + 'site-index' => true, + 'site' => true, + 'course-view' => true, + 'mod' => false, + 'my' => true, + ); + } + + public function html_attributes() + { + // Get default values. + $attributes = parent::html_attributes(); + // Append our class to class attribute. + $attributes['class'] .= ' block_' . $this->name(); + return $attributes; + } +} diff --git a/block_profile360_multilenguage/profile360/classes/external/get_courses_data.php b/block_profile360_multilenguage/profile360/classes/external/get_courses_data.php new file mode 100644 index 0000000..d877b29 --- /dev/null +++ b/block_profile360_multilenguage/profile360/classes/external/get_courses_data.php @@ -0,0 +1,95 @@ +libdir . '/externallib.php'); +require_once($CFG->dirroot . '/blocks/profile360/UserInfo.php'); + +class block_profile360_external_get_courses_data extends external_api { + + public static function get_courses_data_parameters() { + return new external_function_parameters([ + 'type' => new external_value(PARAM_ALPHA, 'Type of courses data to retrieve') + ]); + } + + public static function get_courses_data($type) { + global $USER; + + try { + $params = self::validate_parameters(self::get_courses_data_parameters(), ['type' => $type]); + + require_login(); + + $userInfo = new UserInfo(); + + switch ($params['type']) { + case 'completed': + $courses = $userInfo->getCompletedCourses(); + return self::format_courses_data($courses); + case 'inprogress': + $courses = $userInfo->getInProgressCourses(); + return self::format_courses_data($courses); + case 'certified': + $certifiedCourses = $userInfo->getCertifiedCourses(); + return self::format_certified_courses_data($certifiedCourses); + case 'ending': + $courses = $userInfo->getEndingSoonCourses(20); + return self::format_courses_data($courses); + default: + throw new invalid_parameter_exception('Invalid course type'); + } + } catch (Exception $e) { + error_log('Error in get_courses_data: ' . $e->getMessage()); + return []; + } + } + + private static function format_courses_data($courses) { + $result = []; + foreach ($courses as $course) { + $courseurl = new moodle_url('/course/view.php', ['id' => $course->id]); + $result[] = [ + 'id' => (int)$course->id, + 'fullname' => $course->fullname, + 'courseurl' => $courseurl->out(false) + ]; + } + return $result; + } + + private static function format_certified_courses_data($certifiedCourses) { + $result = []; + foreach ($certifiedCourses as $courseData) { + $certificates = []; + if (isset($courseData['certificates'])) { + foreach ($courseData['certificates'] as $cert) { + $certificates[] = [ + 'downloadurl' => $cert->downloadurl + ]; + } + } + + $result[] = [ + 'id' => $courseData['courseid'], + 'fullname' => $courseData['fullname'], + 'certificates' => $certificates + ]; + } + return $result; + } + + public static function get_courses_data_returns() { + return new external_multiple_structure( + new external_single_structure([ + 'id' => new external_value(PARAM_INT, 'Course ID'), + 'fullname' => new external_value(PARAM_TEXT, 'Course full name'), + 'courseurl' => new external_value(PARAM_URL, 'Course URL', VALUE_OPTIONAL), + 'certificates' => new external_multiple_structure( + new external_single_structure([ + 'downloadurl' => new external_value(PARAM_URL, 'Certificate download URL') + ]), 'Certificates', VALUE_OPTIONAL + ) + ]) + ); + } +} \ No newline at end of file diff --git a/block_profile360_multilenguage/profile360/classes/observer.php b/block_profile360_multilenguage/profile360/classes/observer.php new file mode 100644 index 0000000..aaa7723 --- /dev/null +++ b/block_profile360_multilenguage/profile360/classes/observer.php @@ -0,0 +1,66 @@ +. + +/** + * Event observer for block_profile360. + * + * @package block_profile360 + * @copyright 2023 Espacio360 + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace block_profile360; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Event observer class. + */ +class observer { + + /** + * Clear user cache when user is updated. + * + * @param \core\event\user_updated $event + */ + public static function user_updated(\core\event\user_updated $event) { + $cache = \cache::make('block_profile360', 'userdata'); + $cachekey = 'block_profile360_user_' . $event->relateduserid; + $cache->delete($cachekey); + } + + /** + * Clear user cache when user enrolment changes. + * + * @param \core\event\base $event + */ + public static function user_enrolment_changed(\core\event\base $event) { + $cache = \cache::make('block_profile360', 'userdata'); + $cachekey = 'block_profile360_user_' . $event->relateduserid; + $cache->delete($cachekey); + } + + /** + * Clear user cache when course is completed. + * + * @param \core\event\course_completed $event + */ + public static function course_completed(\core\event\course_completed $event) { + $cache = \cache::make('block_profile360', 'userdata'); + $cachekey = 'block_profile360_user_' . $event->relateduserid; + $cache->delete($cachekey); + } +} \ No newline at end of file diff --git a/block_profile360_multilenguage/profile360/classes/privacy/provider.php b/block_profile360_multilenguage/profile360/classes/privacy/provider.php new file mode 100644 index 0000000..4312a86 --- /dev/null +++ b/block_profile360_multilenguage/profile360/classes/privacy/provider.php @@ -0,0 +1,44 @@ +. + +/** + * Privacy Subsystem implementation for block_profile360 + * + * @package block_profile360 + * @copyright 2018 Zig Tan + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace block_profile360\privacy; + +/** + * Privacy Subsystem for block_profile360 implementing null_provider. + * + * @copyright 2018 Zig Tan + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class provider implements \core_privacy\local\metadata\null_provider { + + /** + * Get the language string identifier with the component's language + * file to explain why this plugin stores no data. + * + * @return string + */ + public static function get_reason() : string { + return 'privacy:metadata'; + } +} diff --git a/block_profile360_multilenguage/profile360/db/access.php b/block_profile360_multilenguage/profile360/db/access.php new file mode 100644 index 0000000..5432af0 --- /dev/null +++ b/block_profile360_multilenguage/profile360/db/access.php @@ -0,0 +1,48 @@ +. + +/** + * Plugin capabilities are defined here. + * + * @package block_profile360 + * @category access + * @copyright 2023 Espacio360 + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$capabilities = [ + + 'block/profile360:myaddinstance' => [ + 'captype' => 'write', + 'contextlevel' => CONTEXT_SYSTEM, + 'archetypes' => [ + 'user' => CAP_ALLOW, + ], + 'clonepermissionsfrom' => 'moodle/my:manageblocks', + ], + + 'block/profile360:addinstance' => [ + 'riskbitmask' => RISK_SPAM | RISK_XSS, + 'captype' => 'write', + 'contextlevel' => CONTEXT_BLOCK, + 'archetypes' => [ + 'manager' => CAP_ALLOW, + ], + 'clonepermissionsfrom' => 'moodle/site:manageblocks', + ], +]; diff --git a/block_profile360_multilenguage/profile360/db/caches.php b/block_profile360_multilenguage/profile360/db/caches.php new file mode 100644 index 0000000..6425ffc --- /dev/null +++ b/block_profile360_multilenguage/profile360/db/caches.php @@ -0,0 +1,34 @@ +. + +/** + * Cache definitions for block_profile360. + * + * @package block_profile360 + * @copyright 2023 Espacio360 + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$definitions = array( + 'userdata' => array( + 'mode' => cache_store::MODE_APPLICATION, + 'simplekeys' => true, + 'simpledata' => false, + 'ttl' => 300, // 5 minutes + ), +); \ No newline at end of file diff --git a/block_profile360_multilenguage/profile360/db/events.php b/block_profile360_multilenguage/profile360/db/events.php new file mode 100644 index 0000000..594b337 --- /dev/null +++ b/block_profile360_multilenguage/profile360/db/events.php @@ -0,0 +1,44 @@ +. + +/** + * Event observers for block_profile360. + * + * @package block_profile360 + * @copyright 2023 Espacio360 + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$observers = array( + array( + 'eventname' => '\core\event\user_updated', + 'callback' => '\block_profile360\observer::user_updated', + ), + array( + 'eventname' => '\core\event\user_enrolment_created', + 'callback' => '\block_profile360\observer::user_enrolment_changed', + ), + array( + 'eventname' => '\core\event\user_enrolment_deleted', + 'callback' => '\block_profile360\observer::user_enrolment_changed', + ), + array( + 'eventname' => '\core\event\course_completed', + 'callback' => '\block_profile360\observer::course_completed', + ), +); \ No newline at end of file diff --git a/block_profile360_multilenguage/profile360/db/services.php b/block_profile360_multilenguage/profile360/db/services.php new file mode 100644 index 0000000..e9e22b5 --- /dev/null +++ b/block_profile360_multilenguage/profile360/db/services.php @@ -0,0 +1,14 @@ + [ + 'classname' => 'block_profile360_external_get_courses_data', + 'methodname' => 'get_courses_data', + 'classpath' => 'blocks/profile360/classes/external/get_courses_data.php', + 'description' => 'Get courses data for profile360 block', + 'type' => 'read', + 'ajax' => true, + 'loginrequired' => true, + ], +]; \ No newline at end of file diff --git a/block_profile360_multilenguage/profile360/lang/en/block_profile360.php b/block_profile360_multilenguage/profile360/lang/en/block_profile360.php new file mode 100644 index 0000000..1bfc374 --- /dev/null +++ b/block_profile360_multilenguage/profile360/lang/en/block_profile360.php @@ -0,0 +1,46 @@ +. + +/** + * Plugin strings are defined here. + * + * @package block_profile360 + * @category string + * @copyright 2023 Espacio360 + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$string['pluginname'] = 'User Profile 360'; +$string['customfields'] = 'Custom fields'; +$string['customfields_desc'] = 'Visible custom fields that can be shown in the profile'; +$string['show'] = 'show in profile'; +$string['totalcourses'] = 'Total courses'; +$string['completedcourses'] = 'Completed courses'; +$string['inprogresscourses'] = 'Courses in progress'; +$string['certifications'] = 'Certifications'; +$string['certifiedcourses'] = 'Certified courses'; +$string['coursesexpiring'] = 'Courses expiring'; +$string['coursesexpiringmessage'] = 'Don\'t wait until the last minute! The following courses are about to expire.'; +$string['nocompletedcourses'] = 'No completed courses.'; +$string['noinprogresscourses'] = 'No courses in progress.'; +$string['nocertifiedcourses'] = 'No certified courses.'; +$string['close'] = 'Close'; +$string['bannerimage'] = 'Banner background image'; +$string['bannerimage_desc'] = 'Upload an image to use as the banner background. If no image is uploaded, a default gradient will be used.'; + + diff --git a/block_profile360_multilenguage/profile360/lang/es_mx/block_profile360.php b/block_profile360_multilenguage/profile360/lang/es_mx/block_profile360.php new file mode 100644 index 0000000..bf4fbd3 --- /dev/null +++ b/block_profile360_multilenguage/profile360/lang/es_mx/block_profile360.php @@ -0,0 +1,44 @@ +. + +/** + * Plugin strings are defined here. + * + * @package block_profile360 + * @category string + * @copyright 2023 Espacio360 + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$string['pluginname'] = 'Perfil de usuario 360'; +$string['customfields'] = 'Campos personalizados'; +$string['customfields_desc'] = 'Campos personalizados visibles que se pueden mostrar en el perfil'; +$string['show'] = 'mostrar en el perfil'; +$string['totalcourses'] = 'Total de cursos'; +$string['completedcourses'] = 'Cursos completados'; +$string['inprogresscourses'] = 'Cursos en progreso'; +$string['certifications'] = 'Certificaciones'; +$string['certifiedcourses'] = 'Cursos certificados'; +$string['coursesexpiring'] = 'Cursos por expirar'; +$string['coursesexpiringmessage'] = '¡No esperes hasta el último minuto! Los siguientes cursos están a punto de expirar.'; +$string['nocompletedcourses'] = 'No hay cursos completados.'; +$string['noinprogresscourses'] = 'No hay cursos en progreso.'; +$string['nocertifiedcourses'] = 'No hay cursos certificados.'; +$string['close'] = 'Cerrar'; +$string['bannerimage'] = 'Imagen de fondo del banner'; +$string['bannerimage_desc'] = 'Sube una imagen para usar como fondo del banner. Si no se sube ninguna imagen, se usará un degradado por defecto.'; \ No newline at end of file diff --git a/block_profile360_multilenguage/profile360/lang/fil/block_profile360.php b/block_profile360_multilenguage/profile360/lang/fil/block_profile360.php new file mode 100644 index 0000000..e13545f --- /dev/null +++ b/block_profile360_multilenguage/profile360/lang/fil/block_profile360.php @@ -0,0 +1,44 @@ +. + +/** + * Plugin strings are defined here. + * + * @package block_profile360 + * @category string + * @copyright 2023 Espacio360 + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$string['pluginname'] = 'Profile ng User 360'; +$string['customfields'] = 'Mga customized na field'; +$string['customfields_desc'] = 'Mga nakikitang customized na field na maaaring ipakita sa profile'; +$string['show'] = 'ipakita sa profile'; +$string['totalcourses'] = 'Kabuuang mga kurso'; +$string['completedcourses'] = 'Mga natapos na kurso'; +$string['inprogresscourses'] = 'Mga kasalukuyang kurso'; +$string['certifications'] = 'Mga sertipikasyon'; +$string['certifiedcourses'] = 'Mga sertipikadong kurso'; +$string['coursesexpiring'] = 'Mga kurso na malapit matapos'; +$string['coursesexpiringmessage'] = 'Huwag mag-antay hanggang sa huling minuto! Ang mga sumusunod na kurso ay malapit nang matapos.'; +$string['nocompletedcourses'] = 'Walang natapos na kurso.'; +$string['noinprogresscourses'] = 'Walang kasalukuyang kurso.'; +$string['nocertifiedcourses'] = 'Walang sertipikadong kurso.'; +$string['close'] = 'Isara'; +$string['bannerimage'] = 'Background image ng banner'; +$string['bannerimage_desc'] = 'Mag-upload ng isang larawan na gagamitin bilang background ng banner. Kung walang na-upload na larawan, default gradient ang gagamitin.'; \ No newline at end of file diff --git a/block_profile360_multilenguage/profile360/lang/id/block_profile360.php b/block_profile360_multilenguage/profile360/lang/id/block_profile360.php new file mode 100644 index 0000000..46b1f1a --- /dev/null +++ b/block_profile360_multilenguage/profile360/lang/id/block_profile360.php @@ -0,0 +1,44 @@ +. + +/** + * Plugin strings are defined here. + * + * @package block_profile360 + * @category string + * @copyright 2023 Espacio360 + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$string['pluginname'] = 'Profil Pengguna 360'; +$string['customfields'] = 'Bidang kustom'; +$string['customfields_desc'] = 'Bidang kustom yang terlihat yang dapat ditampilkan dalam profil'; +$string['show'] = 'tampilkan di profil'; +$string['totalcourses'] = 'Total kursus'; +$string['completedcourses'] = 'Kursus selesai'; +$string['inprogresscourses'] = 'Kursus sedang berlangsung'; +$string['certifications'] = 'Sertifikasi'; +$string['certifiedcourses'] = 'Kursus bersertifikat'; +$string['coursesexpiring'] = 'Kursus akan berakhir'; +$string['coursesexpiringmessage'] = 'Jangan menunggu hingga menit terakhir! Kursus berikut akan segera berakhir.'; +$string['nocompletedcourses'] = 'Tidak ada kursus yang selesai.'; +$string['noinprogresscourses'] = 'Tidak ada kursus sedang berlangsung.'; +$string['nocertifiedcourses'] = 'Tidak ada kursus bersertifikat.'; +$string['close'] = 'Tutup'; +$string['bannerimage'] = 'Gambar latar banner'; +$string['bannerimage_desc'] = 'Unggah gambar untuk digunakan sebagai latar banner. Jika tidak ada gambar yang diunggah, gradien default akan digunakan.'; \ No newline at end of file diff --git a/block_profile360_multilenguage/profile360/lang/vi/block_profile360.php b/block_profile360_multilenguage/profile360/lang/vi/block_profile360.php new file mode 100644 index 0000000..a334b10 --- /dev/null +++ b/block_profile360_multilenguage/profile360/lang/vi/block_profile360.php @@ -0,0 +1,44 @@ +. + +/** + * Plugin strings are defined here. + * + * @package block_profile360 + * @category string + * @copyright 2023 Espacio360 + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$string['pluginname'] = 'Hồ sơ người dùng 360'; +$string['customfields'] = 'Trường tùy chỉnh'; +$string['customfields_desc'] = 'Các trường tùy chỉnh hiển thị có thể được hiển thị trong hồ sơ'; +$string['show'] = 'hiển thị trong hồ sơ'; +$string['totalcourses'] = 'Tổng số khóa học'; +$string['completedcourses'] = 'Khóa học đã hoàn thành'; +$string['inprogresscourses'] = 'Khóa học đang thực hiện'; +$string['certifications'] = 'Chứng nhận'; +$string['certifiedcourses'] = 'Khóa học đã chứng nhận'; +$string['coursesexpiring'] = 'Khóa học sắp hết hạn'; +$string['coursesexpiringmessage'] = 'Đừng chờ đến phút cuối! Các khóa học sau sắp hết hạn.'; +$string['nocompletedcourses'] = 'Không có khóa học đã hoàn thành.'; +$string['noinprogresscourses'] = 'Không có khóa học đang thực hiện.'; +$string['nocertifiedcourses'] = 'Không có khóa học đã chứng nhận.'; +$string['close'] = 'Đóng'; +$string['bannerimage'] = 'Hình ảnh nền banner'; +$string['bannerimage_desc'] = 'Tải lên một hình ảnh để sử dụng làm nền banner. Nếu không tải lên hình ảnh nào, sẽ sử dụng gradient mặc định.'; \ No newline at end of file diff --git a/block_profile360_multilenguage/profile360/lib.php b/block_profile360_multilenguage/profile360/lib.php new file mode 100644 index 0000000..719cd6b --- /dev/null +++ b/block_profile360_multilenguage/profile360/lib.php @@ -0,0 +1,69 @@ +. + +/** + * Plugin lib functions. + * + * @package block_profile360 + * @copyright 2023 Espacio360 + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +/** + * Serves the files from the block_profile360 file areas. + * + * @param stdClass $course the course object + * @param stdClass $cm the course module object + * @param stdClass $context the context + * @param string $filearea the name of the file area + * @param array $args extra arguments (itemid, path) + * @param bool $forcedownload whether or not force download + * @param array $options additional options affecting the file serving + * @return bool false if the file not found, just send the file otherwise and do not return anything + */ +function block_profile360_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options = array()) { + // Check context level + if ($context->contextlevel != CONTEXT_SYSTEM) { + return false; + } + + // Check if user is logged in + require_login(); + + // Check filearea + if ($filearea !== 'bannerimage') { + return false; + } + + // Get file details + $itemid = (int)array_shift($args); + $filename = array_pop($args); + $filepath = $args ? '/'.implode('/', $args).'/' : '/'; + + // Get file from storage + $fs = get_file_storage(); + $file = $fs->get_file($context->id, 'block_profile360', $filearea, $itemid, $filepath, $filename); + + if (!$file || $file->is_directory()) { + return false; + } + + // Send the file + send_stored_file($file, 86400, 0, $forcedownload, $options); + return true; +} \ No newline at end of file diff --git a/block_profile360_multilenguage/profile360/pix/01.png b/block_profile360_multilenguage/profile360/pix/01.png new file mode 100644 index 0000000000000000000000000000000000000000..279b9a4a1b86bad47fa9f3e52ef85f5a795349a9 GIT binary patch literal 84591 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 { + global $DB; + + // Consulta para obtener campos personalizados visibles + $sql = "SELECT shortname, name FROM {user_info_field} WHERE visible > :visible ORDER BY sortorder ASC"; + + // Ejecutar la consulta + $visibleCustomFields = $DB->get_records_sql_menu($sql, ['visible' => 0]); + + $settings->add(new admin_setting_heading('block_profile360_settings', 'Configuración', '')); + + // Banner background image setting + $settings->add( + new admin_setting_configstoredfile( + 'block_profile360/bannerimage', + get_string('bannerimage', 'block_profile360'), + get_string('bannerimage_desc', 'block_profile360'), + 'bannerimage', + 0, + array('maxfiles' => 1, 'accepted_types' => array('.jpg', '.jpeg', '.png', '.gif')) + ) + ); + + // Agregar los campos personalizados visibles como opciones + foreach ($visibleCustomFields as $shortname => $fieldName) { + $settings->add( + new admin_setting_configcheckbox( + 'block_profile360/customfield_' . $shortname, + $fieldName, + 'Mostar en el bloque', + true + ) + ); + } +} diff --git a/block_profile360_multilenguage/profile360/styles.css b/block_profile360_multilenguage/profile360/styles.css new file mode 100644 index 0000000..71178d8 --- /dev/null +++ b/block_profile360_multilenguage/profile360/styles.css @@ -0,0 +1,182 @@ +/* ===== PROFILE 360 (EQUAL HEIGHT CARDS) ===== */ + +.block_profile360 { + border: none; + box-shadow: rgba(0, 0, 0, 0.1) 0 4px 12px; +} + +.block_profile360 h5.card-title { display: none; } +.block_profile360 .card-body { padding: 0; } + +.block_profile360 .card-header.miniBanner { + min-height: 100px; + border-top-left-radius: 5px; + border-top-right-radius: 5px; + background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); + background-size: cover; + background-position: center bottom; +} +.block_profile360 .card-header.miniBanner[style*="background-image"] { background: none; } + +.block_profile360 .card { + background-color: #fff; + border: none; +} + +.block_profile360 .top-container { width: 100%; } + +.block_profile360 .profile-image { + border-radius: 10px; + background-color: #fff; + padding: 5px; + box-shadow: rgba(0, 0, 0, 0.05) 0 1px 2px 0; +} + +.block_profile360 .profile-image-container { + margin-top: -80px; + padding-left: 25px; +} + +/* ===== STATS CARDS ===== */ + +/* Caja base (div o ) */ +.block_profile360 .stats-card, +.block_profile360 .stadisticsUserCourse { + position: relative; + display: flex; /* ← centra vertical/horizontal */ + flex-direction: column; + justify-content: center; + align-items: center; + gap: .35rem; + text-align: center; + text-decoration: none !important; + color: #222; + background-color: #f2e7e8; + border-radius: 14px; + padding: 1rem; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); + transition: transform .2s ease, box-shadow .2s ease; + border: 1px solid rgba(255, 255, 255, 0.8); + overflow: hidden; + min-height: 150px; /* ← alto uniforme (mobile) */ +} +@media (min-width: 768px) { + .block_profile360 .stats-card, + .block_profile360 .stadisticsUserCourse { + min-height: 170px; /* ← alto uniforme (md+) */ + } +} +@media (min-width: 1200px) { + .block_profile360 .stats-card, + .block_profile360 .stadisticsUserCourse { + min-height: 180px; /* ← alto uniforme (lg) */ + } +} + +/* Icono circular */ +.block_profile360 .round-div.stats-icon { + width: 54px; + height: 54px; + border-radius: 50%; + background-color: rgba(255,255,255,0.95); + display: flex; + justify-content: center; + align-items: center; + box-shadow: 0 2px 4px rgba(0,0,0,.08); +} +.block_profile360 .round-div.stats-icon .icon-profile { + font-size: 20px; + line-height: 1; +} + +/* Número y etiqueta */ +.block_profile360 .stats-value { + margin: .2rem 0 0; + line-height: 1.1; + font-weight: 800; + background: none !important; + box-shadow: none !important; + border: 0 !important; +} +.block_profile360 .stats-value::before, +.block_profile360 .stats-value::after { content: none !important; } + +.block_profile360 .stats-label { + display: block; + margin-top: .15rem; + opacity: .95; +} + +/* Paletas */ +.block_profile360 .totalCourses { background: linear-gradient(135deg, rgba(10, 74, 153, .12), rgba(10, 74, 153, .22)); border-left: 4px solid #0a4a99; } +.block_profile360 .completedCourses { background: linear-gradient(135deg, rgba(72, 153, 10, .12), rgba(72, 153, 10, .22)); border-left: 4px solid #48990a; } +.block_profile360 .inProgressCourses { background: linear-gradient(135deg, rgba(238, 202, 6, .12), rgba(238, 202, 6, .22)); border-left: 4px solid #d4a806; } +.block_profile360 .certificatedCourses { background: linear-gradient(135deg, rgba(120, 14, 23, .12), rgba(120, 14, 23, .22)); border-left: 4px solid #780E17; } + +.block_profile360 .totalCourses .icon-profile, .block_profile360 .totalCourses .stats-value { color: #0a4a99; } +.block_profile360 .completedCourses .icon-profile, .block_profile360 .completedCourses .stats-value { color: #48990a; } +.block_profile360 .inProgressCourses .icon-profile, .block_profile360 .inProgressCourses .stats-value { color: #e0b000; } +.block_profile360 .certificatedCourses .icon-profile, .block_profile360 .certificatedCourses .stats-value { color: #780E17; } + +/* Interacciones */ +.block_profile360 .no-click { cursor: default; } +.block_profile360 .no-click:hover { transform: none; box-shadow: 0 2px 8px rgba(0,0,0,.08); } +.block_profile360 .completedCourses, +.block_profile360 .inProgressCourses, +.block_profile360 .certificatedCourses { cursor: pointer; } +.block_profile360 .completedCourses:hover, +.block_profile360 .inProgressCourses:hover, +.block_profile360 .certificatedCourses:hover { + transform: translateY(-2px); + box-shadow: 0 4px 16px rgba(0,0,0,.15); +} + +/* Datos personalizados */ +.block_profile360 .custom-profile-data { font-size: .9rem; margin: 0; } + +/* Modales centrados */ +.block_profile360 .modal-dialog { + min-height: calc(100vh - 60px); + display: flex; + flex-direction: column; + justify-content: center; + overflow: auto; +} +@media (max-width: 768px) { + .block_profile360 .modal-dialog { min-height: calc(100vh - 20px); } +} +.block_profile360 .modal.show { + background-color: rgba(0,0,0,.5); + font-size: 16px; +} + +/* Email */ +.block_profile360 .mail { font-size: .9rem !important; } + +/* ===== DARK MODE ===== */ +html.dark-mode .block_profile360 .card { background-color: #2d3748; color: #e2e8f0; } +html.dark-mode .block_profile360 .stats-card { border-color: rgba(255,255,255,.1); box-shadow: 0 2px 8px rgba(0,0,0,.3); color: #fff; } +html.dark-mode .block_profile360 .totalCourses { background: linear-gradient(135deg, rgba(10, 74, 153, .28), rgba(10, 74, 153, .38)); border-left-color: #4299e1; } +html.dark-mode .block_profile360 .completedCourses { background: linear-gradient(135deg, rgba(72, 153, 10, .28), rgba(72, 153, 10, .38)); border-left-color: #48bb78; } +html.dark-mode .block_profile360 .inProgressCourses{ background: linear-gradient(135deg, rgba(238, 202, 6, .28), rgba(238, 202, 6, .38)); border-left-color: #fbbf24; } +html.dark-mode .block_profile360 .certificatedCourses { background: linear-gradient(135deg, rgba(120, 14, 23, .28), rgba(120, 14, 23, .38)); border-left-color: #f87171; } +html.dark-mode .block_profile360 .totalCourses .stats-value { color: #4299e1; } +html.dark-mode .block_profile360 .completedCourses .stats-value { color: #48bb78; } +html.dark-mode .block_profile360 .inProgressCourses .stats-value { color: #fbbf24; } +html.dark-mode .block_profile360 .certificatedCourses .stats-value { color: #f87171; } +html.dark-mode .block_profile360 .round-div.stats-icon { background-color: rgba(255,255,255,.15); } +html.dark-mode .block_profile360 .modal-content { background-color: #2d3748; color: #e2e8f0; } +html.dark-mode .block_profile360 .modal-header { border-bottom-color: #4a5568; } +html.dark-mode .block_profile360 .modal-footer { border-top-color: #4a5568; } +html.dark-mode .block_profile360 .custom-profile-data { color: #cbd5e0; } + +/* ===== RESETS contra adornos del tema ===== */ +.block_profile360 .h1, .block_profile360 .h2, .block_profile360 .h3, +.block_profile360 h1, .block_profile360 h2, .block_profile360 h3 { + background: none !important; box-shadow: none !important; +} +.block_profile360 .h1::before, .block_profile360 .h1::after, +.block_profile360 .h2::before, .block_profile360 .h2::after, +.block_profile360 h1::before, .block_profile360 h1::after, +.block_profile360 h2::before, .block_profile360 h2::after, +.block_profile360 h3::before, .block_profile360 h3::after { content: none !important; } diff --git a/block_profile360_multilenguage/profile360/styles/profile360.css b/block_profile360_multilenguage/profile360/styles/profile360.css new file mode 100644 index 0000000..94fae0a --- /dev/null +++ b/block_profile360_multilenguage/profile360/styles/profile360.css @@ -0,0 +1,186 @@ +.block_profile360 { + border: none; + box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px; +} + +.block_profile360 h5.card-title { + display: none; +} + +.block_profile360 .card-body { + padding: 0; +} + +.block_profile360 .card-header.miniBanner { + min-height: 100px; + border-top-left-radius: 5px; + border-top-right-radius: 5px; + background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); + background-size: cover; + background-position: center bottom; +} + +.block_profile360 .card-header.miniBanner[style*="background-image"] { + background: none; +} + +.block_profile360 .card { + background-color: #fff; + border: none; +} + +.block_profile360 .top-container { + width: 100%; +} + +.block_profile360 .profile-image { + border-radius: 10px; + background-color: #fff; + padding: 5px; + box-shadow: rgba(0, 0, 0, 0.05) 0px 1px 2px 0px; +} + +.block_profile360 .profile-image-container { + margin-top: -80px; + padding-left: 25px; +} + +.block_profile360 .middle-container { + background-color: #eee; + border-radius: 12px; +} + +.block_profile360 .round-div { + border-radius: 50%; + width: 35px; + height: 35px; + background-color: #fff; + display: flex; + justify-content: center; + align-items: center; + text-align: center; +} + +.block_profile360 .stadisticsUserCourse { + color: #222; + background-color: #f2e7e8; + text-decoration: none; + border-radius: 10px; + padding: 1rem; +} + +.block_profile360 .stadisticsUserCourse.totalCourses { + background-color: rgba(10, 74, 153, 0.3); + color: #0a4a99; +} + +.block_profile360 .stadisticsUserCourse.totalCourses .icon-profile, .block_profile360 .stadisticsUserCourse.totalCourses .h2 { + color: #0a4a99; +} + +.block_profile360 .stadisticsUserCourse.completedCourses { + background-color: rgba(72, 153, 10, 0.3); + color: #48990a; +} + +.block_profile360 .stadisticsUserCourse.completedCourses .icon-profile, .block_profile360 .stadisticsUserCourse.completedCourses .h2 { + color: #48990a; +} + +.block_profile360 .stadisticsUserCourse.inProgressCourses { + background-color: rgba(238, 202, 6, 0.3); + color: #eeca06; +} + +.block_profile360 .stadisticsUserCourse.inProgressCourses .icon-profile, .block_profile360 .stadisticsUserCourse.inProgressCourses .h2 { + color: #eeca06; +} + +.block_profile360 .stadisticsUserCourse.certificatedCourses { + /* background-color: rgba(10,74,153,.3); */ + color: #780E17; +} + +.block_profile360 .stadisticsUserCourse.certificatedCourses .icon-profile, .block_profile360 .stadisticsUserCourse.certificatedCourses .h2 { + color: #780E17; +} + +.block_profile360 .inProgressCourses, .block_profile360 .certificatedCourses { + transition: all 0.5s ease; + cursor: pointer; +} + +.block_profile360 .inProgressCourses:hover, .block_profile360 .certificatedCourses:hover { + transform: scale(0.9); +} + +.block_profile360 .custom-profile-data { + font-size: small; + margin: 0; +} + +.block_profile360 .modal-dialog { + min-height: calc(100vh - 60px); + display: flex; + flex-direction: column; + justify-content: center; + overflow: auto; +} + +@media (max-width: 768px) { + .block_profile360 .modal-dialog { + min-height: calc(100vh - 20px); + } +} + +.block_profile360 .modal.show { + background-color: rgba(0, 0, 0, 0.5); + font-size: 16px; +} + +/* Dark mode support */ +html.dark-mode .block_profile360 .card { + background-color: #2d3748; + color: #e2e8f0; +} + +html.dark-mode .block_profile360 .stadisticsUserCourse { + color: #e2e8f0; +} + +html.dark-mode .block_profile360 .stadisticsUserCourse.totalCourses { + background-color: rgba(10, 74, 153, 0.4); + color: #63b3ed; +} + +html.dark-mode .block_profile360 .stadisticsUserCourse.completedCourses { + background-color: rgba(72, 153, 10, 0.4); + color: #68d391; +} + +html.dark-mode .block_profile360 .stadisticsUserCourse.inProgressCourses { + background-color: rgba(238, 202, 6, 0.4); + color: #f6e05e; +} + +html.dark-mode .block_profile360 .stadisticsUserCourse.certificatedCourses { + background-color: rgba(120, 14, 23, 0.4); + color: #fc8181; +} + +html.dark-mode .block_profile360 .modal-content { + background-color: #2d3748; + color: #e2e8f0; +} + +html.dark-mode .block_profile360 .modal-header { + border-bottom-color: #4a5568; +} + +html.dark-mode .block_profile360 .modal-footer { + border-top-color: #4a5568; +} + +html.dark-mode .block_profile360 .custom-profile-data { + color: #cbd5e0; +} diff --git a/block_profile360_multilenguage/profile360/styles/profile360.min.css b/block_profile360_multilenguage/profile360/styles/profile360.min.css new file mode 100644 index 0000000..2db709e --- /dev/null +++ b/block_profile360_multilenguage/profile360/styles/profile360.min.css @@ -0,0 +1 @@ +.block_profile360{border:none;box-shadow:rgba(0,0,0,0.1) 0px 4px 12px}.block_profile360 h5.card-title{display:none}.block_profile360 .card-body{padding:0}.block_profile360 .card-header.miniBanner{min-height:100px;border-top-left-radius:5px;border-top-right-radius:5px;background-image:url(../pix/01.png);background-size:cover;background-position:center bottom}.block_profile360 .card{background-color:#fff;border:none}.block_profile360 .top-container{width:100%}.block_profile360 .profile-image{border-radius:10px;background-color:#fff;padding:5px;box-shadow:rgba(0,0,0,0.05) 0px 1px 2px 0px}.block_profile360 .profile-image-container{margin-top:-80px;padding-left:25px}.block_profile360 .middle-container{background-color:#eee;border-radius:12px}.block_profile360 .round-div{border-radius:50%;width:35px;height:35px;background-color:#fff;display:flex;justify-content:center;align-items:center;text-align:center}.block_profile360 .stadisticsUserCourse{color:#222;background-color:#f2e7e8;text-decoration:none;border-radius:10px;padding:1rem}.block_profile360 .stadisticsUserCourse.totalCourses{background-color:rgba(10,74,153,0.3);color:#0a4a99}.block_profile360 .stadisticsUserCourse.totalCourses .icon-profile,.block_profile360 .stadisticsUserCourse.totalCourses .h2{color:#0a4a99}.block_profile360 .stadisticsUserCourse.completedCourses{background-color:rgba(72,153,10,0.3);color:#48990a}.block_profile360 .stadisticsUserCourse.completedCourses .icon-profile,.block_profile360 .stadisticsUserCourse.completedCourses .h2{color:#48990a}.block_profile360 .stadisticsUserCourse.inProgressCourses{background-color:rgba(238,202,6,0.3);color:#eeca06}.block_profile360 .stadisticsUserCourse.inProgressCourses .icon-profile,.block_profile360 .stadisticsUserCourse.inProgressCourses .h2{color:#eeca06}.block_profile360 .stadisticsUserCourse.certificatedCourses{color:#780E17}.block_profile360 .stadisticsUserCourse.certificatedCourses .icon-profile,.block_profile360 .stadisticsUserCourse.certificatedCourses .h2{color:#780E17}.block_profile360 .inProgressCourses,.block_profile360 .certificatedCourses{transition:all 0.5s ease;cursor:pointer}.block_profile360 .inProgressCourses:hover,.block_profile360 .certificatedCourses:hover{transform:scale(0.9)}.block_profile360 .custom-profile-data{font-size:small;margin:0}.block_profile360 .modal-dialog{min-height:calc(100vh - 60px);display:flex;flex-direction:column;justify-content:center;overflow:auto}@media (max-width: 768px){.block_profile360 .modal-dialog{min-height:calc(100vh - 20px)}}.block_profile360 .modal.show{background-color:rgba(0,0,0,0.5);font-size:16px} diff --git a/block_profile360_multilenguage/profile360/styles/profile360.scss b/block_profile360_multilenguage/profile360/styles/profile360.scss new file mode 100644 index 0000000..75dbdd9 --- /dev/null +++ b/block_profile360_multilenguage/profile360/styles/profile360.scss @@ -0,0 +1,129 @@ +.block_profile360 { + border: none; + box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px; +} + +.block_profile360 { + h5.card-title { + display: none; + } + + .card-body { + padding: 0; + } + + .card-header.miniBanner { + min-height: 100px; + border-top-left-radius: 5px; + border-top-right-radius: 5px; + background-image: url(../pix/01.png); + background-size: cover; + background-position: center bottom; + } + + .card { + background-color: #fff; + border: none; + } + + .top-container { + width: 100%; + } + + .profile-image { + border-radius: 10px; + background-color: #fff; + padding: 5px; + box-shadow: rgba(0, 0, 0, 0.05) 0px 1px 2px 0px; + } + + .profile-image-container { + margin-top: -80px; + padding-left: 25px; + + } + + .middle-container { + background-color: #eee; + border-radius: 12px; + + } + + .round-div { + border-radius: 50%; + width: 35px; + height: 35px; + background-color: #fff; + display: flex; + justify-content: center; + align-items: center; + text-align: center; + } + + .stadisticsUserCourse { + color: #222; + background-color: #f2e7e8; + text-decoration: none; + border-radius: 10px; + padding: 1rem; + } + + .stadisticsUserCourse.totalCourses { + background-color: rgba(10,74,153,.3); + color: #0a4a99; + } + .stadisticsUserCourse.totalCourses .icon-profile, .stadisticsUserCourse.totalCourses .h2{ + color: #0a4a99; + } + + .stadisticsUserCourse.completedCourses { + background-color: rgba(72,153,10,.3); + color: #48990a; + } + .stadisticsUserCourse.completedCourses .icon-profile, .stadisticsUserCourse.completedCourses .h2{ + color: #48990a; + } + + .stadisticsUserCourse.inProgressCourses { + background-color: rgba(238,202,6,.3); + color: #eeca06; + } + .stadisticsUserCourse.inProgressCourses .icon-profile, .stadisticsUserCourse.inProgressCourses .h2{ + color: #eeca06; + } + + .stadisticsUserCourse.certificatedCourses { + /* background-color: rgba(10,74,153,.3); */ + color: #780E17; + } + .stadisticsUserCourse.certificatedCourses .icon-profile, .stadisticsUserCourse.certificatedCourses .h2{ + color:#780E17; + } + .inProgressCourses, .certificatedCourses{ + transition: all 0.5s ease; + cursor: pointer; + } + .inProgressCourses:hover, .certificatedCourses:hover{ + transform: scale(.9); + } + .custom-profile-data{ + font-size: small; + margin: 0; + } + + .modal-dialog { + min-height: calc(100vh - 60px); + display: flex; + flex-direction: column; + justify-content: center; + overflow: auto; + @media(max-width: 768px) { + min-height: calc(100vh - 20px); + } + } + + .modal.show{ + background-color: rgba(0,0,0,.5); + font-size: 16px; + } +} \ No newline at end of file diff --git a/block_profile360_multilenguage/profile360/templates/profile360.mustache b/block_profile360_multilenguage/profile360/templates/profile360.mustache new file mode 100644 index 0000000..65b7b91 --- /dev/null +++ b/block_profile360_multilenguage/profile360/templates/profile360.mustache @@ -0,0 +1,198 @@ + + + + + + + + + + + + + + + diff --git a/block_profile360_multilenguage/profile360/version.php b/block_profile360_multilenguage/profile360/version.php new file mode 100644 index 0000000..836f79e --- /dev/null +++ b/block_profile360_multilenguage/profile360/version.php @@ -0,0 +1,31 @@ +. + +/** + * Plugin version and other meta-data are defined here. + * + * @package block_profile360 + * @copyright 2023 Espacio360 + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$plugin->component = 'block_profile360'; +$plugin->release = '1.6.2'; +$plugin->version = 2025010127; +$plugin->requires = 2021051700; +$plugin->maturity = MATURITY_STABLE;