From fa8070f8be36e88ccfaad17d8d7177b1e46ffea2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luca=20B=C3=B6sch?= Date: Sat, 13 May 2023 13:55:40 +0200 Subject: [PATCH] Initial commit. --- LICENSE.md | 596 ++++++++++++ README.md | 44 + classes/event/backend_created.php | 30 + classes/event/backend_deleted.php | 30 + classes/event/backend_disabled.php | 30 + classes/event/backend_enabled.php | 30 + classes/event/backend_updated.php | 30 + classes/event/query_created.php | 30 + classes/event/query_deleted.php | 30 + classes/event/query_disabled.php | 30 + classes/event/query_enabled.php | 30 + classes/event/query_updated.php | 30 + classes/local/backend.php | 135 +++ classes/local/backend_category.php | 117 +++ classes/local/form/backend.php | 95 ++ classes/local/form/query.php | 89 ++ classes/local/query.php | 135 +++ classes/local/query_category.php | 117 +++ classes/local/table/backend_list.php | 178 ++++ classes/local/table/query_list.php | 178 ++++ classes/output/backend.php | 161 ++++ classes/output/backend_category.php | 162 ++++ classes/output/index_page.php | 149 +++ classes/output/query.php | 162 ++++ classes/output/query_category.php | 162 ++++ classes/output/renderer.php | 70 ++ classes/privacy/provider.php | 44 + classes/task/run_reports.php | 98 ++ classes/utils.php | 102 ++ db/access.php | 51 + db/install.php | 28 + db/install.xml | 63 ++ db/uninstall.php | 32 + index.php | 71 ++ lang/en/report_datawarehouse.php | 37 + locallib.php | 895 ++++++++++++++++++ settings.php | 32 + templates/backend.mustache | 93 ++ templates/backend_category.mustache | 93 ++ templates/category_query.mustache | 67 ++ templates/expand_collapse_link.mustache | 38 + .../form-user-selector-suggestion.mustache | 49 + templates/form_report_information.mustache | 44 + templates/index_page.mustache | 38 + templates/query.mustache | 93 ++ templates/query_actions.mustache | 45 + templates/query_category.mustache | 93 ++ version.php | 31 + 48 files changed, 4987 insertions(+) create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 classes/event/backend_created.php create mode 100644 classes/event/backend_deleted.php create mode 100644 classes/event/backend_disabled.php create mode 100644 classes/event/backend_enabled.php create mode 100644 classes/event/backend_updated.php create mode 100644 classes/event/query_created.php create mode 100644 classes/event/query_deleted.php create mode 100644 classes/event/query_disabled.php create mode 100644 classes/event/query_enabled.php create mode 100644 classes/event/query_updated.php create mode 100644 classes/local/backend.php create mode 100644 classes/local/backend_category.php create mode 100644 classes/local/form/backend.php create mode 100644 classes/local/form/query.php create mode 100644 classes/local/query.php create mode 100644 classes/local/query_category.php create mode 100644 classes/local/table/backend_list.php create mode 100644 classes/local/table/query_list.php create mode 100644 classes/output/backend.php create mode 100644 classes/output/backend_category.php create mode 100644 classes/output/index_page.php create mode 100644 classes/output/query.php create mode 100644 classes/output/query_category.php create mode 100644 classes/output/renderer.php create mode 100644 classes/privacy/provider.php create mode 100644 classes/task/run_reports.php create mode 100644 classes/utils.php create mode 100644 db/access.php create mode 100644 db/install.php create mode 100644 db/install.xml create mode 100644 db/uninstall.php create mode 100644 index.php create mode 100644 lang/en/report_datawarehouse.php create mode 100644 locallib.php create mode 100644 settings.php create mode 100644 templates/backend.mustache create mode 100644 templates/backend_category.mustache create mode 100644 templates/category_query.mustache create mode 100644 templates/expand_collapse_link.mustache create mode 100644 templates/form-user-selector-suggestion.mustache create mode 100644 templates/form_report_information.mustache create mode 100644 templates/index_page.mustache create mode 100644 templates/query.mustache create mode 100644 templates/query_actions.mustache create mode 100644 templates/query_category.mustache create mode 100644 version.php diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..16d89e0 --- /dev/null +++ b/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/README.md b/README.md new file mode 100644 index 0000000..c0ad2c4 --- /dev/null +++ b/README.md @@ -0,0 +1,44 @@ +# Data warehouse reports # + +TODO Describe the plugin shortly here. + +TODO Provide more detailed description here. + +## Installing via uploaded ZIP file ## + +1. Log in to your Moodle site as an admin and go to _Site administration > + Plugins > Install plugins_. +2. Upload the ZIP file with the plugin code. You should only be prompted to add + extra details if your plugin type is not automatically detected. +3. Check the plugin validation report and finish the installation. + +## Installing manually ## + +The plugin can be also installed by putting the contents of this directory to + + {your/moodle/dirroot}/report/datawarehouse + +Afterwards, log in to your Moodle site as an admin and go to _Site administration > +Notifications_ to complete the installation. + +Alternatively, you can run + + $ php admin/cli/upgrade.php + +to complete the installation from the command line. + +## License ## + +2023 Luca Bösch + +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 . diff --git a/classes/event/backend_created.php b/classes/event/backend_created.php new file mode 100644 index 0000000..83f429b --- /dev/null +++ b/classes/event/backend_created.php @@ -0,0 +1,30 @@ +. + +namespace report_datawarehouse\event; + +/** + * The backend_created event class. + * + * @package report_datawarehouse + * @query_category event + * @copyright 2023 Luca Bösch + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class backend_created extends base { + + // For more information about the Events API please visit {@link https://docs.moodle.org/dev/Events_API}. +} diff --git a/classes/event/backend_deleted.php b/classes/event/backend_deleted.php new file mode 100644 index 0000000..6c6a1ee --- /dev/null +++ b/classes/event/backend_deleted.php @@ -0,0 +1,30 @@ +. + +namespace report_datawarehouse\event; + +/** + * The backend_deleted event class. + * + * @package report_datawarehouse + * @query_category event + * @copyright 2023 Luca Bösch + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class backend_deleted extends base { + + // For more information about the Events API please visit {@link https://docs.moodle.org/dev/Events_API}. +} diff --git a/classes/event/backend_disabled.php b/classes/event/backend_disabled.php new file mode 100644 index 0000000..04a177f --- /dev/null +++ b/classes/event/backend_disabled.php @@ -0,0 +1,30 @@ +. + +namespace report_datawarehouse\event; + +/** + * The backend_disabled event class. + * + * @package report_datawarehouse + * @query_category event + * @copyright 2023 Luca Bösch + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class backend_disabled extends base { + + // For more information about the Events API please visit {@link https://docs.moodle.org/dev/Events_API}. +} diff --git a/classes/event/backend_enabled.php b/classes/event/backend_enabled.php new file mode 100644 index 0000000..505cabf --- /dev/null +++ b/classes/event/backend_enabled.php @@ -0,0 +1,30 @@ +. + +namespace report_datawarehouse\event; + +/** + * The backend_enabled event class. + * + * @package report_datawarehouse + * @query_category event + * @copyright 2023 Luca Bösch + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class backend_enabled extends base { + + // For more information about the Events API please visit {@link https://docs.moodle.org/dev/Events_API}. +} diff --git a/classes/event/backend_updated.php b/classes/event/backend_updated.php new file mode 100644 index 0000000..3772078 --- /dev/null +++ b/classes/event/backend_updated.php @@ -0,0 +1,30 @@ +. + +namespace report_datawarehouse\event; + +/** + * The backend_updated event class. + * + * @package report_datawarehouse + * @query_category event + * @copyright 2023 Luca Bösch + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class backend_updated extends base { + + // For more information about the Events API please visit {@link https://docs.moodle.org/dev/Events_API}. +} diff --git a/classes/event/query_created.php b/classes/event/query_created.php new file mode 100644 index 0000000..21f6176 --- /dev/null +++ b/classes/event/query_created.php @@ -0,0 +1,30 @@ +. + +namespace report_datawarehouse\event; + +/** + * The query_created event class. + * + * @package report_datawarehouse + * @query_category event + * @copyright 2023 Luca Bösch + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class query_created extends base { + + // For more information about the Events API please visit {@link https://docs.moodle.org/dev/Events_API}. +} diff --git a/classes/event/query_deleted.php b/classes/event/query_deleted.php new file mode 100644 index 0000000..2111d80 --- /dev/null +++ b/classes/event/query_deleted.php @@ -0,0 +1,30 @@ +. + +namespace report_datawarehouse\event; + +/** + * The query_deleted event class. + * + * @package report_datawarehouse + * @query_category event + * @copyright 2023 Luca Bösch + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class query_deleted extends base { + + // For more information about the Events API please visit {@link https://docs.moodle.org/dev/Events_API}. +} diff --git a/classes/event/query_disabled.php b/classes/event/query_disabled.php new file mode 100644 index 0000000..18b4095 --- /dev/null +++ b/classes/event/query_disabled.php @@ -0,0 +1,30 @@ +. + +namespace report_datawarehouse\event; + +/** + * The query_disabled event class. + * + * @package report_datawarehouse + * @query_category event + * @copyright 2023 Luca Bösch + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class query_disabled extends base { + + // For more information about the Events API please visit {@link https://docs.moodle.org/dev/Events_API}. +} diff --git a/classes/event/query_enabled.php b/classes/event/query_enabled.php new file mode 100644 index 0000000..9219f89 --- /dev/null +++ b/classes/event/query_enabled.php @@ -0,0 +1,30 @@ +. + +namespace report_datawarehouse\event; + +/** + * The query_enabled event class. + * + * @package report_datawarehouse + * @query_category event + * @copyright 2023 Luca Bösch + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class query_enabled extends base { + + // For more information about the Events API please visit {@link https://docs.moodle.org/dev/Events_API}. +} diff --git a/classes/event/query_updated.php b/classes/event/query_updated.php new file mode 100644 index 0000000..262b7e9 --- /dev/null +++ b/classes/event/query_updated.php @@ -0,0 +1,30 @@ +. + +namespace report_datawarehouse\event; + +/** + * The query_updated event class. + * + * @package report_datawarehouse + * @query_category event + * @copyright 2023 Luca Bösch + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class query_updated extends base { + + // For more information about the Events API please visit {@link https://docs.moodle.org/dev/Events_API}. +} diff --git a/classes/local/backend.php b/classes/local/backend.php new file mode 100644 index 0000000..8d08491 --- /dev/null +++ b/classes/local/backend.php @@ -0,0 +1,135 @@ +. + +namespace report_datawarehouse\local; + +use moodle_url; + +/** + * Backend class. + * + * @package report_datawarehouse + * @copyright 2023 Luca Bösch + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class backend { + /** @var \stdClass The query's record from database. */ + private $record; + + /** + * Create a new backend object. + * + * @param \stdClass $record The record from database. + */ + public function __construct(\stdClass $record) { + $this->record = $record; + } + + /** + * Get backend Id. + * + * @return int Query's Id. + */ + public function get_id(): int { + return $this->record->id; + } + + /** + * Get backend's display name. + * + * @return string Display name. + */ + public function get_displayname(): string { + return $this->record->displayname; + } + + /** + * Get url to view backend. + * + * @return moodle_url View url. + */ + public function get_url(): moodle_url { + return report_datawarehouse_url('view.php', ['id' => $this->record->id]); + } + + /** Get url to edit backend. + * + * @param moodle_url|null $returnurl Return url. + * @return moodle_url Edit url. + */ + public function get_edit_url(moodle_url $returnurl = null): moodle_url { + $param = ['id' => $this->record->id]; + if ($returnurl) { + $param['returnurl'] = $returnurl->out_as_local_url(false); + } + + return report_datawarehouse_url('edit.php', $param); + } + + /** + * Get url to delete backend. + * + * @param moodle_url|null $returnurl Return url. + * @return moodle_url Delete url. + */ + public function get_delete_url(moodle_url $returnurl = null): moodle_url { + $param = ['id' => $this->record->id]; + if ($returnurl) { + $param['returnurl'] = $returnurl->out_as_local_url(false); + } + + return report_datawarehouse_url('delete.php', $param); + } + + /** + * Get the time note. + * + * @return string Time note. + */ + public function get_time_note() { + return report_datawarehouse_time_note($this->record, 'span'); + } + + /** + * Get the text to display the capability. + * + * @return string Capability text. + */ + public function get_capability_string() { + $capabilities = report_datawarehouse_capability_options(); + return $capabilities[$this->record->capability]; + } + + /** + * Check if user can edit the backend. + * + * @param \context $context The context to check. + * @return bool true if the user has this capability. Otherwise false. + */ + public function can_edit(\context $context): bool { + return has_capability('report/datawarehouse:managebackends', $context); + } + + /** + * Check the capability to view the query. + * + * @param \context $context The context to check. + * @return bool Has capability to view or not? + */ + public function can_view(\context $context):bool { + return empty($report->capability) || has_capability($report->capability, $context); + } +} diff --git a/classes/local/backend_category.php b/classes/local/backend_category.php new file mode 100644 index 0000000..ed2f66f --- /dev/null +++ b/classes/local/backend_category.php @@ -0,0 +1,117 @@ +. + +namespace report_datawarehouse\local; + +use report_datawarehouse\utils; + +/** + * Backend category class. + * + * @package report_datawarehouse + * @copyright 2023 Luca Bösch + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class backend_category { + /** @var int Category ID. */ + private $id; + + /** @var string Category name. */ + private $name; + + /** @var array Pre-loaded backends data. */ + private $backendsdata; + + /** @var array Pre-loaded statistic data. */ + private $statistic; + + /** + * Create a new backend_category object. + * + * @param \stdClass $record The record from database. + */ + public function __construct(\stdClass $record) { + $this->id = $record->id; + $this->name = $record->name; + } + + /** + * Load backends of backend_category from records. + * + * @param array $backends Records to load. + */ + public function load_backends_data(array $backends): void { + $statistic = []; + $backendsdata = []; + foreach (report_datawarehouse_runable_options() as $type => $description) { + $fitleredbackends = utils::get_number_of_report_by_type($backends, $type); + $statistic[$type] = count($fitleredbackends); + if ($fitleredbackends) { + $backendsdata[] = [ + 'type' => $type, + 'backends' => $fitleredbackends + ]; + } + } + $this->backendsdata = $backendsdata; + $this->statistic = $statistic; + } + + /** + * Get backend_category ID. + * + * @return int Category ID. + */ + public function get_id(): int { + return $this->id; + } + + /** + * Get backend_category name. + * + * @return string Backend Category name. + */ + public function get_name(): string { + return $this->name; + } + + /** + * Get pre-loaded backends' data of this backend_category. + * + * @return array Backends' data. + */ + public function get_backends_data(): array { + return $this->backendsdata; + } + + /** + * Get pre-loaded statistic of this backend_category. + * + * @return array Statistic data. + */ + public function get_statistic(): array { + return $this->statistic; + } + + /** + * Get url to view the backend_category. + * + * @return \moodle_url Backend category's url. + */ + public function get_url(): \moodle_url { + return report_datawarehouse_url('backend_category.php', ['id' => $this->id]); + } +} diff --git a/classes/local/form/backend.php b/classes/local/form/backend.php new file mode 100644 index 0000000..46166fd --- /dev/null +++ b/classes/local/form/backend.php @@ -0,0 +1,95 @@ +. + +/** + * Form for manipulating the data warehouse backends. + * + * @package report_datawarehouse + * @copyright 2023 Luca Bösch + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace report_datawarehouse\local\form; + +/** + * Form for manipulating the data warehouse backends. + * + * @copyright 2023 Luca Bösch + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class backend extends \core\form\persistent { + + /** @var string Persistent class name. */ + protected static $persistentclass = 'report_datawarehouse\\backend'; + + /** + * Form definition. + */ + protected function definition() { + global $CFG; + + $mform = $this->_form; + + $mform->addElement('text', 'name', get_string('name', 'report_datawarehouse')); + $mform->addRule('name', get_string('required'), 'required', null, 'client'); + $mform->setType('name', PARAM_TEXT); + + $mform->addElement('textarea', 'description', get_string('description', 'report_datawarehouse')); + $mform->setType('description', PARAM_TEXT); + + $mform->addElement('text', 'url', get_string('url', 'report_datawarehouse')); + $mform->addRule('url', get_string('required'), 'required', null, 'client'); + $mform->setType('url', PARAM_TEXT); + + $mform->addElement('text', 'username', get_string('username', 'report_datawarehouse')); + $mform->setType('username', PARAM_TEXT); + + $mform->addElement('text', 'password', get_string('password', 'report_datawarehouse')); + $mform->setType('password', PARAM_TEXT); + + $mform->addElement('selectyesno', 'enabled', get_string('enabled', 'report_datawarehouse')); + $mform->setType('enabled', PARAM_INT); + + $mform->addElement('text', 'alloweduser', get_string('alloweduser', 'report_datawarehouse')); + $mform->setType('alloweduser', PARAM_TEXT); + + $this->add_action_buttons(); + + if (!empty($this->get_persistent()) && !$this->get_persistent()->can_delete()) { + $mform->hardFreezeAllVisibleExcept([]); + $mform->addElement('cancel'); + } + } + + /** + * Extra validation. + * + * @param \stdClass $data Data to validate. + * @param array $files Array of files. + * @param array $errors Currently reported errors. + * @return array of additional errors, or overridden errors. + */ + protected function extra_validation($data, $files, array &$errors) { + $newerrors = []; + + // Check name. + if (empty($data->name)) { + $newerrors['name'] = get_string('namerequired', 'report_datawarehouse'); + } + + return $newerrors; + } +} diff --git a/classes/local/form/query.php b/classes/local/form/query.php new file mode 100644 index 0000000..7c71ad8 --- /dev/null +++ b/classes/local/form/query.php @@ -0,0 +1,89 @@ +. + +/** + * Form for manipulating the query records. + * + * @package report_datawarehouse + * @copyright 2023 Luca Bösch + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace report_datawarehouse\local\form; + +/** + * Form for manipulating the query records. + * + * @copyright 2023 Luca Bösch + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class query extends \core\form\persistent { + + /** @var string Persistent class name. */ + protected static $persistentclass = 'report_datawarehouse\\query'; + + /** + * Form definition. + */ + protected function definition() { + global $CFG; + + $mform = $this->_form; + + $mform->addElement('text', 'name', get_string('name', 'report_datawarehouse')); + $mform->addRule('name', get_string('required'), 'required', null, 'client'); + $mform->setType('name', PARAM_TEXT); + + $mform->addElement('textarea', 'description', get_string('description', 'report_datawarehouse')); + $mform->setType('description', PARAM_TEXT); + + $mform->addElement('textarea', 'querysql', get_string('querysql', 'report_datawarehouse'), + ['rows' => '25', 'cols' => '80']); + $mform->addRule('querysql', get_string('required'), 'required'); + + $mform->addElement('selectyesno', 'enabled', get_string('enabled', 'report_datawarehouse')); + $mform->setType('enabled', PARAM_INT); + + $mform->addElement('static', 'note', get_string('note', 'report_datawarehouse'), + get_string('querynote', 'report_datawarehouse', $CFG->wwwroot)); + + $this->add_action_buttons(); + + if (!empty($this->get_persistent()) && !$this->get_persistent()->can_delete()) { + $mform->hardFreezeAllVisibleExcept([]); + $mform->addElement('cancel'); + } + } + + /** + * Extra validation. + * + * @param \stdClass $data Data to validate. + * @param array $files Array of files. + * @param array $errors Currently reported errors. + * @return array of additional errors, or overridden errors. + */ + protected function extra_validation($data, $files, array &$errors) { + $newerrors = []; + + // Check name. + if (empty($data->name)) { + $newerrors['name'] = get_string('namerequired', 'report_datawarehouse'); + } + + return $newerrors; + } +} diff --git a/classes/local/query.php b/classes/local/query.php new file mode 100644 index 0000000..09872ca --- /dev/null +++ b/classes/local/query.php @@ -0,0 +1,135 @@ +. + +namespace report_datawarehouse\local; + +use moodle_url; + +/** + * Query class. + * + * @package report_datawarehouse + * @copyright 2023 Luca Bösch + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class query { + /** @var \stdClass The query's record from database. */ + private $record; + + /** + * Create a new query object. + * + * @param \stdClass $record The record from database. + */ + public function __construct(\stdClass $record) { + $this->record = $record; + } + + /** + * Get query Id. + * + * @return int Query's Id. + */ + public function get_id(): int { + return $this->record->id; + } + + /** + * Get query's display name. + * + * @return string Display name. + */ + public function get_displayname(): string { + return $this->record->displayname; + } + + /** + * Get url to view query. + * + * @return moodle_url View url. + */ + public function get_url(): moodle_url { + return report_datawarehouse_url('view.php', ['id' => $this->record->id]); + } + + /** Get url to edit query. + * + * @param moodle_url|null $returnurl Return url. + * @return moodle_url Edit url. + */ + public function get_edit_url(moodle_url $returnurl = null): moodle_url { + $param = ['id' => $this->record->id]; + if ($returnurl) { + $param['returnurl'] = $returnurl->out_as_local_url(false); + } + + return report_datawarehouse_url('edit.php', $param); + } + + /** + * Get url to delete query. + * + * @param moodle_url|null $returnurl Return url. + * @return moodle_url Delete url. + */ + public function get_delete_url(moodle_url $returnurl = null): moodle_url { + $param = ['id' => $this->record->id]; + if ($returnurl) { + $param['returnurl'] = $returnurl->out_as_local_url(false); + } + + return report_datawarehouse_url('delete.php', $param); + } + + /** + * Get the time note. + * + * @return string Time note. + */ + public function get_time_note() { + return report_datawarehouse_time_note($this->record, 'span'); + } + + /** + * Get the text to display the capability. + * + * @return string Capability text. + */ + public function get_capability_string() { + $capabilities = report_datawarehouse_capability_options(); + return $capabilities[$this->record->capability]; + } + + /** + * Check if user can edit the query. + * + * @param \context $context The context to check. + * @return bool true if the user has this capability. Otherwise false. + */ + public function can_edit(\context $context): bool { + return has_capability('report/datawarehouse:managequeries', $context); + } + + /** + * Check the capability to view the query. + * + * @param \context $context The context to check. + * @return bool Has capability to view or not? + */ + public function can_view(\context $context):bool { + return empty($report->capability) || has_capability($report->capability, $context); + } +} diff --git a/classes/local/query_category.php b/classes/local/query_category.php new file mode 100644 index 0000000..e3406e3 --- /dev/null +++ b/classes/local/query_category.php @@ -0,0 +1,117 @@ +. + +namespace report_datawarehouse\local; + +use report_datawarehouse\utils; + +/** + * Query category class. + * + * @package report_datawarehouse + * @copyright 2023 Luca Bösch + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class query_category { + /** @var int Category ID. */ + private $id; + + /** @var string Category name. */ + private $name; + + /** @var array Pre-loaded queries data. */ + private $queriesdata; + + /** @var array Pre-loaded statistic data. */ + private $statistic; + + /** + * Create a new query_category object. + * + * @param \stdClass $record The record from database. + */ + public function __construct(\stdClass $record) { + $this->id = $record->id; + $this->name = $record->name; + } + + /** + * Load queries of query_category from records. + * + * @param array $queries Records to load. + */ + public function load_queries_data(array $queries): void { + $statistic = []; + $queriesdata = []; + foreach (report_datawarehouse_runable_options() as $type => $description) { + $filteredqueries = utils::get_number_of_report_by_type($queries, $type); + $statistic[$type] = count($filteredqueries); + if ($filteredqueries) { + $queriesdata[] = [ + 'type' => $type, + 'queries' => $filteredqueries + ]; + } + } + $this->queriesdata = $queriesdata; + $this->statistic = $statistic; + } + + /** + * Get query_category ID. + * + * @return int Category ID. + */ + public function get_id(): int { + return $this->id; + } + + /** + * Get query_category name. + * + * @return string Query Category name. + */ + public function get_name(): string { + return $this->name; + } + + /** + * Get pre-loaded queries' data of this query_category. + * + * @return array Queries' data. + */ + public function get_queries_data(): array { + return $this->queriesdata; + } + + /** + * Get pre-loaded statistic of this query_category. + * + * @return array Statistic data. + */ + public function get_statistic(): array { + return $this->statistic; + } + + /** + * Get url to view the query_category. + * + * @return \moodle_url Query category's url. + */ + public function get_url(): \moodle_url { + return report_datawarehouse_url('query_category.php', ['id' => $this->id]); + } +} diff --git a/classes/local/table/backend_list.php b/classes/local/table/backend_list.php new file mode 100644 index 0000000..9f05780 --- /dev/null +++ b/classes/local/table/backend_list.php @@ -0,0 +1,178 @@ +. + +/** + * Backends table. + * + * @package report_datawarehouse + * @copyright 2023 Luca Bösch + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace report_datawarehouse\local\table; + +use report_datawarehouse\helper; +use report_datawarehouse\backend; +use report_datawarehouse\backend_controller; + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->libdir.'/tablelib.php'); + +/** + * Backends table. + * + * @package report_datawarehouse + * @copyright 2023 Luca Bösch + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class backend_list extends \flexible_table { + + /** + * @var int Autogenerated id. + */ + private static $autoid = 0; + + /** + * Constructor + * + * @param string|null $id to be used by the table, autogenerated if null. + */ + public function __construct($id = null) { + global $PAGE; + + $id = (is_null($id) ? self::$autoid++ : $id); + parent::__construct('report_datawarehouse' . $id); + + $this->define_baseurl($PAGE->url); + $this->set_attribute('class', 'generaltable admintable'); + + // Column definition. + $this->define_columns([ + 'name', + 'description', + 'enabled', + 'used', + 'actions', + ]); + + $this->define_headers([ + get_string('name', 'report_datawarehouse'), + get_string('description', 'report_datawarehouse'), + get_string('enabled', 'report_datawarehouse'), + get_string('used', 'report_datawarehouse'), + get_string('actions'), + ]); + + $this->setup(); + } + + /** + * Display name column. + * + * @param \report_datawarehouse\backend $data Backend for this row. + * @return string + */ + protected function col_name(backend $data) : string { + return \html_writer::link( + new \moodle_url(backend_controller::get_base_url(), [ + 'id' => $data->get('id'), + 'action' => backend_controller::ACTION_EDIT, + ]), + $data->get('name') + ); + } + + /** + * Display description column. + * + * @param \report_datawarehouse\backend $data Backend for this row. + * @return string + */ + protected function col_description(backend $data) : string { + return $data->get('description'); + } + + /** + * Display enabled column. + * + * @param \report_datawarehouse\backend $data Backend for this row. + * @return string + */ + protected function col_enabled(backend $data): string { + return empty($data->get('enabled')) ? get_string('no') : get_string('yes'); + } + + /** + * Display if a backend is being used. + * + * @param \report_datawarehouse\backend $data Backend for this row. + * @return string + */ + protected function col_used(backend $data): string { + return $data->can_delete() ? get_string('no') : get_string('yes'); + } + + /** + * Display actions column. + * + * @param \report_datawarehouse\backend $data Backend for this row. + * @return string + */ + protected function col_actions(backend $data) : string { + $actions = []; + + $actions[] = helper::format_icon_link( + new \moodle_url(backend_controller::get_base_url(), [ + 'id' => $data->get('id'), + 'action' => backend_controller::ACTION_EDIT, + ]), + 't/edit', + get_string('edit') + ); + + $actions[] = helper::format_icon_link( + new \moodle_url(backend_controller::get_base_url(), [ + 'id' => $data->get('id'), + 'action' => backend_controller::ACTION_DELETE, + 'sesskey' => sesskey(), + ]), + 't/delete', + get_string('delete'), + null, + [ + 'data-action' => 'delete', + 'data-id' => $data->get('id'), + ] + ); + + return implode(' ', $actions); + } + + /** + * Sets the data of the table. + * + * @param \report_datawarehouse\backend[] $records An array with records. + */ + public function display(array $records) { + foreach ($records as $record) { + $this->add_data_keyed($this->format_row($record)); + } + + $this->finish_output(); + } + +} diff --git a/classes/local/table/query_list.php b/classes/local/table/query_list.php new file mode 100644 index 0000000..b2c80e4 --- /dev/null +++ b/classes/local/table/query_list.php @@ -0,0 +1,178 @@ +. + +/** + * Queries table. + * + * @package report_datawarehouse + * @copyright 2023 Luca Bösch + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace report_datawarehouse\local\table; + +use report_datawarehouse\helper; +use report_datawarehouse\query; +use report_datawarehouse\query_controller; + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->libdir.'/tablelib.php'); + +/** + * Queries table. + * + * @package report_datawarehouse + * @copyright 2023 Luca Bösch + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class query_list extends \flexible_table { + + /** + * @var int Autogenerated id. + */ + private static $autoid = 0; + + /** + * Constructor + * + * @param string|null $id to be used by the table, autogenerated if null. + */ + public function __construct($id = null) { + global $PAGE; + + $id = (is_null($id) ? self::$autoid++ : $id); + parent::__construct('report_datawarehouse' . $id); + + $this->define_baseurl($PAGE->url); + $this->set_attribute('class', 'generaltable admintable'); + + // Column definition. + $this->define_columns([ + 'name', + 'description', + 'enabled', + 'used', + 'actions', + ]); + + $this->define_headers([ + get_string('name', 'report_datawarehouse'), + get_string('description', 'report_datawarehouse'), + get_string('enabled', 'report_datawarehouse'), + get_string('used', 'report_datawarehouse'), + get_string('actions'), + ]); + + $this->setup(); + } + + /** + * Display name column. + * + * @param \report_datawarehouse\query $data Query for this row. + * @return string + */ + protected function col_name(query $data) : string { + return \html_writer::link( + new \moodle_url(query_controller::get_base_url(), [ + 'id' => $data->get('id'), + 'action' => query_controller::ACTION_EDIT, + ]), + $data->get('name') + ); + } + + /** + * Display description column. + * + * @param \report_datawarehouse\query $data Query for this row. + * @return string + */ + protected function col_description(query $data) : string { + return $data->get('description'); + } + + /** + * Display enabled column. + * + * @param \report_datawarehouse\query $data Query for this row. + * @return string + */ + protected function col_enabled(query $data): string { + return empty($data->get('enabled')) ? get_string('no') : get_string('yes'); + } + + /** + * Display if a query is being used. + * + * @param \report_datawarehouse\query $data Query for this row. + * @return string + */ + protected function col_used(query $data): string { + return $data->can_delete() ? get_string('no') : get_string('yes'); + } + + /** + * Display actions column. + * + * @param \report_datawarehouse\query $data Query for this row. + * @return string + */ + protected function col_actions(query $data) : string { + $actions = []; + + $actions[] = helper::format_icon_link( + new \moodle_url(query_controller::get_base_url(), [ + 'id' => $data->get('id'), + 'action' => query_controller::ACTION_EDIT, + ]), + 't/edit', + get_string('edit') + ); + + $actions[] = helper::format_icon_link( + new \moodle_url(query_controller::get_base_url(), [ + 'id' => $data->get('id'), + 'action' => query_controller::ACTION_DELETE, + 'sesskey' => sesskey(), + ]), + 't/delete', + get_string('delete'), + null, + [ + 'data-action' => 'delete', + 'data-id' => $data->get('id'), + ] + ); + + return implode(' ', $actions); + } + + /** + * Sets the data of the table. + * + * @param \report_datawarehouse\query[] $records An array with records. + */ + public function display(array $records) { + foreach ($records as $record) { + $this->add_data_keyed($this->format_row($record)); + } + + $this->finish_output(); + } + +} diff --git a/classes/output/backend.php b/classes/output/backend.php new file mode 100644 index 0000000..51bb968 --- /dev/null +++ b/classes/output/backend.php @@ -0,0 +1,161 @@ +. + +namespace report_datawarehouse\output; + +use context; +use moodle_url; +use renderable; +use templatable; +use renderer_base; +use report_datawarehouse\local\backend as report_backend; +use report_datawarehouse\local\query_category as report_category; + +/** + * Backend renderable class. + * + * @package report_datawarehouse + * @copyright 2023 Luca Bösch + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class backend implements renderable, templatable { + /** @var report_category Category object. */ + private $category; + + /** @var context Context. */ + private $context; + + /** @var int Shown query_category id from optional param. */ + private $showcat; + + /** @var int Hidden query_category id from optional param. */ + private $hidecat; + + /** @var bool Do we show the 'Show only' link? */ + private $showonlythislink; + + /** @var bool Can the query_category expanse/collapse? */ + private $expandable; + + /** @var moodle_url Return url. */ + private $returnurl; + + /** @var bool Show 'Add new backend' button or not. */ + private $addnewbackendbtn; + + /** Create the query_category renderable object. + * + * @param report_category $category Category object. + * @param context $context Context to check the permission. + * @param bool $expandable Can the query_category expanse/collapse? + * @param int $showcat Shown query_category id from optional param + * @param int $hidecat Hidden query_category id from optional param + * @param bool $showonlythislink Do we show the 'Show only' link? + * @param bool $addnewbackendbtn Show 'Add new backend' button or not. + * @param moodle_url|null $returnurl Return url. + */ + public function __construct(report_category $category, context $context, bool $expandable = false, int $showcat = 0, + int $hidecat = 0, bool $showonlythislink = false, bool $addnewbackendbtn = true, moodle_url $returnurl = null) { + $this->category = $category; + $this->context = $context; + $this->expandable = $expandable; + $this->showcat = $showcat; + $this->hidecat = $hidecat; + $this->showonlythislink = $showonlythislink; + $this->addnewbackendbtn = $addnewbackendbtn; + $this->returnurl = $returnurl ?? $this->category->get_url(); + } + + /** + * Export the data so it can be used as the context for a mustache template. + * + * @param renderer_base $output The renderer base + * @return array The data to be passed to mustache + * @throws \coding_exception + */ + public function export_for_template(renderer_base $output) { + + $backendsdata = $this->category->get_backends_data(); + + $backendgroups = []; + foreach ($backendsdata as $backendgroup) { + $backends = []; + foreach ($backendgroup['queries'] as $backenddata) { + $backend = new report_backend($backenddata); + if (!$backend->can_view($this->context)) { + continue; + } + $backendwidget = new category_query($backend, $this->category, $this->context, $this->returnurl); + $backends[] = ['categorybackenditem' => $output->render($backendwidget)]; + } + + $backendgroups[] = [ + 'type' => $backendgroup['type'], + 'title' => get_string($backendgroup['type'] . 'header', 'report_datawarehouse'), + 'helpicon' => $output->help_icon($backendgroup['type'] . 'header', 'report_datawarehouse'), + 'backends' => $backends + ]; + } + + $addbackendbutton = ''; + if ($this->addbackendbutton && has_capability('report/datawarehouse:managebackends', $this->context)) { + $addnewbackendurl = report_datawarehouse_url('editbackend.php', ['categoryid' => $this->category->get_id(), + 'returnurl' => $this->returnurl->out_as_local_url(false)]); + $addbackendbutton = $output->single_button($addnewbackendurl, get_string('addbackend', 'report_datawarehouse'), 'post', + ['class' => 'mb-1']); + } + + return [ + 'id' => $this->category->get_id(), + 'name' => $this->category->get_name(), + 'expandable' => $this->expandable, + 'show' => $this->get_showing_state(), + 'showonlythislink' => $this->showonlythislink, + 'url' => $this->category->get_url()->out(false), + 'linkref' => $this->get_link_reference(), + 'statistic' => $this->category->get_statistic(), + 'backendgroups' => $backendgroups, + 'addbackendbutton' => $addbackendbutton + ]; + } + + /** + * Get showing state of query_category. Default is hidden. + * + * @return string + */ + private function get_showing_state(): string { + $categoryid = $this->category->get_id(); + + return $categoryid == $this->showcat && $categoryid != $this->hidecat ? 'shown' : 'hidden'; + } + + /** + * Get the link with showcat/hidecat parameter. + * + * @return string The url. + */ + private function get_link_reference(): string { + $categoryid = $this->category->get_id(); + if ($categoryid == $this->showcat) { + $params = ['hidecat' => $categoryid]; + } else { + $params = ['showcat' => $categoryid]; + } + + return report_datawarehouse_url('index.php', $params)->out(false); + } +} diff --git a/classes/output/backend_category.php b/classes/output/backend_category.php new file mode 100644 index 0000000..12b509a --- /dev/null +++ b/classes/output/backend_category.php @@ -0,0 +1,162 @@ +. + +namespace report_datawarehouse\output; + +use context; +use moodle_url; +use renderable; +use templatable; +use renderer_base; +use report_datawarehouse\local\backend as report_backend; +use report_datawarehouse\local\backend_category as report_category; + +/** + * Backend category renderable class. + * + * @package report_datawarehouse + * @copyright 2023 Luca Bösch + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class backend_category implements renderable, templatable { + /** @var \report_datawarehouse\local\backend_category Category object. */ + private $backendcategory; + + /** @var context Context. */ + private $context; + + /** @var int Shown backend_category id from optional param. */ + private $showbackendcat; + + /** @var int Hidden backend_category id from optional param. */ + private $hidebackendcat; + + /** @var bool Do we show the 'Show only' link? */ + private $showonlythislink; + + /** @var bool Can the backend_category expanse/collapse? */ + private $expandable; + + /** @var moodle_url Return url. */ + private $returnurl; + + /** @var bool Show 'Add new backend' button or not. */ + private $addnewbackendbtn; + + /** Create the backend_category renderable object. + * + * @param backend_category $backendcategory Category object. + * @param context $context Context to check the permission. + * @param bool $expandable Can the backend_category expanse/collapse? + * @param int $showbackendcat Shown backend_category id from optional param + * @param int $hidebackendcat Hidden backend_category id from optional param + * @param bool $showonlythislink Do we show the 'Show only' link? + * @param bool $addnewbackendbtn Show 'Add new backend' button or not. + * @param moodle_url|null $returnurl Return url. + */ + public function __construct(\report_datawarehouse\local\backend_category $backendcategory, context $context, + bool $expandable = false, int $showbackendcat = 0, int $hidebackendcat = 0, bool $showonlythislink = false, + bool $addnewbackendbtn = true, moodle_url $returnurl = null) { + $this->backendcategory = $backendcategory; + $this->context = $context; + $this->expandable = $expandable; + $this->showbackendcat = $showbackendcat; + $this->hidebackendcat = $hidebackendcat; + $this->showonlythislink = $showonlythislink; + $this->addnewbackendbtn = $addnewbackendbtn; + $this->returnurl = $returnurl ?? $this->category->get_url(); + } + + /** + * Export the data so it can be used as the context for a mustache template. + * + * @param renderer_base $output The renderer base + * @return array The data to be passed to mustache + * @throws \coding_exception + */ + public function export_for_template(renderer_base $output) { + + $queriesdata = $this->backendcategory->get_backends_data(); + + $backendgroups = []; + foreach ($queriesdata as $backendgroup) { + $queries = []; + foreach ($backendgroup['queries'] as $backenddata) { + $backend = new report_backend($backenddata); + if (!$backend->can_view($this->context)) { + continue; + } + $backendwidget = new category_backend($backend, $this->category, $this->context, $this->returnurl); + $queries[] = ['categorybackenditem' => $output->render($backendwidget)]; + } + + $backendgroups[] = [ + 'type' => $backendgroup['type'], + 'title' => get_string($backendgroup['type'] . 'header', 'report_datawarehouse'), + 'helpicon' => $output->help_icon($backendgroup['type'] . 'header', 'report_datawarehouse'), + 'queries' => $queries + ]; + } + + $addbackendbutton = ''; + if ($this->addnewbackendbtn && has_capability('report/customsql:definequeries', $this->context)) { + $addnewbackendurl = report_datawarehouse_url('edit.php', ['categoryid' => $this->category->get_id(), + 'returnurl' => $this->returnurl->out_as_local_url(false)]); + $addbackendbutton = $output->single_button($addnewbackendurl, get_string('addreport', 'report_datawarehouse'), 'post', + ['class' => 'mb-1']); + } + + return [ + 'id' => $this->backendcategory->get_id(), + 'name' => $this->backendcategory->get_name(), + 'expandable' => $this->expandable, + 'show' => $this->get_showing_state(), + 'showonlythislink' => $this->showonlythislink, + 'url' => $this->backendcategory->get_url()->out(false), + 'linkref' => $this->get_link_reference(), + 'statistic' => $this->backendcategory->get_statistic(), + 'backendgroups' => $backendgroups, + 'addbackendbutton' => $addbackendbutton + ]; + } + + /** + * Get showing state of backend_category. Default is hidden. + * + * @return string + */ + private function get_showing_state(): string { + $categoryid = $this->backendcategory->get_id(); + + return $categoryid == $this->showbackendcat && $categoryid != $this->hidebackendcat ? 'shown' : 'hidden'; + } + + /** + * Get the link with showcat/hidecat parameter. + * + * @return string The url. + */ + private function get_link_reference(): string { + $categoryid = $this->backendcategory->get_id(); + if ($categoryid == $this->showbackendcat) { + $params = ['hidebackendcat' => $categoryid]; + } else { + $params = ['showbackendcat' => $categoryid]; + } + + return report_datawarehouse_url('index.php', $params)->out(false); + } +} diff --git a/classes/output/index_page.php b/classes/output/index_page.php new file mode 100644 index 0000000..4780535 --- /dev/null +++ b/classes/output/index_page.php @@ -0,0 +1,149 @@ +. + +namespace report_datawarehouse\output; + +use context; +use moodle_url; +use renderable; +use templatable; +use renderer_base; +use report_datawarehouse\utils; +use report_datawarehouse\local\query_category as local_query_category; +use report_datawarehouse\local\backend_category as local_backend_category;; +use report_datawarehouse\output\query_category; + +/** + * Index page renderable class. + * + * @package report_datawarehouse + * @copyright 2023 Luca Bösch + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class index_page implements renderable, templatable { + /** @var array Query querycategories' data. */ + private $querycategories; + + /** @var array Backend querycategories' data. */ + private $backendcategories; + + /** @var array Queries' data. */ + private $queries; + + /** @var array Backends' data. */ + private $backends; + + /** @var context Context to check the capability. */ + private $context; + + /** @var moodle_url Return url for edit/delete link. */ + private $returnurl; + + /** @var int Shown query_category id from optional param. */ + private $showquerycat; + + /** @var int Hidden query_category id from optional param. */ + private $hidequerycat; + + /** @var int Shown backend_category id from optional param. */ + private $showbackendcat; + + /** @var int Hidden backend_category id from optional param. */ + private $hidebackendcat; + + /** Build the index page renderable object. + * + * @param array $querycategories Query categories for renderer. + * @param array $backendcategories Query categories for renderer. + * @param array $queries Queries for renderer. + * @param array $backends Backends for renderer. + * @param context $context Context to check the capability. + * @param moodle_url $returnurl Return url for edit/delete link. + * @param int $showquerycat Showing Query category Id. + * @param int $hidequerycat Hiding Query category Id. + * @param int $showbackendcat Showing Backend category Id. + * @param int $hidebackendcat Hiding Backend category Id. + */ + public function __construct(array $querycategories, array $backendcategories, array $queries, array $backends, context $context, + moodle_url $returnurl, int $showquerycat = 0, int $hidequerycat = 0, int $showbackendcat = 0, int $hidebackendcat = 0) { + $this->querycategories = $querycategories; + $this->backendcategories = $backendcategories; + $this->queries = $queries; + $this->backends = $backends; + $this->context = $context; + $this->returnurl = $returnurl; + $this->showquerycat = $showquerycat; + $this->hidequerycat = $hidequerycat; + $this->showbackendcat = $showbackendcat; + $this->hidebackendcat = $hidebackendcat; + } + + /** + * Export the data so it can be used as the context for a mustache template. + * + * @param renderer_base $output The renderer base + * @return array The data to be passed to mustache + * @throws \coding_exception + */ + public function export_for_template(renderer_base $output) { + $querycategoriesdata = []; + $groupedqueries = utils::group_queries_by_query_category($this->queries); + foreach ($this->querycategories as $querycategory) { + $localquerycategory = new local_query_category($querycategory); + $queries = $groupedqueries[$querycategory->id] ?? []; + $localquerycategory->load_queries_data($queries); + $querycategorywidget = new query_category($localquerycategory, $this->context, true, $this->showquerycat, + $this->hidequerycat, true, false, $this->returnurl); + $querycategoriesdata[] = ['query_category' => $output->render($querycategorywidget)]; + } + $backendcategoriesdata = []; + $groupedbackends = utils::group_backends_by_backend_category($this->backends); + foreach ($this->backendcategories as $backendcategory) { + $localbackendcategory = new local_backend_category($backendcategory); + $backends = $groupedbackends[$backendcategory->id] ?? []; + $localbackendcategory->load_backends_data($backends); + $backendcategorywidget = new backend_category($localbackendcategory, $this->context, true, $this->showquerycat, + $this->hidequerycat, true, false, $this->returnurl); + $backendcategoriesdata[] = ['backend_category' => $output->render($backendcategorywidget)]; + } + + $addquerybutton = ''; + if (has_capability('report/datawarehouse:managequeries', $this->context)) { + $addquerybutton = $output->single_button(report_datawarehouse_url('editquery.php', ['returnurl' => $this->returnurl]), + get_string('addquery', 'report_datawarehouse'), 'post', ['class' => 'mb-1']); + } + $addbackendbutton = ''; + if (has_capability('report/datawarehouse:managebackends', $this->context)) { + $addbackendbutton = + $output->single_button(report_datawarehouse_url('editbackend.php', ['returnurl' => $this->returnurl]), + get_string('addbackend', 'report_datawarehouse'), 'post', ['class' => 'mb-1']); + } + // phpcs:disable + /* + if (has_capability('report/customsql:managecategories', $this->context)) { + $managecategorybutton = $output->single_button(report_customsql_url('manage.php'), + get_string('managecategories', 'report_customsql')); + } + */ + // phpcs:enable + + $data = [ + 'addquerybutton' => $addquerybutton, + 'addbackendbutton' => $addbackendbutton + ]; + return $data; + } +} diff --git a/classes/output/query.php b/classes/output/query.php new file mode 100644 index 0000000..c779d69 --- /dev/null +++ b/classes/output/query.php @@ -0,0 +1,162 @@ +. + +namespace report_datawarehouse\output; + +use context; +use moodle_url; +use renderable; +use templatable; +use renderer_base; +use report_datawarehouse\local\query as report_query; +use report_datawarehouse\local\query_category as report_category; + +/** + * Query renderable class. + * + * @package report_datawarehouse + * @copyright 2023 Luca Bösch + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class query implements renderable, templatable { + /** @var report_category Category object. */ + private $category; + + /** @var context Context. */ + private $context; + + /** @var int Shown query_category id from optional param. */ + private $showquerycat; + + /** @var int Hidden query_category id from optional param. */ + private $hidequerycat; + + /** @var bool Do we show the 'Show only' link? */ + private $showonlythislink; + + /** @var bool Can the query_category expanse/collapse? */ + private $expandable; + + /** @var moodle_url Return url. */ + private $returnurl; + + /** @var bool Show 'Add new query' button or not. */ + private $addnewquerybtn; + + /** + /** Create the query_category renderable object. + * + * @param query_category $querycategory Category object. + * @param context $context Context to check the permission. + * @param bool $expandable Can the query_category expanse/collapse? + * @param int $showquerycat Shown query_category id from optional param + * @param int $hidequerycat Hidden query_category id from optional param + * @param bool $showonlythislink Do we show the 'Show only' link? + * @param bool $addnewquerybtn Show 'Add new query' button or not. + * @param moodle_url|null $returnurl Return url. + */ + public function __construct(query_category $querycategory, context $context, bool $expandable = false, int $showquerycat = 0, + int $hidequerycat = 0, bool $showonlythislink = false, bool $addnewquerybtn = true, moodle_url $returnurl = null) { + $this->querycategory = $querycategory; + $this->context = $context; + $this->expandable = $expandable; + $this->showquerycat = $showquerycat; + $this->hidequerycat = $hidequerycat; + $this->showonlythislink = $showonlythislink; + $this->addnewquerybtn = $addnewquerybtn; + $this->returnurl = $returnurl ?? $this->category->get_url(); + } + + /** + * Export the data so it can be used as the context for a mustache template. + * + * @param renderer_base $output The renderer base + * @return array The data to be passed to mustache + * @throws \coding_exception + */ + public function export_for_template(renderer_base $output) { + + $queriesdata = $this->category->get_queries_data(); + + $querygroups = []; + foreach ($queriesdata as $querygroup) { + $queries = []; + foreach ($querygroup['queries'] as $querydata) { + $query = new report_query($querydata); + if (!$query->can_view($this->context)) { + continue; + } + $querywidget = new category_query($query, $this->category, $this->context, $this->returnurl); + $queries[] = ['categoryqueryitem' => $output->render($querywidget)]; + } + + $querygroups[] = [ + 'type' => $querygroup['type'], + 'title' => get_string($querygroup['type'] . 'header', 'report_datawarehouse'), + 'helpicon' => $output->help_icon($querygroup['type'] . 'header', 'report_datawarehouse'), + 'queries' => $queries + ]; + } + + $addquerybutton = ''; + if ($this->addnewquerybtn && has_capability('report/datawarehouse:definequeries', $this->context)) { + $addnewqueryurl = report_datawarehouse_url('edit.php', ['categoryid' => $this->category->get_id(), + 'returnurl' => $this->returnurl->out_as_local_url(false)]); + $addquerybutton = $output->single_button($addnewqueryurl, get_string('addreport', 'report_datawarehouse'), 'post', + ['class' => 'mb-1']); + } + + return [ + 'id' => $this->category->get_id(), + 'name' => $this->category->get_name(), + 'expandable' => $this->expandable, + 'show' => $this->get_showing_state(), + 'showonlythislink' => $this->showonlythislink, + 'url' => $this->category->get_url()->out(false), + 'linkref' => $this->get_link_reference(), + 'statistic' => $this->category->get_statistic(), + 'querygroups' => $querygroups, + 'addquerybutton' => $addquerybutton + ]; + } + + /** + * Get showing state of query_category. Default is hidden. + * + * @return string + */ + private function get_showing_state(): string { + $categoryid = $this->category->get_id(); + + return $categoryid == $this->showcat && $categoryid != $this->hidecat ? 'shown' : 'hidden'; + } + + /** + * Get the link with showcat/hidecat parameter. + * + * @return string The url. + */ + private function get_link_reference(): string { + $categoryid = $this->category->get_id(); + if ($categoryid == $this->showcat) { + $params = ['hidecat' => $categoryid]; + } else { + $params = ['showcat' => $categoryid]; + } + + return report_datawarehouse_url('index.php', $params)->out(false); + } +} diff --git a/classes/output/query_category.php b/classes/output/query_category.php new file mode 100644 index 0000000..285beb2 --- /dev/null +++ b/classes/output/query_category.php @@ -0,0 +1,162 @@ +. + +namespace report_datawarehouse\output; + +use context; +use moodle_url; +use renderable; +use templatable; +use renderer_base; +use report_datawarehouse\local\query as report_query; +use report_datawarehouse\local\query_category as report_category; + +/** + * Query category renderable class. + * + * @package report_datawarehouse + * @copyright 2023 Luca Bösch + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class query_category implements renderable, templatable { + /** @var \report_datawarehouse\local\query_category Category object. */ + private $querycategory; + + /** @var context Context. */ + private $context; + + /** @var int Shown query_category id from optional param. */ + private $showquerycat; + + /** @var int Hidden query_category id from optional param. */ + private $hidequerycat; + + /** @var bool Do we show the 'Show only' link? */ + private $showonlythislink; + + /** @var bool Can the query_category expanse/collapse? */ + private $expandable; + + /** @var moodle_url Return url. */ + private $returnurl; + + /** @var bool Show 'Add new query' button or not. */ + private $addnewquerybtn; + + /** Create the query_category renderable object. + * + * @param query_category $querycategory Category object. + * @param context $context Context to check the permission. + * @param bool $expandable Can the query_category expanse/collapse? + * @param int $showquerycat Shown query_category id from optional param + * @param int $hidequerycat Hidden query_category id from optional param + * @param bool $showonlythislink Do we show the 'Show only' link? + * @param bool $addnewquerybtn Show 'Add new query' button or not. + * @param moodle_url|null $returnurl Return url. + */ + public function __construct(\report_datawarehouse\local\query_category $querycategory, context $context, + bool $expandable = false, int $showquerycat = 0, int $hidequerycat = 0, bool $showonlythislink = false, + bool $addnewquerybtn = true, moodle_url $returnurl = null) { + $this->querycategory = $querycategory; + $this->context = $context; + $this->expandable = $expandable; + $this->showquerycat = $showquerycat; + $this->hidequerycat = $hidequerycat; + $this->showonlythislink = $showonlythislink; + $this->addnewquerybtn = $addnewquerybtn; + $this->returnurl = $returnurl ?? $this->category->get_url(); + } + + /** + * Export the data so it can be used as the context for a mustache template. + * + * @param renderer_base $output The renderer base + * @return array The data to be passed to mustache + * @throws \coding_exception + */ + public function export_for_template(renderer_base $output) { + + $queriesdata = $this->querycategory->get_queries_data(); + + $querygroups = []; + foreach ($queriesdata as $querygroup) { + $queries = []; + foreach ($querygroup['queries'] as $querydata) { + $query = new report_query($querydata); + if (!$query->can_view($this->context)) { + continue; + } + $querywidget = new category_query($query, $this->category, $this->context, $this->returnurl); + $queries[] = ['categoryqueryitem' => $output->render($querywidget)]; + } + + $querygroups[] = [ + 'type' => $querygroup['type'], + 'title' => get_string($querygroup['type'] . 'header', 'report_datawarehouse'), + 'helpicon' => $output->help_icon($querygroup['type'] . 'header', 'report_datawarehouse'), + 'queries' => $queries + ]; + } + + $addquerybutton = ''; + if ($this->addnewquerybtn && has_capability('report/customsql:definequeries', $this->context)) { + $addnewqueryurl = report_datawarehouse_url('edit.php', ['categoryid' => $this->category->get_id(), + 'returnurl' => $this->returnurl->out_as_local_url(false)]); + $addquerybutton = $output->single_button($addnewqueryurl, get_string('addreport', 'report_datawarehouse'), 'post', + ['class' => 'mb-1']); + } + + return [ + 'id' => $this->querycategory->get_id(), + 'name' => $this->querycategory->get_name(), + 'expandable' => $this->expandable, + 'show' => $this->get_showing_state(), + 'showonlythislink' => $this->showonlythislink, + 'url' => $this->querycategory->get_url()->out(false), + 'linkref' => $this->get_link_reference(), + 'statistic' => $this->querycategory->get_statistic(), + 'querygroups' => $querygroups, + 'addquerybutton' => $addquerybutton + ]; + } + + /** + * Get showing state of query_category. Default is hidden. + * + * @return string + */ + private function get_showing_state(): string { + $categoryid = $this->querycategory->get_id(); + + return $categoryid == $this->showquerycat && $categoryid != $this->hidequerycat ? 'shown' : 'hidden'; + } + + /** + * Get the link with showcat/hidecat parameter. + * + * @return string The url. + */ + private function get_link_reference(): string { + $categoryid = $this->querycategory->get_id(); + if ($categoryid == $this->showquerycat) { + $params = ['hidequerycat' => $categoryid]; + } else { + $params = ['showquerycat' => $categoryid]; + } + + return report_datawarehouse_url('index.php', $params)->out(false); + } +} diff --git a/classes/output/renderer.php b/classes/output/renderer.php new file mode 100644 index 0000000..2e32825 --- /dev/null +++ b/classes/output/renderer.php @@ -0,0 +1,70 @@ +. + +namespace report_datawarehouse\output; + +use context; +use html_writer; +use moodle_url; +use plugin_renderer_base; +use stdClass; + +/** + * Data warehouse report queries renderer class. + * + * @package report_datawarehouse + * @copyright 2023 Luca Bösch + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class renderer extends plugin_renderer_base { + + /** + * Output the standard action icons (edit, delete and back to list) for a report. + * + * @param stdClass $report the report. + * @param stdClass $category Category object. + * @param context $context context to use for permission checks. + * @return string HTML for report actions. + * @throws \coding_exception + * @throws \moodle_exception + */ + public function render_report_actions(stdClass $report, stdClass $category, context $context):string { + if (has_capability('report/customsql:definequeries', $context)) { + $reporturl = report_datawarehouse_url('view.php', ['id' => $report->id]); + $editaction = $this->action_link( + report_datawarehouse_url('edit.php', ['id' => $report->id, 'returnurl' => $reporturl->out_as_local_url(false)]), + $this->pix_icon('t/edit', '') . ' ' . + get_string('editreportx', 'report_datawarehouse', format_string($report->displayname))); + $deleteaction = $this->action_link( + report_datawarehouse_url('delete.php', ['id' => $report->id, 'returnurl' => $reporturl->out_as_local_url(false)]), + $this->pix_icon('t/delete', '') . ' ' . + get_string('deletereportx', 'report_datawarehouse', format_string($report->displayname))); + } + + $backtocategoryaction = $this->action_link( + report_datawarehouse_url('query_category.php', ['id' => $category->id]), + $this->pix_icon('t/left', '') . + get_string('backtocategory', 'report_datawarehouse', $category->name)); + + $context = [ + 'editaction' => $editaction, + 'deleteaction' => $deleteaction, + 'backtocategoryaction' => $backtocategoryaction + ]; + + return $this->render_from_template('report_datawarehouse/query_actions', $context); + } +} diff --git a/classes/privacy/provider.php b/classes/privacy/provider.php new file mode 100644 index 0000000..fe576a2 --- /dev/null +++ b/classes/privacy/provider.php @@ -0,0 +1,44 @@ +. +/** + * Privacy Subsystem implementation for report_datawarehouse. + * + * @package report_datawarehouse + * @copyright 2023 Luca Bösch + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace report_datawarehouse\privacy; + +/** + * Privacy Subsystem implementing null_provider. + * + * @package report_datawarehouse + * @copyright 2023 Luca Bösch + * @license https://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/classes/task/run_reports.php b/classes/task/run_reports.php new file mode 100644 index 0000000..37f8c82 --- /dev/null +++ b/classes/task/run_reports.php @@ -0,0 +1,98 @@ +. + +/** + * A scheduled task for Report Custom SQL, to run the scheduled reports. + * + * @package report_datawarehouse + * @copyright 2015 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace report_datawarehouse\task; + +/** + * A scheduled task for Report Custom SQL, to run the scheduled reports. + * + * @copyright 2015 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class run_reports extends \core\task\scheduled_task { + + /** + * Get a descriptive name for this task (shown to admins). + * + * @return string + */ + public function get_name() { + return get_string('crontask', 'report_datawarehouse'); + } + + /** + * Function to be run periodically according to the moodle cron + * This function searches for things that need to be done, such + * as sending out mail, toggling flags etc ... + * + * Runs any automatically scheduled reports weekly or monthly. + * + * @return boolean + */ + public function execute() { + global $CFG, $DB; + + require_once(dirname(__FILE__) . '/../../locallib.php'); + + $timenow = time(); + + list($startofthisweek, $startoflastweek) = report_datawarehouse_get_week_starts($timenow); + list($startofthismonth) = report_datawarehouse_get_month_starts($timenow); + + mtrace("... Looking for old temp CSV files to delete."); + $numdeleted = report_datawarehouse_delete_old_temp_files($startoflastweek); + if ($numdeleted) { + mtrace("... $numdeleted old temporary files deleted."); + } + + // Get daily scheduled reports. + $dailyreportstorun = report_datawarehouse_get_ready_to_run_daily_reports($timenow); + + // Get weekly and monthly scheduled reports. + $scheduledreportstorun = $DB->get_records_select('report_datawarehouse_queries', + "(runable = 'weekly' AND lastrun < :startofthisweek) OR + (runable = 'monthly' AND lastrun < :startofthismonth)", + array('startofthisweek' => $startofthisweek, + 'startofthismonth' => $startofthismonth), 'lastrun'); + + // All reports ready to run. + $reportstorun = array_merge($dailyreportstorun, $scheduledreportstorun); + + foreach ($reportstorun as $report) { + mtrace("... Running report " . report_datawarehouse_plain_text_report_name($report)); + try { + report_datawarehouse_generate_csv($report, $timenow); + } catch (\Exception $e) { + $info = get_exception_info($e); + mtrace("... REPORT FAILED " . $info->message); + if (!empty($info->debuginfo)) { + mtrace("\nDebug info: $info->debuginfo"); + } + if (!empty($info->backtrace)) { + mtrace("\nStack trace: " . format_backtrace($info->backtrace, true)); + } + } + } + } +} diff --git a/classes/utils.php b/classes/utils.php new file mode 100644 index 0000000..e5a4621 --- /dev/null +++ b/classes/utils.php @@ -0,0 +1,102 @@ +. + +namespace report_datawarehouse; + +/** + * Static utility methods to support the report_datawarehouse module. + * + * @package report_datawarehouse + * @copyright 2023 Luca Bösch + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class utils { + + /** + * Return the current timestamp, or a fixed timestamp specified by an automated test. + * + * @return int The timestamp + */ + public static function time(): int { + if ((defined('BEHAT_SITE_RUNNING') || PHPUNIT_TEST) && + $time = get_config('report_customsql', 'behat_fixed_time')) { + return $time; + } else { + return time(); + } + } + + /** + * Group the queries by query_category id. + * + * @param array $queries Queries need to be grouped. + * @return array Pre-loaded Categories. + */ + public static function group_queries_by_query_category($queries) { + $groupedqueries = []; + foreach ($queries as $query) { + if (isset($groupedqueries[$query->categoryid])) { + $groupedqueries[$query->categoryid][] = $query; + } else { + $groupedqueries[$query->categoryid] = [$query]; + } + } + + return $groupedqueries; + } + + /** + * Group the backends by backend_category id. + * + * @param array $backends Backends need to be grouped. + * @return array Pre-loaded Categories. + */ + public static function group_backends_by_backend_category($backends) { + $groupedbackends = []; + foreach ($backends as $backend) { + if (isset($groupedbackends[$backend->categoryid])) { + $groupedbackends[$backend->categoryid][] = $backend; + } else { + $groupedbackends[$backend->categoryid] = [$backend]; + } + } + + return $groupedbackends; + } + + /** + * Look up queries' data + * + * @param array $queries the queries to get data from + */ + public function get_queries_data($queries) { + + } + + /** + * Get queries for each type. + * + * @param array $queries Array of queries. + * @param string $type Type to filter. + * @return array All queries of type. + */ + public static function get_number_of_report_by_type(array $queries, string $type) { + return array_filter($queries, function($query) use ($type) { + return $query->runable == $type; + }, ARRAY_FILTER_USE_BOTH); + } + +} diff --git a/db/access.php b/db/access.php new file mode 100644 index 0000000..95b541a --- /dev/null +++ b/db/access.php @@ -0,0 +1,51 @@ +. + +/** + * Plugin capabilities are defined here. + * + * @package report_datawarehouse + * @query_category access + * @copyright 2023 Luca Bösch + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$capabilities = [ + + 'report/datawarehouse:view' => [ + 'captype' => 'view', + 'contextlevel' => CONTEXT_SYSTEM, + 'archetypes' => [ + 'manager' => CAP_ALLOW, + ], + ], + 'report/datawarehouse:managequeries' => array( + 'captype' => 'write', + 'contextlevel' => CONTEXT_SYSTEM, + 'archetypes' => array( + 'manager' => CAP_ALLOW + ), + ), + 'report/datawarehouse:managebackends' => array( + 'captype' => 'write', + 'contextlevel' => CONTEXT_SYSTEM, + 'archetypes' => array( + 'manager' => CAP_ALLOW + ), + ), +]; diff --git a/db/install.php b/db/install.php new file mode 100644 index 0000000..209b6a8 --- /dev/null +++ b/db/install.php @@ -0,0 +1,28 @@ +. + +/** + * Post-install script for the data warehouse report. + * @package report_datawarehouse + * @copyright 2023 Luca Bösch + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +/** + * Post-install script + */ +function xmldb_report_datawarehouse_install() { +} diff --git a/db/install.xml b/db/install.xml new file mode 100644 index 0000000..db8c2a6 --- /dev/null +++ b/db/install.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+ + + + + + + + +
+
+
diff --git a/db/uninstall.php b/db/uninstall.php new file mode 100644 index 0000000..01f4810 --- /dev/null +++ b/db/uninstall.php @@ -0,0 +1,32 @@ +. + +/** + * Code that is executed before the tables and data are dropped during the plugin uninstallation. + * + * @package report_datawarehouse + * @query_category upgrade + * @copyright 2023 Luca Bösch + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +/** + * Custom uninstallation procedure. + */ +function xmldb_report_datawarehouse_uninstall() { + + return true; +} diff --git a/index.php b/index.php new file mode 100644 index 0000000..02c9489 --- /dev/null +++ b/index.php @@ -0,0 +1,71 @@ +. + +/** + * Data warehouse report. + * + * Users with the report/datawarehouse:definequeries capability can enter custom + * SQL SELECT statements. If they have report/datawarehouse:managecategories + * capability can create custom categories for the sql reports. + * Other users with the moodle/site:viewreports capability + * can see the list of available queries and run them. Reports are displayed as + * a table. Every data value is a string, and field names come from the database + * results set. + * + * This page shows the list of categorised queries, with edit icons, an add new button + * if you have the report/datawarehouse:definequeries capability, and a manage categories button + * if you have report/datawarehouse:managecategories capability + * + * @package report_datawarehouse + * @copyright 2023 Luca Bösch + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +require_once(dirname(__FILE__) . '/../../config.php'); +require_once(dirname(__FILE__) . '/locallib.php'); +require_once($CFG->libdir . '/adminlib.php'); + +// Start the page. +admin_externalpage_setup('report_datawarehouse'); +$context = context_system::instance(); +require_capability('report/datawarehouse:view', $context); + +$querycategories = $DB->get_records('report_datawarehouse_q_cats', null, 'name, id'); +$backendcategories = $DB->get_records('report_datawarehouse_b_cats', null, 'name, id'); +// This are just dummy categories. This feature is not yet used. +$querycategories[0] = new \stdClass(); +$querycategories[0]->id = 1; +$querycategories[0]->name = 'Dummy category'; +$backendcategories[0] = new \stdClass(); +$backendcategories[0]->id = 1; +$backendcategories[0]->name = 'Dummy category'; +$queries = $DB->get_records('report_datawarehouse_queries', null, 'sortorder'); +$backends = $DB->get_records('report_datawarehouse_bkends', null, 'sortorder'); +$showquerycat = optional_param('showquerycat', 0, PARAM_INT); +$hidequerycat = optional_param('hidequerycat', 0, PARAM_INT); +$showbackendcat = optional_param('showbackendcat', 0, PARAM_INT); +$hidebackendcat = optional_param('hidebackendcat', 0, PARAM_INT); +$returnurl = report_datawarehouse_url('index.php'); + +$widget = new \report_datawarehouse\output\index_page($querycategories, $backendcategories, $queries, $backends, $context, + $returnurl, $showquerycat, $hidequerycat, $showbackendcat, $hidebackendcat); +$output = $PAGE->get_renderer('report_datawarehouse'); + +echo $OUTPUT->header(); + +echo $output->render($widget); + +echo $OUTPUT->footer(); diff --git a/lang/en/report_datawarehouse.php b/lang/en/report_datawarehouse.php new file mode 100644 index 0000000..fef74a6 --- /dev/null +++ b/lang/en/report_datawarehouse.php @@ -0,0 +1,37 @@ +. + +/** + * Plugin strings are defined here. + * + * @package report_datawarehouse + * @query_category string + * @copyright 2023 Luca Bösch + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$string['addbackend'] = 'Add new backend'; +$string['addquery'] = 'Add new query'; +$string['availablebackends'] = 'Available backends'; +$string['availablequeries'] = 'Available queries'; +$string['datawarehouse:managebackends'] = 'Manage data warehouse report backends'; +$string['datawarehouse:managequeries'] = 'Manage data warehouse report queries'; +$string['datawarehouse:view'] = 'View data warehouse reports'; +$string['nobackendsavailable'] = 'No backends available'; +$string['noqueriesavailable'] = 'No queries available'; +$string['pluginname'] = 'Data warehouse reports'; diff --git a/locallib.php b/locallib.php new file mode 100644 index 0000000..50c6424 --- /dev/null +++ b/locallib.php @@ -0,0 +1,895 @@ +. + +/** + * Library code for the report datawarehouse. + * + * @package report_datawarehouse + * @copyright 2023 Luca Bösch + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->libdir . '/validateurlsyntax.php'); + +define('REPORT_DATAWAREHOUSE_LIMIT_EXCEEDED_MARKER', '-- ROW LIMIT EXCEEDED --'); + +function report_datawarehouse_execute_query($sql, $params = null, $limitnum = null) { + global $CFG, $DB; + + if ($limitnum === null) { + $limitnum = get_config('report_datawarehouse', 'querylimitdefault'); + } + + $sql = preg_replace('/\bprefix_(?=\w+)/i', $CFG->prefix, $sql); + + foreach ($params as $name => $value) { + if (((string) (int) $value) === ((string) $value)) { + $params[$name] = (int) $value; + } + } + + // Note: throws Exception if there is an error. + return $DB->get_recordset_sql($sql, $params, 0, $limitnum); +} + +function report_datawarehouse_prepare_sql($report, $timenow) { + global $USER; + $sql = $report->querysql; + if ($report->runable != 'manual') { + list($end, $start) = report_datawarehouse_get_starts($report, $timenow); + $sql = report_datawarehouse_substitute_time_tokens($sql, $start, $end); + } + $sql = report_datawarehouse_substitute_user_token($sql, $USER->id); + return $sql; +} + +/** + * Extract all the placeholder names from the SQL. + * @param string $sql The sql. + * @return array placeholder names including the leading colon. + */ +function report_datawarehouse_get_query_placeholders($sql) { + preg_match_all('/(? form field name. + */ +function report_datawarehouse_get_query_placeholders_and_field_names(string $querysql): array { + $queryparams = []; + foreach (report_datawarehouse_get_query_placeholders($querysql) as $queryparam) { + $queryparams[substr($queryparam, 1)] = 'queryparam' . substr($queryparam, 1); + } + return $queryparams; +} + +/** + * Return the type of form field to use for a placeholder, based on its name. + * @param string $name the placeholder name. + * @return string a formslib element type, for example 'text' or 'date_time_selector'. + */ +function report_datawarehouse_get_element_type($name) { + $regex = '/^date|date$/'; + if (preg_match($regex, $name)) { + return 'date_time_selector'; + } + return 'text'; +} + +function report_datawarehouse_generate_csv($report, $timenow) { + global $DB; + $starttime = microtime(true); + + $sql = report_datawarehouse_prepare_sql($report, $timenow); + + $queryparams = !empty($report->queryparams) ? unserialize($report->queryparams) : array(); + $querylimit = $report->querylimit ?? get_config('report_datawarehouse', 'querylimitdefault'); + // Query one extra row, so we can tell if we hit the limit. + $rs = report_datawarehouse_execute_query($sql, $queryparams, $querylimit + 1); + + $csvfilenames = array(); + $csvtimestamp = null; + $count = 0; + foreach ($rs as $row) { + if (!$csvtimestamp) { + list($csvfilename, $csvtimestamp) = report_datawarehouse_csv_filename($report, $timenow); + $csvfilenames[] = $csvfilename; + + if (!file_exists($csvfilename)) { + $handle = fopen($csvfilename, 'w'); + report_datawarehouse_start_csv($handle, $row, $report); + } else { + $handle = fopen($csvfilename, 'a'); + } + } + + $data = get_object_vars($row); + foreach ($data as $name => $value) { + if (report_datawarehouse_get_element_type($name) == 'date_time_selector' && + report_datawarehouse_is_integer($value) && $value > 0) { + $data[$name] = userdate($value, '%F %T'); + } + } + if ($report->singlerow) { + array_unshift($data, date('%Y-%m-%d', $timenow)); + } + report_datawarehouse_write_csv_row($handle, $data); + $count += 1; + } + $rs->close(); + + if (!empty($handle)) { + if ($count > $querylimit) { + report_datawarehouse_write_csv_row($handle, [REPORT_DATAWAREHOUSE_LIMIT_EXCEEDED_MARKER]); + } + + fclose($handle); + } + + // Update the execution time in the DB. + $updaterecord = new stdClass(); + $updaterecord->id = $report->id; + $updaterecord->lastrun = time(); + $updaterecord->lastexecutiontime = round((microtime(true) - $starttime) * 1000); + $DB->update_record('report_datawarehouse_queries', $updaterecord); + + // Report is runable daily, weekly or monthly. + if ($report->runable != 'manual') { + if ($csvfilenames) { + foreach ($csvfilenames as $csvfilename) { + if (!empty($report->emailto)) { + report_datawarehouse_email_report($report, $csvfilename); + } + if (!empty($report->customdir)) { + report_datawarehouse_copy_csv_to_customdir($report, $timenow, $csvfilename); + } + } + } else { // If there is no data. + if (!empty($report->emailto)) { + report_datawarehouse_email_report($report); + } + if (!empty($report->customdir)) { + report_datawarehouse_copy_csv_to_customdir($report, $timenow); + } + } + } + return $csvtimestamp; +} + +/** + * Return whether a value is an integer or looks like an integer. + * + * @param mixed $value some value + * @return bool whether $value is an integer, or a string that looks like an integer. + */ +function report_datawarehouse_is_integer($value) { + return (string) (int) $value === (string) $value; +} + +function report_datawarehouse_csv_filename($report, $timenow) { + if ($report->runable == 'manual') { + return report_datawarehouse_temp_csv_name($report->id, $timenow); + + } else if ($report->singlerow) { + return report_datawarehouse_accumulating_csv_name($report->id); + + } else { + list($timestart) = report_datawarehouse_get_starts($report, $timenow); + return report_datawarehouse_scheduled_csv_name($report->id, $timestart); + } +} + +function report_datawarehouse_temp_csv_name($reportid, $timestamp) { + global $CFG; + $path = 'admin_report_datawarehouse/temp/'.$reportid; + make_upload_directory($path); + return array($CFG->dataroot.'/'.$path.'/'.date('%Y%m%d-%H%M%S', $timestamp).'.csv', + $timestamp); +} + +function report_datawarehouse_scheduled_csv_name($reportid, $timestart) { + global $CFG; + $path = 'admin_report_datawarehouse/'.$reportid; + make_upload_directory($path); + return array($CFG->dataroot.'/'.$path.'/'.date('%Y%m%d-%H%M%S', $timestart).'.csv', + $timestart); +} + +function report_datawarehouse_accumulating_csv_name($reportid) { + global $CFG; + $path = 'admin_report_datawarehouse/'.$reportid; + make_upload_directory($path); + return array($CFG->dataroot.'/'.$path.'/accumulate.csv', 0); +} + +function report_datawarehouse_get_archive_times($report) { + global $CFG; + if ($report->runable == 'manual' || $report->singlerow) { + return array(); + } + $files = glob($CFG->dataroot.'/admin_report_datawarehouse/'.$report->id.'/*.csv'); + $archivetimes = array(); + foreach ($files as $file) { + if (preg_match('|/(\d\d\d\d)(\d\d)(\d\d)-(\d\d)(\d\d)(\d\d)\.csv$|', $file, $matches)) { + $archivetimes[] = mktime($matches[4], $matches[5], $matches[6], $matches[2], + $matches[3], $matches[1]); + } + } + rsort($archivetimes); + return $archivetimes; +} + +function report_datawarehouse_substitute_time_tokens($sql, $start, $end) { + return str_replace(array('%%STARTTIME%%', '%%ENDTIME%%'), array($start, $end), $sql); +} + +function report_datawarehouse_substitute_user_token($sql, $userid) { + return str_replace('%%USERID%%', $userid, $sql); +} + +/** + * Create url to $relativeurl. + * + * @param string $relativeurl Relative url. + * @param array $params Parameter for url. + * @return moodle_url the relative url. + */ +function report_datawarehouse_url($relativeurl, $params = []) { + return new moodle_url('/report/datawarehouse/' . $relativeurl, $params); +} + +/** + * Create the download url for the report. + * + * @param int $reportid The reportid. + * @param array $params Parameters for the url. + * + * @return moodle_url The download url. + */ +function report_datawarehouse_downloadurl($reportid, $params = []) { + $downloadurl = moodle_url::make_pluginfile_url( + context_system::instance()->id, + 'report_datawarehouse', + 'download', + $reportid, + null, + null + ); + // Add the params to the url. + // Used to pass values for the arbitrary number of params in the sql report. + $downloadurl->params($params); + + return $downloadurl; +} + +function report_datawarehouse_capability_options() { + return array( + 'report/datawarehouse:view' => get_string('anyonewhocanveiwthisreport', 'report_datawarehouse'), + 'moodle/site:viewreports' => get_string('userswhocanviewsitereports', 'report_datawarehouse'), + 'moodle/site:config' => get_string('userswhocanconfig', 'report_datawarehouse') + ); +} + +function report_datawarehouse_runable_options($type = null) { + if ($type === 'manual') { + return array('manual' => get_string('manual', 'report_datawarehouse')); + } + return array('manual' => get_string('manual', 'report_datawarehouse'), + 'daily' => get_string('automaticallydaily', 'report_datawarehouse'), + 'weekly' => get_string('automaticallyweekly', 'report_datawarehouse'), + 'monthly' => get_string('automaticallymonthly', 'report_datawarehouse') + ); +} + +function report_datawarehouse_daily_at_options() { + $time = array(); + for ($h = 0; $h < 24; $h++) { + $hour = ($h < 10) ? "0$h" : $h; + $time[$h] = "$hour:00"; + } + return $time; +} + +function report_datawarehouse_email_options() { + return array('emailnumberofrows' => get_string('emailnumberofrows', 'report_datawarehouse'), + 'emailresults' => get_string('emailresults', 'report_datawarehouse'), + ); +} + +function report_datawarehouse_bad_words_list() { + return array('ALTER', 'CREATE', 'DELETE', 'DROP', 'GRANT', 'INSERT', 'INTO', + 'TRUNCATE', 'UPDATE'); +} + +function report_datawarehouse_contains_bad_word($string) { + return preg_match('/\b('.implode('|', report_datawarehouse_bad_words_list()).')\b/i', $string); +} + +function report_datawarehouse_log_delete($id) { + $event = \report_datawarehouse\event\query_deleted::create( + array('objectid' => $id, 'context' => context_system::instance())); + $event->trigger(); +} + +function report_datawarehouse_log_edit($id) { + $event = \report_datawarehouse\event\query_edited::create( + array('objectid' => $id, 'context' => context_system::instance())); + $event->trigger(); +} + +function report_datawarehouse_log_view($id) { + $event = \report_datawarehouse\event\query_viewed::create( + array('objectid' => $id, 'context' => context_system::instance())); + $event->trigger(); +} + +/** + * Returns all reports for a given type sorted by report 'displayname'. + * + * @param int $categoryid The category id + * @param string $type the type of report (manual, daily, weekly or monthly) + * @return stdClass[] relevant rows from report_datawarehouse_queries. + */ +function report_datawarehouse_get_reports_for($categoryid, $type) { + global $DB; + $records = $DB->get_records('report_datawarehouse_queries', + array('runable' => $type, 'categoryid' => $categoryid)); + + return report_datawarehouse_sort_reports_by_displayname($records); +} + +/** + * Display a list of reports of one type in one query_category. + * + * @param object $reports the result of DB query + * @param string $type the type of report (manual, daily, weekly or monthly) + */ +function report_datawarehouse_print_reports_for($reports, $type) { + global $OUTPUT; + + if (empty($reports)) { + return; + } + + if (!empty($type)) { + $help = html_writer::tag('span', $OUTPUT->help_icon($type . 'header', 'report_datawarehouse')); + echo $OUTPUT->heading(get_string($type . 'header', 'report_datawarehouse') . $help, 3); + } + + $context = context_system::instance(); + $canedit = has_capability('report/datawarehouse:definequeries', $context); + $capabilities = report_datawarehouse_capability_options(); + foreach ($reports as $report) { + if (!empty($report->capability) && !has_capability($report->capability, $context)) { + continue; + } + + echo html_writer::start_tag('p'); + echo html_writer::tag('a', format_string($report->displayname), + array('href' => report_datawarehouse_url('view.php?id='.$report->id))). + ' '.report_datawarehouse_time_note($report, 'span'); + if ($canedit) { + $imgedit = $OUTPUT->pix_icon('t/edit', get_string('edit')); + $imgdelete = $OUTPUT->pix_icon('t/delete', get_string('delete')); + echo ' '.html_writer::tag('span', get_string('availableto', 'report_datawarehouse', + $capabilities[$report->capability]), + array('class' => 'admin_note')).' '. + html_writer::tag('a', $imgedit, + ['title' => get_string('editreportx', 'report_datawarehouse', format_string($report->displayname)), + 'href' => report_datawarehouse_url('edit.php?id='.$report->id)]) . ' ' . + html_writer::tag('a', $imgdelete, + array('title' => get_string('deletereportx', 'report_datawarehouse', + format_string($report->displayname)), + 'href' => report_datawarehouse_url('delete.php?id='.$report->id))); + } + echo html_writer::end_tag('p'); + echo "\n"; + } +} + +/** + * Get the list of actual column headers from the list of raw column names. + * + * This matches up the 'Column name' and 'Column name link url' columns. + * + * @param string[] $row the row of raw column headers from the CSV file. + * @return array with two elements: the column headers to use in the table, and the columns that are links. + */ +function report_datawarehouse_get_table_headers($row) { + $colnames = array_combine($row, $row); + $linkcolumns = []; + $colheaders = []; + + foreach ($row as $key => $colname) { + if (substr($colname, -9) === ' link url' && isset($colnames[substr($colname, 0, -9)])) { + // This is a link_url column for another column. Skip. + $linkcolumns[$key] = -1; + + } else if (isset($colnames[$colname . ' link url'])) { + $colheaders[] = $colname; + $linkcolumns[$key] = array_search($colname . ' link url', $row); + } else { + $colheaders[] = $colname; + } + } + + return [$colheaders, $linkcolumns]; +} + +/** + * Prepare the values in a data row for display. + * + * This deals with $linkcolumns as detected above and other values that looks like links. + * Auto-formatting dates is handled when the CSV is generated. + * + * @param string[] $row the row of raw data. + * @param int[] $linkcolumns + * @return string[] cell contents for output. + */ +function report_datawarehouse_display_row($row, $linkcolumns) { + $rowdata = array(); + foreach ($row as $key => $value) { + if (isset($linkcolumns[$key]) && $linkcolumns[$key] === -1) { + // This row is the link url for another row. + continue; + } else if (isset($linkcolumns[$key])) { + // Column with link url coming from another column. + if (validateUrlSyntax($row[$linkcolumns[$key]], 's+H?S?F?E?u-P-a?I?p?f?q?r?')) { + $rowdata[] = '' . s($value) . ''; + } else { + $rowdata[] = s($value); + } + } else if (validateUrlSyntax($value, 's+H?S?F?E?u-P-a?I?p?f?q?r?')) { + // Column where the value just looks like a link. + $rowdata[] = '' . s($value) . ''; + } else { + $rowdata[] = s($value); + } + } + return $rowdata; +} + +function report_datawarehouse_time_note($report, $tag) { + if ($report->lastrun) { + $a = new stdClass; + $a->lastrun = userdate($report->lastrun); + $a->lastexecutiontime = $report->lastexecutiontime / 1000; + $note = get_string('lastexecuted', 'report_datawarehouse', $a); + + } else { + $note = get_string('notrunyet', 'report_datawarehouse'); + } + + return html_writer::tag($tag, $note, array('class' => 'admin_note')); +} + + +function report_datawarehouse_pretify_column_names($row, $querysql) { + $colnames = []; + + foreach (get_object_vars($row) as $colname => $ignored) { + // Databases tend to return the columns lower-cased. + // Try to get the original case from the query. + if (preg_match('~SELECT.*?\s(' . preg_quote($colname, '~') . ')\b~is', + $querysql, $matches)) { + $colname = $matches[1]; + } + + // Change underscores to spaces. + $colnames[] = str_replace('_', ' ', $colname); + } + return $colnames; +} + +/** + * Writes a CSV row and replaces placeholders. + * @param resource $handle the file pointer + * @param array $data a data row + */ +function report_datawarehouse_write_csv_row($handle, $data) { + global $CFG; + $escapeddata = array(); + foreach ($data as $value) { + $value = str_replace('%%WWWROOT%%', $CFG->wwwroot, $value); + $value = str_replace('%%Q%%', '?', $value); + $value = str_replace('%%C%%', ':', $value); + $value = str_replace('%%S%%', ';', $value); + $escapeddata[] = '"'.str_replace('"', '""', $value).'"'; + } + fwrite($handle, implode(',', $escapeddata)."\r\n"); +} + +/** + * Read the next row of data from a CSV file. + * + * Wrapper around fgetcsv to eliminate the non-standard escaping behaviour. + * + * @param resource $handle pointer to the file to read. + * @return array|false|null next row of data (as for fgetcsv). + */ +function report_datawarehouse_read_csv_row($handle) { + static $disablestupidphpescaping = null; + if ($disablestupidphpescaping === null) { + // One-time init, can be removed once we only need to support PHP 7.4+. + $disablestupidphpescaping = ''; + if (!check_php_version('7.4')) { + // This argument of fgetcsv cannot be unset in PHP < 7.4, so substitute a character which is unlikely to ever appear. + $disablestupidphpescaping = "\v"; + } + } + + return fgetcsv($handle, 0, ',', '"', $disablestupidphpescaping); +} + +function report_datawarehouse_start_csv($handle, $firstrow, $report) { + $colnames = report_datawarehouse_pretify_column_names($firstrow, $report->querysql); + if ($report->singlerow) { + array_unshift($colnames, get_string('queryrundate', 'report_datawarehouse')); + } + report_datawarehouse_write_csv_row($handle, $colnames); +} + +/** + * Get the daily start time. + * + * @param int $timenow a timestamp. + * @param int $at an hour, 0 to 23. + * @return array with two elements: the timestamp for hour $at today (where today + * is defined by $timenow) and the timestamp for hour $at yesterday. + */ +function report_datawarehouse_get_daily_time_starts($timenow, $at) { + $hours = $at; + $minutes = 0; + $dateparts = getdate($timenow); + return array( + mktime((int)$hours, (int)$minutes, 0, + $dateparts['mon'], $dateparts['mday'], $dateparts['year']), + mktime((int)$hours, (int)$minutes, 0, + $dateparts['mon'], $dateparts['mday'] - 1, $dateparts['year']), + ); +} + +function report_datawarehouse_get_week_starts($timenow) { + $dateparts = getdate($timenow); + + // Get configured start of week value. If -1 then use the value from the site calendar. + $startofweek = get_config('report_datawarehouse', 'startwday'); + if ($startofweek == -1) { + $startofweek = \core_calendar\type_factory::get_calendar_instance()->get_starting_weekday(); + } + $daysafterweekstart = ($dateparts['wday'] - $startofweek + 7) % 7; + + return array( + mktime(0, 0, 0, $dateparts['mon'], $dateparts['mday'] - $daysafterweekstart, + $dateparts['year']), + mktime(0, 0, 0, $dateparts['mon'], $dateparts['mday'] - $daysafterweekstart - 7, + $dateparts['year']), + ); +} + +function report_datawarehouse_get_month_starts($timenow) { + $dateparts = getdate($timenow); + + return array( + mktime(0, 0, 0, $dateparts['mon'], 1, $dateparts['year']), + mktime(0, 0, 0, $dateparts['mon'] - 1, 1, $dateparts['year']), + ); +} + +function report_datawarehouse_get_starts($report, $timenow) { + switch ($report->runable) { + case 'daily': + return report_datawarehouse_get_daily_time_starts($timenow, $report->at); + case 'weekly': + return report_datawarehouse_get_week_starts($timenow); + case 'monthly': + return report_datawarehouse_get_month_starts($timenow); + default: + throw new Exception('unexpected $report->runable.'); + } +} + +function report_datawarehouse_delete_old_temp_files($upto) { + global $CFG; + + $count = 0; + $comparison = date('%Y%m%d-%H%M%S', $upto).'csv'; + + $files = glob($CFG->dataroot.'/admin_report_datawarehouse/temp/*/*.csv'); + if (empty($files)) { + return; + } + foreach ($files as $file) { + if (basename($file) < $comparison) { + unlink($file); + $count += 1; + } + } + + return $count; +} + +/** + * Check the list of userids are valid, and have permission to access the report. + * + * @param array $userids user ids. + * @param string $capability capability name. + * @return string|null null if all OK, else error message. + */ +function report_datawarehouse_validate_users($userids, $capability) { + global $DB; + if (empty($userstring)) { + return null; + } + + $a = new stdClass(); + $a->capability = $capability; + $a->whocanaccess = get_string('whocanaccess', 'report_datawarehouse'); + + foreach ($userids as $userid) { + // Cannot find the user in the database. + if (!$user = $DB->get_record('user', ['id' => $userid])) { + return get_string('usernotfound', 'report_datawarehouse', $userid); + } + // User does not have the chosen access level. + $context = context_user::instance($user->id); + $a->userid = $userid; + $a->name = s(fullname($user)); + if (!has_capability($capability, $context, $user)) { + return get_string('userhasnothiscapability', 'report_datawarehouse', $a); + } + } + return null; +} + +function report_datawarehouse_get_message_no_data($report) { + // Construct subject. + $subject = get_string('emailsubjectnodata', 'report_datawarehouse', + report_datawarehouse_plain_text_report_name($report)); + $url = new moodle_url('/report/datawarehouse/view.php', array('id' => $report->id)); + $link = get_string('emailink', 'report_datawarehouse', html_writer::tag('a', $url, array('href' => $url))); + $fullmessage = html_writer::tag('p', get_string('nodatareturned', 'report_datawarehouse') . ' ' . $link); + $fullmessagehtml = $fullmessage; + + // Create the message object. + $message = new stdClass(); + $message->subject = $subject; + $message->fullmessage = $fullmessage; + $message->fullmessageformat = FORMAT_HTML; + $message->fullmessagehtml = $fullmessagehtml; + $message->smallmessage = null; + return $message; +} + +function report_datawarehouse_get_message($report, $csvfilename) { + $handle = fopen($csvfilename, 'r'); + $table = new html_table(); + $table->head = report_datawarehouse_read_csv_row($handle); + $countrows = 0; + while ($row = report_datawarehouse_read_csv_row($handle)) { + $rowdata = array(); + foreach ($row as $value) { + $rowdata[] = $value; + } + $table->data[] = $rowdata; + $countrows++; + } + fclose($handle); + + // Construct subject. + if ($countrows == 0) { + $subject = get_string('emailsubjectnodata', 'report_datawarehouse', + report_datawarehouse_plain_text_report_name($report)); + } else if ($countrows == 1) { + $subject = get_string('emailsubject1row', 'report_datawarehouse', + report_datawarehouse_plain_text_report_name($report)); + } else { + $subject = get_string('emailsubjectxrows', 'report_datawarehouse', + ['name' => report_datawarehouse_plain_text_report_name($report), 'rows' => $countrows]); + } + + // Construct message without the table. + $fullmessage = ''; + if (!html_is_blank($report->description)) { + $fullmessage .= html_writer::tag('p', format_text($report->description, FORMAT_HTML)); + } + + if ($countrows === 1) { + $returnrows = html_writer::tag('span', get_string('emailrow', 'report_datawarehouse', $countrows)); + } else { + $returnrows = html_writer::tag('span', get_string('emailrows', 'report_datawarehouse', $countrows)); + } + $url = new moodle_url('/report/datawarehouse/view.php', array('id' => $report->id)); + $link = get_string('emailink', 'report_datawarehouse', html_writer::tag('a', $url, array('href' => $url))); + $fullmessage .= html_writer::tag('p', $returnrows . ' ' . $link); + + // Construct message in html. + $fullmessagehtml = null; + if ($report->emailwhat === 'emailresults') { + $fullmessagehtml = html_writer::table($table); + } + $fullmessagehtml .= $fullmessage; + + // Create the message object. + $message = new stdClass(); + $message->subject = $subject; + $message->fullmessage = $fullmessage; + $message->fullmessageformat = FORMAT_HTML; + $message->fullmessagehtml = $fullmessagehtml; + $message->smallmessage = null; + + return $message; +} + +function report_datawarehouse_email_report($report, $csvfilename = null) { + global $DB; + + // If there are no recipients return. + if (!$report->emailto) { + return; + } + // Get the message. + if ($csvfilename) { + $message = report_datawarehouse_get_message($report, $csvfilename); + } else { + $message = report_datawarehouse_get_message_no_data($report); + } + + // Email all recipients. + $userids = preg_split("/[\s,]+/", $report->emailto); + foreach ($userids as $userid) { + $recipient = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST); + $messageid = report_datawarehouse_send_email_notification($recipient, $message); + if (!$messageid) { + mtrace(get_string('emailsentfailed', 'report_datawarehouse', fullname($recipient))); + } + } +} + +function report_datawarehouse_get_ready_to_run_daily_reports($timenow) { + global $DB; + $reports = $DB->get_records_select('report_datawarehouse_queries', "runable = ?", array('daily'), 'id'); + + $reportstorun = array(); + foreach ($reports as $id => $r) { + // Check whether the report is ready to run. + if (!report_datawarehouse_is_daily_report_ready($r, $timenow)) { + continue; + } + $reportstorun[$id] = $r; + } + return $reportstorun; +} + +/** + * Sends a notification message to the reciepients. + * + * @param object $recipient the message recipient. + * @param object $message the message object. + * @return mixed result of {@see message_send()}. + */ +function report_datawarehouse_send_email_notification($recipient, $message) { + + // Prepare the message. + $eventdata = new \core\message\message(); + $eventdata->component = 'report_datawarehouse'; + $eventdata->name = 'notification'; + $eventdata->notification = 1; + $eventdata->courseid = SITEID; + $eventdata->userfrom = \core_user::get_support_user(); + $eventdata->userto = $recipient; + $eventdata->subject = $message->subject; + $eventdata->fullmessage = $message->fullmessage; + $eventdata->fullmessageformat = $message->fullmessageformat; + $eventdata->fullmessagehtml = $message->fullmessagehtml; + $eventdata->smallmessage = $message->smallmessage; + + return message_send($eventdata); +} + +/** + * Check if the report is ready to run. + * + * @param object $report + * @param int $timenow + * @return boolean + */ +function report_datawarehouse_is_daily_report_ready($report, $timenow) { + // Time when the report should run today. + list($runtimetoday) = report_datawarehouse_get_daily_time_starts($timenow, $report->at); + + // Values used to check whether the report has already run today. + list($today) = report_datawarehouse_get_daily_time_starts($timenow, 0); + list($lastrunday) = report_datawarehouse_get_daily_time_starts($report->lastrun, 0); + + if (($runtimetoday <= $timenow) && ($today > $lastrunday)) { + return true; + } + return false; +} + +function report_datawarehouse_category_options() { + global $DB; + return $DB->get_records_menu('report_datawarehouse_categories', null, 'name ASC', 'id, name'); +} + +/** + * Copies a csv file to an optional custom directory or file path. + * + * @param object $report + * @param integer $timenow + * @param string $csvfilename + */ +function report_datawarehouse_copy_csv_to_customdir($report, $timenow, $csvfilename = null) { + + // If the filename is empty then there was no data so we can't export a + // new file, but if we are saving over the same file then we should delete + // the existing file or it will have stale data in it. + if (empty($csvfilename)) { + $filepath = $report->customdir; + if (!is_dir($filepath)) { + file_put_contents($filepath, ''); + mtrace("No data so resetting $filepath"); + } + return; + } + + $filename = $report->id . '-' . basename($csvfilename); + if (is_dir($report->customdir)) { + $filepath = realpath($report->customdir) . DIRECTORY_SEPARATOR . $filename; + } else { + $filepath = $report->customdir; + } + + copy($csvfilename, $filepath); + mtrace("Exported $csvfilename to $filepath"); +} + +/** + * Get a report name as plain text, for use in places like cron output and email subject lines. + * + * @param object $report report settings from the database. + * @return string the usable version of the name. + */ +function report_datawarehouse_plain_text_report_name($report): string { + return format_string($report->displayname, true, + ['context' => context_system::instance()]); +} + +/** + * Returns all reports for a given type sorted by report 'displayname'. + * + * @param array $records relevant rows from report_datawarehouse_queries + * @return array + */ +function report_datawarehouse_sort_reports_by_displayname(array $records): array { + $sortedrecords = []; + + foreach ($records as $record) { + $sortedrecords[$record->displayname] = $record; + } + + ksort($sortedrecords, SORT_NATURAL); + + return $sortedrecords; +} diff --git a/settings.php b/settings.php new file mode 100644 index 0000000..a5e7265 --- /dev/null +++ b/settings.php @@ -0,0 +1,32 @@ +. + +/** + * Plugin administration pages are defined here. + * + * @package report_datawarehouse + * @query_category admin + * @copyright 2023 Luca Bösch + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); +global $ADMIN; + +$ADMIN->add('reports', new admin_externalpage('report_datawarehouse', + get_string('pluginname', 'report_datawarehouse'), + new moodle_url('/report/datawarehouse/index.php'), + 'report/datawarehouse:view')); diff --git a/templates/backend.mustache b/templates/backend.mustache new file mode 100644 index 0000000..374e415 --- /dev/null +++ b/templates/backend.mustache @@ -0,0 +1,93 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle 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. + + Moodle 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 Moodle. If not, see . +}} +{{! + @template report_datawarehouse/query_category + + Template to render a query_category. + + Classes required for JS: + * none + + Data attributes required for JS: + * none + + Context variables required for this template: + * name Category name. + + Example context (json): + { + "id": 1, + "name": "query_category 1", + "expandable": false, + "show": "shown", + "showonlythislink": "true", + "url": "https://example.com/report_datawarehouse/category.php?id=1", + "linkref": "https://example.com/report_datawarehouse/index.php?hidecat=1", + "statistic": { + "manual": 1, + "daily": 2, + "weekly": 3, + "monthly": 4 + }, + "querygroups": [] + } +}} + +
+

+ {{#expandable}} + {{name}} + {{/expandable}} + {{^expandable}} + {{{name}}} + {{/expandable}} + {{#statistic}} + + {{#str}}categorycontent, report_datawarehouse, { + "manual": {{#quote}}{{manual}}{{/quote}}, + "daily": {{#quote}}{{daily}}{{/quote}}, + "weekly": {{#quote}}{{weekly}}{{/quote}}, + "monthly": {{#quote}}{{monthly}}{{/quote}} + }{{/str}} + + {{/statistic}} + {{#showonlythislink}} + {{#str}}showonlythiscategory, report_datawarehouse, {{name}}{{/str}} + {{/showonlythislink}} +

+
+ {{#querygroups}} + {{#type}} +

+ {{title}} + {{{helpicon}}} +

+ {{/type}} + {{#queries}} + {{{categoryqueryitem}}} + {{/queries}} + {{/querygroups}} + {{^querygroups}} +

{{#str}}availablebackends, report_datawarehouse{{/str}}

+

{{#str}}nobackendsavailable, report_datawarehouse{{/str}}

+ {{/querygroups}} +
+
+ +{{#addbackendbutton}} + {{{addbackendbutton}}} +{{/addbackendbutton}} diff --git a/templates/backend_category.mustache b/templates/backend_category.mustache new file mode 100644 index 0000000..8d863eb --- /dev/null +++ b/templates/backend_category.mustache @@ -0,0 +1,93 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle 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. + + Moodle 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 Moodle. If not, see . +}} +{{! + @template report_datawarehouse/backend_category + + Template to render a backend_category. + + Classes required for JS: + * none + + Data attributes required for JS: + * none + + Context variables required for this template: + * name Category name. + + Example context (json): + { + "id": 1, + "name": "query_category 1", + "expandable": false, + "show": "shown", + "showonlythislink": "true", + "url": "https://example.com/report_customsql/category.php?id=1", + "linkref": "https://example.com/report_customsql/index.php?hidecat=1", + "statistic": { + "manual": 1, + "daily": 2, + "weekly": 3, + "monthly": 4 + }, + "querygroups": [] + } +}} + +
+

+ {{#expandable}} + {{name}} + {{/expandable}} + {{^expandable}} + {{{name}}} + {{/expandable}} + {{#statistic}} + + {{#str}}categorycontent, report_datawarehouse, { + "manual": {{#quote}}{{manual}}{{/quote}}, + "daily": {{#quote}}{{daily}}{{/quote}}, + "weekly": {{#quote}}{{weekly}}{{/quote}}, + "monthly": {{#quote}}{{monthly}}{{/quote}} + }{{/str}} + + {{/statistic}} + {{#showonlythislink}} + {{#str}}showonlythiscategory, report_datawarehouse, {{name}}{{/str}} + {{/showonlythislink}} +

+
+ {{#querygroups}} + {{#type}} +

+ {{title}} + {{{helpicon}}} +

+ {{/type}} + {{#queries}} + {{{categoryqueryitem}}} + {{/queries}} + {{/querygroups}} + {{^querygroups}} +

{{#str}}availablereports, report_datawarehouse{{/str}}

+

{{#str}}noreportsavailable, report_datawarehouse{{/str}}

+ {{/querygroups}} +
+
+ +{{#addquerybutton}} + {{{addquerybutton}}} +{{/addquerybutton}} diff --git a/templates/category_query.mustache b/templates/category_query.mustache new file mode 100644 index 0000000..ea745b9 --- /dev/null +++ b/templates/category_query.mustache @@ -0,0 +1,67 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle 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. + + Moodle 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 Moodle. If not, see . +}} +{{! + @template report_customsql/category_query + + Moodle template for the list of valid options in an autocomplate form element. + + Classes required for JS: + * none + + Data attributes required for JS: + * none + + Context variables required for this template: + * displayname string Display name. + * url string Url to the query. + * timenote string to say if there are identity fields to show. + * capability string Capability text. + + Example context (json): + { + "displayname": "Query 1", + "url": "http://example.com/report/customsql/view.php?id=1", + "canedit": true, + "timenote": "This query was last run on Monday, 30 August 2021, 5:30 PM.", + "capability": "Only administrators (moodle/site:config)", + "editbutton": { + "url": "http://example.com/report/customsql/edit.php?id=1", + "img": "Edit button img" + }, + "deletebutton": { + "url": "http://example.com/green/report/customsql/delete.php?id=1", + "img": "Delete button img" + } + } +}} + +

+ {{{displayname}}} {{{timenote}}} + {{#canedit}} + {{#str}}availableto, report_customsql, {{capability}} {{/str}} + {{#editbutton}} + + {{{img}}} + + {{/editbutton}} + {{#deletebutton}} + + {{{img}}} + + {{/deletebutton}} + {{/ canedit }} +

diff --git a/templates/expand_collapse_link.mustache b/templates/expand_collapse_link.mustache new file mode 100644 index 0000000..190339e --- /dev/null +++ b/templates/expand_collapse_link.mustache @@ -0,0 +1,38 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle 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. + + Moodle 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 Moodle. If not, see . +}} +{{! + @template report_customsql/expand_collapse_link + + Moodle template for the list of valid options in an autocomplate form element. + + Classes required for JS: + * none + + Data attributes required for JS: + * none + + Example context (json): + { + + } +}} + + diff --git a/templates/form-user-selector-suggestion.mustache b/templates/form-user-selector-suggestion.mustache new file mode 100644 index 0000000..72711cc --- /dev/null +++ b/templates/form-user-selector-suggestion.mustache @@ -0,0 +1,49 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle 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. + + Moodle 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 Moodle. If not, see . +}} +{{! + @template report_customsql/form-user-selector-suggestion + + Moodle template for the list of valid options in an autocomplate form element. + + Classes required for JS: + * none + + Data attributes required for JS: + * none + + Context variables required for this template: + * profileimageurlsmall Url to a small profile image. + * fullname Users full name + * hasidentity boolean to say if there are identity fields to show + * identity concatenated list of identity fields. + + Example context (json): + { + "id": "1", + "fullname": "Admin User", + "hasidentity": true, + "identity": "admin@example.com, 0144114141", + "profileimageurlsmall": "http://example.com/smile.svg" + } +}} + + + {{fullname}} + {{#hasidentity}} + {{identity}} + {{/hasidentity}} + diff --git a/templates/form_report_information.mustache b/templates/form_report_information.mustache new file mode 100644 index 0000000..64014dc --- /dev/null +++ b/templates/form_report_information.mustache @@ -0,0 +1,44 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle 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. + + Moodle 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 Moodle. If not, see . +}} +{{! + @template report_customsql/form_report_information + + Template to display timecreated, timemodified and usermodified. + + Classes required for JS: + * none + + Data attributes required for JS: + * none + + Context variables required for this template: + * timecreated Report time created. + * timemodified Report last time modified. + * usermodified Last modified by. + + Example context (json): + { + "timecreated": "Wednesday, 5 May 2021", + "timemodified": "Wednesday, 5 May 2021", + "usermodified": "Admin User" + } +}} +
+

{{# str }} timecreated, report_customsql, {{ timecreated }} {{/ str }}

+

{{# str }} timemodified, report_customsql, {{ timemodified }} {{/ str }}

+

{{# str }} usermodified, report_customsql, {{{ usermodified }}} {{/ str }}

+
diff --git a/templates/index_page.mustache b/templates/index_page.mustache new file mode 100644 index 0000000..3c3d9df --- /dev/null +++ b/templates/index_page.mustache @@ -0,0 +1,38 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle 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. + + Moodle 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 Moodle. If not, see . +}} +{{! + @template report_datawarehouse/index_page + + Moodle template for the list of valid options in an autocomplate form element. + + Classes required for JS: + * none + + Data attributes required for JS: + * none + + Context variables required for this template: + * none + + Example context (json): + { + } +}} +

{{#str}}pluginname, report_datawarehouse{{/str}}

+ +{{>report_datawarehouse/query}} +{{>report_datawarehouse/backend}} diff --git a/templates/query.mustache b/templates/query.mustache new file mode 100644 index 0000000..1cfea39 --- /dev/null +++ b/templates/query.mustache @@ -0,0 +1,93 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle 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. + + Moodle 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 Moodle. If not, see . +}} +{{! + @template report_datawarehouse/query_category + + Template to render a query_category. + + Classes required for JS: + * none + + Data attributes required for JS: + * none + + Context variables required for this template: + * name Category name. + + Example context (json): + { + "id": 1, + "name": "query_category 1", + "expandable": false, + "show": "shown", + "showonlythislink": "true", + "url": "https://example.com/report_datawarehouse/category.php?id=1", + "linkref": "https://example.com/report_datawarehouse/index.php?hidecat=1", + "statistic": { + "manual": 1, + "daily": 2, + "weekly": 3, + "monthly": 4 + }, + "querygroups": [] + } +}} + +
+

+ {{#expandable}} + {{name}} + {{/expandable}} + {{^expandable}} + {{{name}}} + {{/expandable}} + {{#statistic}} + + {{#str}}categorycontent, report_datawarehouse, { + "manual": {{#quote}}{{manual}}{{/quote}}, + "daily": {{#quote}}{{daily}}{{/quote}}, + "weekly": {{#quote}}{{weekly}}{{/quote}}, + "monthly": {{#quote}}{{monthly}}{{/quote}} + }{{/str}} + + {{/statistic}} + {{#showonlythislink}} + {{#str}}showonlythiscategory, report_datawarehouse, {{name}}{{/str}} + {{/showonlythislink}} +

+
+ {{#querygroups}} + {{#type}} +

+ {{title}} + {{{helpicon}}} +

+ {{/type}} + {{#queries}} + {{{categoryqueryitem}}} + {{/queries}} + {{/querygroups}} + {{^querygroups}} +

{{#str}}availablequeries, report_datawarehouse{{/str}}

+

{{#str}}noqueriesavailable, report_datawarehouse{{/str}}

+ {{/querygroups}} +
+
+ +{{#addquerybutton}} + {{{addquerybutton}}} +{{/addquerybutton}} diff --git a/templates/query_actions.mustache b/templates/query_actions.mustache new file mode 100644 index 0000000..6e631ed --- /dev/null +++ b/templates/query_actions.mustache @@ -0,0 +1,45 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle 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. + + Moodle 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 Moodle. If not, see . +}} +{{! + @template report_customsql/query_actions + + Moodle template for the list of valid options in an autocomplate form element. + + Classes required for JS: + * none + + Data attributes required for JS: + * none + + Example context (json): + { + "editaction": "Edit query 'Test'", + "deleteaction": "Delete query 'Test'", + "backtocategoryaction": "Back to query_category 'Miscellaneous'" + } +}} +{{#editaction}} +

{{{editaction}}}

+{{/editaction}} + +{{#deleteaction}} +

{{{deleteaction}}}

+{{/deleteaction}} + +{{#backtocategoryaction}} +

{{{backtocategoryaction}}}

+{{/backtocategoryaction}} diff --git a/templates/query_category.mustache b/templates/query_category.mustache new file mode 100644 index 0000000..e518935 --- /dev/null +++ b/templates/query_category.mustache @@ -0,0 +1,93 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle 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. + + Moodle 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 Moodle. If not, see . +}} +{{! + @template report_datawarehouse/query_category + + Template to render a query_category. + + Classes required for JS: + * none + + Data attributes required for JS: + * none + + Context variables required for this template: + * name Category name. + + Example context (json): + { + "id": 1, + "name": "query_category 1", + "expandable": false, + "show": "shown", + "showonlythislink": "true", + "url": "https://example.com/report_customsql/category.php?id=1", + "linkref": "https://example.com/report_customsql/index.php?hidecat=1", + "statistic": { + "manual": 1, + "daily": 2, + "weekly": 3, + "monthly": 4 + }, + "querygroups": [] + } +}} + +
+

+ {{#expandable}} + {{name}} + {{/expandable}} + {{^expandable}} + {{{name}}} + {{/expandable}} + {{#statistic}} + + {{#str}}categorycontent, report_datawarehouse, { + "manual": {{#quote}}{{manual}}{{/quote}}, + "daily": {{#quote}}{{daily}}{{/quote}}, + "weekly": {{#quote}}{{weekly}}{{/quote}}, + "monthly": {{#quote}}{{monthly}}{{/quote}} + }{{/str}} + + {{/statistic}} + {{#showonlythislink}} + {{#str}}showonlythiscategory, report_datawarehouse, {{name}}{{/str}} + {{/showonlythislink}} +

+
+ {{#querygroups}} + {{#type}} +

+ {{title}} + {{{helpicon}}} +

+ {{/type}} + {{#queries}} + {{{categoryqueryitem}}} + {{/queries}} + {{/querygroups}} + {{^querygroups}} +

{{#str}}availablereports, report_datawarehouse{{/str}}

+

{{#str}}noreportsavailable, report_datawarehouse{{/str}}

+ {{/querygroups}} +
+
+ +{{#addquerybutton}} + {{{addquerybutton}}} +{{/addquerybutton}} diff --git a/version.php b/version.php new file mode 100644 index 0000000..b66d61f --- /dev/null +++ b/version.php @@ -0,0 +1,31 @@ +. + +/** + * Plugin version and other meta-data are defined here. + * + * @package report_datawarehouse + * @copyright 2023 Luca Bösch + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$plugin->component = 'report_datawarehouse'; +$plugin->release = '0.1.0'; +$plugin->version = 2023051300; +$plugin->requires = 2022112800; +$plugin->maturity = MATURITY_ALPHA;