diff --git a/week-03/project/.gitignore b/week-03/project/.gitignore
new file mode 100644
index 0000000..91d907b
--- /dev/null
+++ b/week-03/project/.gitignore
@@ -0,0 +1,28 @@
+# IDE & System Related Files
+.buildpath
+.project
+.settings
+.DS_Store
+.idea
+.vscode
+.docker
+/docker-compose.yml
+/nbproject
+
+# Local System File
+/octoconfig.php
+/.htaccess
+/web.config
+/php.ini
+
+# Vendor directory handling
+/libraries/vendor
+
+# OSX
+._*
+.Spotlight-V100
+.Trashes
+
+# Windows
+Thumbs.db
+Desktop.ini
\ No newline at end of file
diff --git a/week-03/project/LICENSE b/week-03/project/LICENSE
new file mode 100644
index 0000000..f288702
--- /dev/null
+++ b/week-03/project/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 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
+.
diff --git a/week-03/project/administrator/includes/app.php b/week-03/project/administrator/includes/app.php
new file mode 100644
index 0000000..ecdaf99
--- /dev/null
+++ b/week-03/project/administrator/includes/app.php
@@ -0,0 +1,63 @@
+
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+defined('_JEXEC') or die;
+
+// Saves the start time and memory usage.
+$startTime = microtime(1);
+$startMem = memory_get_usage();
+
+if (file_exists(dirname(__DIR__) . '/defines.php'))
+{
+ include_once dirname(__DIR__) . '/defines.php';
+}
+
+if (!defined('_JDEFINES'))
+{
+ define('JPATH_BASE', dirname(__DIR__));
+ require_once JPATH_BASE . '/includes/defines.php';
+}
+
+// Check for presence of vendor dependencies not included in the git repository
+if (!file_exists(JPATH_LIBRARIES . '/vendor/autoload.php') || !is_dir(JPATH_ROOT . '/media/vendor'))
+{
+ echo file_get_contents(JPATH_ROOT . '/templates/system/build_incomplete.html');
+
+ exit;
+}
+
+require_once JPATH_BASE . '/includes/framework.php';
+
+// Set profiler start time and memory usage and mark afterLoad in the profiler.
+JDEBUG ? \Joomla\CMS\Profiler\Profiler::getInstance('Application')->setStart($startTime, $startMem)->mark('afterLoad') : null;
+
+// Boot the DI container
+$container = \Joomla\CMS\Factory::getContainer();
+
+/*
+ * Alias the session service keys to the web session service as that is the primary session backend for this application
+ *
+ * In addition to aliasing "common" service keys, we also create aliases for the PHP classes to ensure autowiring objects
+ * is supported. This includes aliases for aliased class names, and the keys for aliased class names should be considered
+ * deprecated to be removed when the class name alias is removed as well.
+ */
+$container->alias('session.web', 'session.web.administrator')
+ ->alias('session', 'session.web.administrator')
+ ->alias('JSession', 'session.web.administrator')
+ ->alias(\Joomla\CMS\Session\Session::class, 'session.web.administrator')
+ ->alias(\Joomla\Session\Session::class, 'session.web.administrator')
+ ->alias(\Joomla\Session\SessionInterface::class, 'session.web.administrator');
+
+// Instantiate the application.
+$app = $container->get(\Joomla\CMS\Application\AdministratorApplication::class);
+
+// Set the application as global app
+\Joomla\CMS\Factory::$application = $app;
+
+// Execute the application.
+$app->execute();
diff --git a/week-03/project/administrator/includes/defines.php b/week-03/project/administrator/includes/defines.php
new file mode 100644
index 0000000..e43728b
--- /dev/null
+++ b/week-03/project/administrator/includes/defines.php
@@ -0,0 +1,27 @@
+
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+defined('_JEXEC') or die;
+
+// Global definitions
+$parts = explode(DIRECTORY_SEPARATOR, JPATH_BASE);
+array_pop($parts);
+
+// Defines
+define('JPATH_ROOT', implode(DIRECTORY_SEPARATOR, $parts));
+define('JPATH_SITE', JPATH_ROOT);
+define('JPATH_CONFIGURATION', JPATH_ROOT);
+define('JPATH_ADMINISTRATOR', JPATH_ROOT . DIRECTORY_SEPARATOR . 'administrator');
+define('JPATH_LIBRARIES', JPATH_ROOT . DIRECTORY_SEPARATOR . 'libraries');
+define('JPATH_PLUGINS', JPATH_ROOT . DIRECTORY_SEPARATOR . 'plugins');
+define('JPATH_INSTALLATION', JPATH_ROOT . DIRECTORY_SEPARATOR . 'installation');
+define('JPATH_THEMES', JPATH_BASE . DIRECTORY_SEPARATOR . 'templates');
+define('JPATH_CACHE', JPATH_ADMINISTRATOR . DIRECTORY_SEPARATOR . 'cache');
+define('JPATH_MANIFESTS', JPATH_ADMINISTRATOR . DIRECTORY_SEPARATOR . 'manifests');
+define('JPATH_API', JPATH_ROOT . DIRECTORY_SEPARATOR . 'api');
+define('JPATH_CLI', JPATH_ROOT . DIRECTORY_SEPARATOR . 'cli');
diff --git a/week-03/project/administrator/includes/framework.php b/week-03/project/administrator/includes/framework.php
new file mode 100644
index 0000000..10cf971
--- /dev/null
+++ b/week-03/project/administrator/includes/framework.php
@@ -0,0 +1,120 @@
+
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+defined('_JEXEC') or die;
+
+use Joomla\CMS\Version;
+use Joomla\Utilities\IpHelper;
+
+// System includes
+require_once JPATH_LIBRARIES . '/bootstrap.php';
+
+// Installation check, and check on removal of the install directory.
+if (!file_exists(JPATH_CONFIGURATION . '/configuration.php')
+ || (filesize(JPATH_CONFIGURATION . '/configuration.php') < 10)
+ || (file_exists(JPATH_INSTALLATION . '/index.php') && (false === (new Version)->isInDevelopmentState())))
+{
+ if (file_exists(JPATH_INSTALLATION . '/index.php'))
+ {
+ header('Location: ../installation/index.php');
+
+ exit();
+ }
+ else
+ {
+ echo 'No configuration file found and no installation code available. Exiting...';
+
+ exit;
+ }
+}
+
+// Pre-Load configuration. Don't remove the Output Buffering due to BOM issues, see JCode 26026
+ob_start();
+require_once JPATH_CONFIGURATION . '/configuration.php';
+ob_end_clean();
+
+// System configuration.
+$config = new JConfig;
+
+// Set the error_reporting, and adjust a global Error Handler
+switch ($config->error_reporting)
+{
+ case 'default':
+ case '-1':
+
+ break;
+
+ case 'none':
+ case '0':
+ error_reporting(0);
+
+ break;
+
+ case 'simple':
+ error_reporting(E_ERROR | E_WARNING | E_PARSE);
+ ini_set('display_errors', 1);
+
+ break;
+
+ case 'maximum':
+ case 'development': // <= Stays for backward compatibility, @TODO: can be removed in 5.0
+ error_reporting(E_ALL);
+ ini_set('display_errors', 1);
+
+ break;
+
+ default:
+ error_reporting($config->error_reporting);
+ ini_set('display_errors', 1);
+
+ break;
+}
+
+define('JDEBUG', $config->debug);
+
+// Check deprecation logging
+if (empty($config->log_deprecated))
+{
+ // Reset handler for E_USER_DEPRECATED
+ set_error_handler(null, E_USER_DEPRECATED);
+}
+else
+{
+ // Make sure handler for E_USER_DEPRECATED is registered
+ set_error_handler(['Joomla\CMS\Exception\ExceptionHandler', 'handleUserDeprecatedErrors'], E_USER_DEPRECATED);
+}
+
+if (JDEBUG || $config->error_reporting === 'maximum')
+{
+ // Set new Exception handler with debug enabled
+ $errorHandler->setExceptionHandler(
+ [
+ new \Symfony\Component\ErrorHandler\ErrorHandler(null, true),
+ 'renderException'
+ ]
+ );
+}
+
+/**
+ * Correctly set the allowing of IP Overrides if behind a trusted proxy/load balancer.
+ *
+ * We need to do this as high up the stack as we can, as the default in \Joomla\Utilities\IpHelper is to
+ * $allowIpOverride = true which is the wrong default for a generic site NOT behind a trusted proxy/load balancer.
+ */
+if (property_exists($config, 'behind_loadbalancer') && $config->behind_loadbalancer == 1)
+{
+ // If Joomla is configured to be behind a trusted proxy/load balancer, allow HTTP Headers to override the REMOTE_ADDR
+ IpHelper::setAllowIpOverrides(true);
+}
+else
+{
+ // We disable the allowing of IP overriding using headers by default.
+ IpHelper::setAllowIpOverrides(false);
+}
+
+unset($config);
diff --git a/week-03/project/administrator/includes/index.html b/week-03/project/administrator/includes/index.html
new file mode 100644
index 0000000..2efb97f
--- /dev/null
+++ b/week-03/project/administrator/includes/index.html
@@ -0,0 +1 @@
+
diff --git a/week-03/project/administrator/index.php b/week-03/project/administrator/index.php
new file mode 100644
index 0000000..6514d2f
--- /dev/null
+++ b/week-03/project/administrator/index.php
@@ -0,0 +1,34 @@
+
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+// NOTE: This file should remain compatible with PHP 5.2 to allow us to run our PHP minimum check and show a friendly error message
+
+// Define the application's minimum supported PHP version as a constant so it can be referenced within the application.
+define('JOOMLA_MINIMUM_PHP', '7.2.5');
+
+if (version_compare(PHP_VERSION, JOOMLA_MINIMUM_PHP, '<'))
+{
+ die(
+ str_replace(
+ '{{phpversion}}',
+ JOOMLA_MINIMUM_PHP,
+ file_get_contents(dirname(__FILE__) . '/../templates/system/incompatible.html')
+ )
+ );
+}
+
+/**
+ * Constant that is checked in included files to prevent direct access.
+ * define() is used rather than "const" to not error for PHP 5.2 and lower
+ */
+define('_JEXEC', 1);
+
+// Run the application - All executable code should be triggered through this file
+require_once dirname(__FILE__) . '/includes/app.php';
+
+
diff --git a/week-03/project/htaccess.txt b/week-03/project/htaccess.txt
new file mode 100644
index 0000000..b05371d
--- /dev/null
+++ b/week-03/project/htaccess.txt
@@ -0,0 +1,196 @@
+##
+# @copyright Copyright (C) 2014 - 2017 Open Source Matters, Inc. All rights reserved.
+# @license http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License Version 2 or Later
+##
+
+###########################################
+# ======= Set basic Security header =======
+
+# Just enable the rules on the live site and exclude the dev sites
+
+
+ # X-XSS-Protection
+ Header always set X-XSS-Protection "1; mode=block"
+ # X-Frame-Options
+ Header always set X-Frame-Options DENY
+ # X-Content-Type nosniff
+ Header always set X-Content-Type-Options nosniff
+ # Referrer Policy
+ Header always set Referrer-Policy "no-referrer-when-downgrade"
+ # Strict-Transport-Security
+ Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
+ # Cross-Origin-Opener-Policy
+ Header always set Cross-Origin-Opener-Policy "same-origin"
+ # Content-Security-Policy
+ Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'sha256-IxJ2MRv31XGmZD5ovlgSBrPmMjftYTJ3OM9/kLh6nBo=' 'sha256-QIsD5w9F5MPp3vlmsPHmbv0GZjN9ja3BuhcwpaWZuUw=' https://*.google-analytics.com https://www.googletagmanager.com https://*.pingdom.net; style-src 'self' 'sha256-4Su6mBWzEIFnH4pAGMOuaeBrstwJN4Z3pq/s1Kn4/KQ=' https://*.joomla.org https://fonts.googleapis.com; connect-src 'self' https://*.pingdom.net https://*.doubleclick.net https://*.google-analytics.com; frame-src 'self' https://*.googletagmanager.com; font-src 'self' https://fonts.gstatic.com https://*.joomla.org; img-src 'self' https://*.google-analytics.com https://*.googletagmanager.com https://*.joomla.org https://*.pingdom.net https://*.doubleclick.net https://*.githubusercontent.com https://*.travis-ci.org https://travis-ci.org; frame-ancestors 'self'; report-uri https://joomla.report-uri.com/r/t/csp/enforce"
+
+
+
+###########################################
+
+###########################################
+# ======= Enable the Rewrite Engine =======
+
+RewriteEngine On
+
+###########################################
+
+
+###########################################
+# ======= No directory listings =======
+
+IndexIgnore *
+Options +FollowSymLinks
+Options -Indexes
+
+###########################################
+
+
+###########################################
+# === Optimal default expiration time ===
+
+
+ # Enable expiration control
+ ExpiresActive On
+
+ # Default expiration: 1 hour after request
+ ExpiresDefault "now plus 1 hour"
+
+ # CSS and JS expiration: 1 week after request
+ ExpiresByType text/css "now plus 1 week"
+ ExpiresByType application/javascript "now plus 1 week"
+ ExpiresByType application/x-javascript "now plus 1 week"
+
+ # Image files expiration: 1 month after request
+ ExpiresByType image/bmp "now plus 1 month"
+ ExpiresByType image/gif "now plus 1 month"
+ ExpiresByType image/jpeg "now plus 1 month"
+ ExpiresByType image/jp2 "now plus 1 month"
+ ExpiresByType image/pipeg "now plus 1 month"
+ ExpiresByType image/png "now plus 1 month"
+ ExpiresByType image/svg+xml "now plus 1 month"
+ ExpiresByType image/tiff "now plus 1 month"
+ ExpiresByType image/vnd.microsoft.icon "now plus 1 month"
+ ExpiresByType image/x-icon "now plus 1 month"
+ ExpiresByType image/ico "now plus 1 month"
+ ExpiresByType image/icon "now plus 1 month"
+ ExpiresByType text/ico "now plus 1 month"
+ ExpiresByType application/ico "now plus 1 month"
+ ExpiresByType image/vnd.wap.wbmp "now plus 1 month"
+ ExpiresByType application/vnd.wap.wbxml "now plus 1 month"
+ ExpiresByType application/smil "now plus 1 month"
+
+ # Audio files expiration: 1 month after request
+ ExpiresByType audio/basic "now plus 1 month"
+ ExpiresByType audio/mid "now plus 1 month"
+ ExpiresByType audio/midi "now plus 1 month"
+ ExpiresByType audio/mpeg "now plus 1 month"
+ ExpiresByType audio/x-aiff "now plus 1 month"
+ ExpiresByType audio/x-mpegurl "now plus 1 month"
+ ExpiresByType audio/x-pn-realaudio "now plus 1 month"
+ ExpiresByType audio/x-wav "now plus 1 month"
+
+ # Movie files expiration: 1 month after request
+ ExpiresByType application/x-shockwave-flash "now plus 1 month"
+ ExpiresByType x-world/x-vrml "now plus 1 month"
+ ExpiresByType video/x-msvideo "now plus 1 month"
+ ExpiresByType video/mpeg "now plus 1 month"
+ ExpiresByType video/mp4 "now plus 1 month"
+ ExpiresByType video/quicktime "now plus 1 month"
+ ExpiresByType video/x-la-asf "now plus 1 month"
+ ExpiresByType video/x-ms-asf "now plus 1 month"
+
+###########################################
+
+
+###########################################
+# == Automatic compression of resources ==
+
+
+ AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css application/xml application/xhtml+xml application/rss+xml application/javascript application/x-javascript
+
+
+ mod_gzip_on Yes
+ mod_gzip_dechunk Yes
+ mod_gzip_keep_workfiles No
+ mod_gzip_can_negotiate Yes
+ mod_gzip_add_header_count Yes
+ mod_gzip_send_vary Yes
+ mod_gzip_min_http 1000
+ mod_gzip_minimum_file_size 300
+ mod_gzip_maximum_file_size 512000
+ mod_gzip_maximum_inmem_size 60000
+ mod_gzip_handle_methods GET
+ mod_gzip_item_include file \.(html?|txt|css|js|php|pl|xml|rb|py)$
+ mod_gzip_item_include mime ^text/plain$
+ mod_gzip_item_include mime ^text/html$
+ mod_gzip_item_include mime ^text/xml$
+ mod_gzip_item_include mime ^text/css$
+ mod_gzip_item_include mime ^application/xml$
+ mod_gzip_item_include mime ^application/xhtml+xml$
+ mod_gzip_item_include mime ^application/rss+xml$
+ mod_gzip_item_include mime ^application/javascript$
+ mod_gzip_item_include mime ^application/x-javascript$
+ mod_gzip_item_exclude rspheader ^Content-Encoding:.*gzip.*
+ mod_gzip_item_include handler ^cgi-script$
+ mod_gzip_item_include handler ^server-status$
+ mod_gzip_item_include handler ^server-info$
+ mod_gzip_item_include handler ^application/x-httpd-php
+ mod_gzip_item_exclude mime ^image/.*
+
+###########################################
+
+
+###########################################
+# ======== Remove multiple slashes ========
+
+RewriteCond %{HTTP_HOST} !=""
+RewriteCond %{THE_REQUEST} ^[A-Z]+\s//+(.*)\sHTTP/[0-9.]+$ [OR]
+RewriteCond %{THE_REQUEST} ^[A-Z]+\s(.*/)/+\sHTTP/[0-9.]+$
+RewriteRule .* http://%{HTTP_HOST}/%1 [R=301,L]
+
+###########################################
+
+
+###########################################
+# ======== Remove trailing slashes ========
+
+RewriteCond %{REQUEST_FILENAME} !-d
+RewriteRule ^(.*)/$ /$1 [R=301,L]
+
+###########################################
+
+
+###########################################
+# ======== Redirect HTTP to HTTPS =========
+
+#RewriteCond %{HTTPS} off
+#RewriteCond %{HTTP_HOST} ^framework.joomla.org$ [NC]
+#RewriteRule ^(.*)$ https://framework.joomla.org/$1 [R=301,L,QSA]
+
+###########################################
+
+
+###########################################
+# ======== Redirect removed pages =========
+
+#RewriteCond %{REQUEST_URI} ^/about
+#RewriteRule ^(.*)$ /contributors [R=301,L,QSA]
+
+###########################################
+
+
+###########################################
+# ======== SEF URL Routing ========
+
+# If the request is not for a static asset
+RewriteCond %{REQUEST_URI} !^/media/
+
+# Or for a file that exists in the web directory
+RewriteCond %{REQUEST_FILENAME} !-f
+RewriteCond %{REQUEST_FILENAME} !-d
+
+# Rewrite the request to run the application
+RewriteRule (.*) index.php
+
+###########################################
\ No newline at end of file
diff --git a/week-03/project/includes/app.php b/week-03/project/includes/app.php
new file mode 100644
index 0000000..15fdbdd
--- /dev/null
+++ b/week-03/project/includes/app.php
@@ -0,0 +1,63 @@
+
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+defined('_JEXEC') or die;
+
+// Saves the start time and memory usage.
+$startTime = microtime(1);
+$startMem = memory_get_usage();
+
+if (file_exists(dirname(__DIR__) . '/defines.php'))
+{
+ include_once dirname(__DIR__) . '/defines.php';
+}
+
+if (!defined('_JDEFINES'))
+{
+ define('JPATH_BASE', dirname(__DIR__));
+ require_once JPATH_BASE . '/includes/defines.php';
+}
+
+// Check for presence of vendor dependencies not included in the git repository
+if (!file_exists(JPATH_LIBRARIES . '/vendor/autoload.php') || !is_dir(JPATH_ROOT . '/media/vendor'))
+{
+ echo file_get_contents(JPATH_ROOT . '/templates/system/build_incomplete.html');
+
+ exit;
+}
+
+require_once JPATH_BASE . '/includes/framework.php';
+
+// Set profiler start time and memory usage and mark afterLoad in the profiler.
+JDEBUG && \Joomla\CMS\Profiler\Profiler::getInstance('Application')->setStart($startTime, $startMem)->mark('afterLoad');
+
+// Boot the DI container
+$container = \Joomla\CMS\Factory::getContainer();
+
+/*
+ * Alias the session service keys to the web session service as that is the primary session backend for this application
+ *
+ * In addition to aliasing "common" service keys, we also create aliases for the PHP classes to ensure autowiring objects
+ * is supported. This includes aliases for aliased class names, and the keys for aliased class names should be considered
+ * deprecated to be removed when the class name alias is removed as well.
+ */
+$container->alias('session.web', 'session.web.site')
+ ->alias('session', 'session.web.site')
+ ->alias('JSession', 'session.web.site')
+ ->alias(\Joomla\CMS\Session\Session::class, 'session.web.site')
+ ->alias(\Joomla\Session\Session::class, 'session.web.site')
+ ->alias(\Joomla\Session\SessionInterface::class, 'session.web.site');
+
+// Instantiate the application.
+$app = $container->get(\Joomla\CMS\Application\SiteApplication::class);
+
+// Set the application as global app
+\Joomla\CMS\Factory::$application = $app;
+
+// Execute the application.
+$app->execute();
diff --git a/week-03/project/includes/defines.php b/week-03/project/includes/defines.php
new file mode 100644
index 0000000..1b89ecb
--- /dev/null
+++ b/week-03/project/includes/defines.php
@@ -0,0 +1,26 @@
+
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+defined('_JEXEC') or die;
+
+// Global definitions
+$parts = explode(DIRECTORY_SEPARATOR, JPATH_BASE);
+
+// Defines.
+define('JPATH_ROOT', implode(DIRECTORY_SEPARATOR, $parts));
+define('JPATH_SITE', JPATH_ROOT);
+define('JPATH_CONFIGURATION', JPATH_ROOT);
+define('JPATH_ADMINISTRATOR', JPATH_ROOT . DIRECTORY_SEPARATOR . 'administrator');
+define('JPATH_LIBRARIES', JPATH_ROOT . DIRECTORY_SEPARATOR . 'libraries');
+define('JPATH_PLUGINS', JPATH_ROOT . DIRECTORY_SEPARATOR . 'plugins');
+define('JPATH_INSTALLATION', JPATH_ROOT . DIRECTORY_SEPARATOR . 'installation');
+define('JPATH_THEMES', JPATH_BASE . DIRECTORY_SEPARATOR . 'templates');
+define('JPATH_CACHE', JPATH_ADMINISTRATOR . DIRECTORY_SEPARATOR . 'cache');
+define('JPATH_MANIFESTS', JPATH_ADMINISTRATOR . DIRECTORY_SEPARATOR . 'manifests');
+define('JPATH_API', JPATH_ROOT . DIRECTORY_SEPARATOR . 'api');
+define('JPATH_CLI', JPATH_ROOT . DIRECTORY_SEPARATOR . 'cli');
diff --git a/week-03/project/includes/framework.php b/week-03/project/includes/framework.php
new file mode 100644
index 0000000..94d5e7f
--- /dev/null
+++ b/week-03/project/includes/framework.php
@@ -0,0 +1,123 @@
+
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+defined('_JEXEC') or die;
+
+use Joomla\CMS\Version;
+use Joomla\Utilities\IpHelper;
+
+// System includes
+require_once JPATH_LIBRARIES . '/bootstrap.php';
+
+// Installation check, and check on removal of the install directory.
+if (!file_exists(JPATH_CONFIGURATION . '/configuration.php')
+ || (filesize(JPATH_CONFIGURATION . '/configuration.php') < 10)
+ || (file_exists(JPATH_INSTALLATION . '/index.php') && (false === (new Version)->isInDevelopmentState())))
+{
+ if (file_exists(JPATH_INSTALLATION . '/index.php'))
+ {
+ header('Location: ' . substr($_SERVER['REQUEST_URI'], 0, strpos($_SERVER['REQUEST_URI'], 'index.php')) . 'installation/index.php');
+
+ exit;
+ }
+ else
+ {
+ echo 'No configuration file found and no installation code available. Exiting...';
+
+ exit;
+ }
+}
+
+// Pre-Load configuration. Don't remove the Output Buffering due to BOM issues, see JCode 26026
+ob_start();
+require_once JPATH_CONFIGURATION . '/configuration.php';
+ob_end_clean();
+
+// System configuration.
+$config = new JConfig;
+
+// Set the error_reporting, and adjust a global Error Handler
+switch ($config->error_reporting)
+{
+ case 'default':
+ case '-1':
+
+ break;
+
+ case 'none':
+ case '0':
+ error_reporting(0);
+
+ break;
+
+ case 'simple':
+ error_reporting(E_ERROR | E_WARNING | E_PARSE);
+ ini_set('display_errors', 1);
+
+ break;
+
+ case 'maximum':
+ case 'development': // <= Stays for backward compatibility, @TODO: can be removed in 5.0
+ error_reporting(E_ALL);
+ ini_set('display_errors', 1);
+
+ break;
+
+ default:
+ error_reporting($config->error_reporting);
+ ini_set('display_errors', 1);
+
+ break;
+}
+
+if (!defined('JDEBUG'))
+{
+ define('JDEBUG', $config->debug);
+}
+
+// Check deprecation logging
+if (empty($config->log_deprecated))
+{
+ // Reset handler for E_USER_DEPRECATED
+ set_error_handler(null, E_USER_DEPRECATED);
+}
+else
+{
+ // Make sure handler for E_USER_DEPRECATED is registered
+ set_error_handler(['Joomla\CMS\Exception\ExceptionHandler', 'handleUserDeprecatedErrors'], E_USER_DEPRECATED);
+}
+
+if (JDEBUG || $config->error_reporting === 'maximum')
+{
+ // Set new Exception handler with debug enabled
+ $errorHandler->setExceptionHandler(
+ [
+ new \Symfony\Component\ErrorHandler\ErrorHandler(null, true),
+ 'renderException'
+ ]
+ );
+}
+
+/**
+ * Correctly set the allowing of IP Overrides if behind a trusted proxy/load balancer.
+ *
+ * We need to do this as high up the stack as we can, as the default in \Joomla\Utilities\IpHelper is to
+ * $allowIpOverride = true which is the wrong default for a generic site NOT behind a trusted proxy/load balancer.
+ */
+if (property_exists($config, 'behind_loadbalancer') && $config->behind_loadbalancer == 1)
+{
+ // If Joomla is configured to be behind a trusted proxy/load balancer, allow HTTP Headers to override the REMOTE_ADDR
+ IpHelper::setAllowIpOverrides(true);
+}
+else
+{
+ // We disable the allowing of IP overriding using headers by default.
+ IpHelper::setAllowIpOverrides(false);
+}
+
+unset($config);
diff --git a/week-03/project/includes/index.html b/week-03/project/includes/index.html
new file mode 100644
index 0000000..2efb97f
--- /dev/null
+++ b/week-03/project/includes/index.html
@@ -0,0 +1 @@
+
diff --git a/week-03/project/index.php b/week-03/project/index.php
new file mode 100644
index 0000000..c2b6893
--- /dev/null
+++ b/week-03/project/index.php
@@ -0,0 +1,32 @@
+
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+// NOTE: This file should remain compatible with PHP 5.2 to allow us to run our PHP minimum check and show a friendly error message
+
+// Define the application's minimum supported PHP version as a constant so it can be referenced within the application.
+define('JOOMLA_MINIMUM_PHP', '7.2.5');
+
+if (version_compare(PHP_VERSION, JOOMLA_MINIMUM_PHP, '<'))
+{
+ die(
+ str_replace(
+ '{{phpversion}}',
+ JOOMLA_MINIMUM_PHP,
+ file_get_contents(dirname(__FILE__) . '/templates/system/incompatible.html')
+ )
+ );
+}
+
+/**
+ * Constant that is checked in included files to prevent direct access.
+ * define() is used rather than "const" to not error for PHP 5.2 and lower
+ */
+define('_JEXEC', 1);
+
+// Run the application - All executable code should be triggered through this file
+require_once dirname(__FILE__) . '/includes/app.php';
diff --git a/week-03/project/libraries/.htaccess b/week-03/project/libraries/.htaccess
new file mode 100644
index 0000000..9afb1a1
--- /dev/null
+++ b/week-03/project/libraries/.htaccess
@@ -0,0 +1,9 @@
+# Apache 2.4+
+
+ Require all denied
+
+
+# Apache 2.0-2.2
+
+ Deny from all
+
diff --git a/week-03/project/libraries/bootstrap.php b/week-03/project/libraries/bootstrap.php
new file mode 100644
index 0000000..54a5976
--- /dev/null
+++ b/week-03/project/libraries/bootstrap.php
@@ -0,0 +1,90 @@
+
+ * @license GNU General Public License version 2 or later; see LICENSE
+ */
+
+defined('_JEXEC') or die;
+
+// Set the platform root path as a constant if necessary.
+defined('JPATH_PLATFORM') or define('JPATH_PLATFORM', __DIR__);
+
+// Detect the native operating system type.
+$os = strtoupper(substr(PHP_OS, 0, 3));
+
+defined('IS_WIN') or define('IS_WIN', ($os === 'WIN'));
+defined('IS_UNIX') or define('IS_UNIX', (($os !== 'MAC') && ($os !== 'WIN')));
+
+// Import the library loader if necessary.
+if (!class_exists('JLoader'))
+{
+ require_once JPATH_PLATFORM . '/loader.php';
+
+ // If JLoader still does not exist panic.
+ if (!class_exists('JLoader'))
+ {
+ throw new RuntimeException('Joomla Platform not loaded.');
+ }
+}
+
+// Setup the autoloaders.
+JLoader::setup();
+
+// Create the Composer autoloader
+/** @var \Composer\Autoload\ClassLoader $loader */
+$loader = require JPATH_LIBRARIES . '/vendor/autoload.php';
+
+// We need to pull our decorated class loader into memory before unregistering Composer's loader
+class_exists('\\Joomla\\CMS\\Autoload\\ClassLoader');
+
+$loader->unregister();
+
+// Decorate Composer autoloader
+spl_autoload_register([new \Joomla\CMS\Autoload\ClassLoader($loader), 'loadClass'], true, true);
+
+// Register the class aliases for Framework classes that have replaced their Platform equivalents
+require_once JPATH_LIBRARIES . '/classmap.php';
+
+/**
+ * Register the global exception handler. And set error level to server default error level.
+ * The error level may be changed later in boot up process, after application config will be loaded.
+ * Do not remove the variable, to allow to use it further, after including this file.
+ */
+$errorHandler = \Symfony\Component\ErrorHandler\ErrorHandler::register();
+\Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer::setTemplate(__DIR__ . '/../templates/system/fatal.php');
+
+// Register the error handler which processes E_USER_DEPRECATED errors
+if (error_reporting() & E_USER_DEPRECATED)
+{
+ set_error_handler(['Joomla\CMS\Exception\ExceptionHandler', 'handleUserDeprecatedErrors'], E_USER_DEPRECATED);
+}
+
+// Suppress phar stream wrapper for non .phar files
+$behavior = new \TYPO3\PharStreamWrapper\Behavior;
+\TYPO3\PharStreamWrapper\Manager::initialize(
+ $behavior->withAssertion(new \TYPO3\PharStreamWrapper\Interceptor\PharExtensionInterceptor)
+);
+
+if (in_array('phar', stream_get_wrappers()))
+{
+ stream_wrapper_unregister('phar');
+ stream_wrapper_register('phar', 'TYPO3\\PharStreamWrapper\\PharStreamWrapper');
+}
+
+// Define the Joomla version if not already defined.
+defined('JVERSION') or define('JVERSION', (new \Joomla\CMS\Version)->getShortVersion());
+
+// Set up the message queue logger for web requests
+if (array_key_exists('REQUEST_METHOD', $_SERVER))
+{
+ \Joomla\CMS\Log\Log::addLogger(['logger' => 'messagequeue'], \Joomla\CMS\Log\Log::ALL, ['jerror']);
+}
+
+// Register the Crypto lib
+JLoader::register('Crypto', JPATH_PLATFORM . '/php-encryption/Crypto.php');
+
+// Register the PasswordHash library.
+JLoader::register('PasswordHash', JPATH_PLATFORM . '/phpass/PasswordHash.php');
diff --git a/week-03/project/libraries/loader.php b/week-03/project/libraries/loader.php
new file mode 100644
index 0000000..2389dca
--- /dev/null
+++ b/week-03/project/libraries/loader.php
@@ -0,0 +1,790 @@
+
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+defined('JPATH_PLATFORM') or die;
+
+/**
+ * Static class to handle loading of libraries.
+ *
+ * @since 1.7.0
+ */
+abstract class JLoader
+{
+ /**
+ * Container for already imported library paths.
+ *
+ * @var array
+ * @since 1.7.0
+ */
+ protected static $classes = array();
+
+ /**
+ * Container for already imported library paths.
+ *
+ * @var array
+ * @since 1.7.0
+ */
+ protected static $imported = array();
+
+ /**
+ * Container for registered library class prefixes and path lookups.
+ *
+ * @var array
+ * @since 3.0.0
+ */
+ protected static $prefixes = array();
+
+ /**
+ * Holds proxy classes and the class names the proxy.
+ *
+ * @var array
+ * @since 3.2
+ */
+ protected static $classAliases = array();
+
+ /**
+ * Holds the inverse lookup for proxy classes and the class names the proxy.
+ *
+ * @var array
+ * @since 3.4
+ */
+ protected static $classAliasesInverse = array();
+
+ /**
+ * Container for namespace => path map.
+ *
+ * @var array
+ * @since 3.1.4
+ */
+ protected static $namespaces = array();
+
+ /**
+ * Holds a reference for all deprecated aliases (mainly for use by a logging platform).
+ *
+ * @var array
+ * @since 3.6.3
+ */
+ protected static $deprecatedAliases = array();
+
+ /**
+ * The root folders where extensions can be found.
+ *
+ * @var array
+ * @since 4.0.0
+ */
+ protected static $extensionRootFolders = array();
+
+ /**
+ * Method to discover classes of a given type in a given path.
+ *
+ * @param string $classPrefix The class name prefix to use for discovery.
+ * @param string $parentPath Full path to the parent folder for the classes to discover.
+ * @param boolean $force True to overwrite the autoload path value for the class if it already exists.
+ * @param boolean $recurse Recurse through all child directories as well as the parent path.
+ *
+ * @return void
+ *
+ * @since 1.7.0
+ * @deprecated 5.0 Classes should be autoloaded. Use JLoader::registerPrefix() or JLoader::registerNamespace() to register an autoloader for
+ * your files.
+ */
+ public static function discover($classPrefix, $parentPath, $force = true, $recurse = false)
+ {
+ try
+ {
+ if ($recurse)
+ {
+ $iterator = new RecursiveIteratorIterator(
+ new RecursiveDirectoryIterator($parentPath),
+ RecursiveIteratorIterator::SELF_FIRST
+ );
+ }
+ else
+ {
+ $iterator = new DirectoryIterator($parentPath);
+ }
+
+ /** @type $file DirectoryIterator */
+ foreach ($iterator as $file)
+ {
+ $fileName = $file->getFilename();
+
+ // Only load for php files.
+ if ($file->isFile() && $file->getExtension() === 'php')
+ {
+ // Get the class name and full path for each file.
+ $class = strtolower($classPrefix . preg_replace('#\.php$#', '', $fileName));
+
+ // Register the class with the autoloader if not already registered or the force flag is set.
+ if ($force || empty(self::$classes[$class]))
+ {
+ self::register($class, $file->getPath() . '/' . $fileName);
+ }
+ }
+ }
+ }
+ catch (UnexpectedValueException $e)
+ {
+ // Exception will be thrown if the path is not a directory. Ignore it.
+ }
+ }
+
+ /**
+ * Method to get the list of registered classes and their respective file paths for the autoloader.
+ *
+ * @return array The array of class => path values for the autoloader.
+ *
+ * @since 1.7.0
+ */
+ public static function getClassList()
+ {
+ return self::$classes;
+ }
+
+ /**
+ * Method to get the list of deprecated class aliases.
+ *
+ * @return array An associative array with deprecated class alias data.
+ *
+ * @since 3.6.3
+ */
+ public static function getDeprecatedAliases()
+ {
+ return self::$deprecatedAliases;
+ }
+
+ /**
+ * Method to get the list of registered namespaces.
+ *
+ * @return array The array of namespace => path values for the autoloader.
+ *
+ * @since 3.1.4
+ */
+ public static function getNamespaces()
+ {
+ return self::$namespaces;
+ }
+
+ /**
+ * Loads a class from specified directories.
+ *
+ * @param string $key The class name to look for (dot notation).
+ * @param string $base Search this directory for the class.
+ *
+ * @return boolean True on success.
+ *
+ * @since 1.7.0
+ * @deprecated 5.0 Classes should be autoloaded. Use JLoader::registerPrefix() or JLoader::registerNamespace() to register an autoloader for
+ * your files.
+ */
+ public static function import($key, $base = null)
+ {
+ // Only import the library if not already attempted.
+ if (!isset(self::$imported[$key]))
+ {
+ // Setup some variables.
+ $success = false;
+ $parts = explode('.', $key);
+ $class = array_pop($parts);
+ $base = (!empty($base)) ? $base : __DIR__;
+ $path = str_replace('.', DIRECTORY_SEPARATOR, $key);
+
+ // Handle special case for helper classes.
+ if ($class === 'helper')
+ {
+ $class = ucfirst(array_pop($parts)) . ucfirst($class);
+ }
+ // Standard class.
+ else
+ {
+ $class = ucfirst($class);
+ }
+
+ // If we are importing a library from the Joomla namespace set the class to autoload.
+ if (strpos($path, 'joomla') === 0)
+ {
+ // Since we are in the Joomla namespace prepend the classname with J.
+ $class = 'J' . $class;
+
+ // Only register the class for autoloading if the file exists.
+ if (is_file($base . '/' . $path . '.php'))
+ {
+ self::$classes[strtolower($class)] = $base . '/' . $path . '.php';
+ $success = true;
+ }
+ }
+ /*
+ * If we are not importing a library from the Joomla namespace directly include the
+ * file since we cannot assert the file/folder naming conventions.
+ */
+ else
+ {
+ // If the file exists attempt to include it.
+ if (is_file($base . '/' . $path . '.php'))
+ {
+ $success = (bool) include_once $base . '/' . $path . '.php';
+ }
+ }
+
+ // Add the import key to the memory cache container.
+ self::$imported[$key] = $success;
+ }
+
+ return self::$imported[$key];
+ }
+
+ /**
+ * Load the file for a class.
+ *
+ * @param string $class The class to be loaded.
+ *
+ * @return boolean True on success
+ *
+ * @since 1.7.0
+ */
+ public static function load($class)
+ {
+ // Sanitize class name.
+ $key = strtolower($class);
+
+ // If the class already exists do nothing.
+ if (class_exists($class, false))
+ {
+ return true;
+ }
+
+ // If the class is registered include the file.
+ if (isset(self::$classes[$key]))
+ {
+ $found = (bool) include_once self::$classes[$key];
+
+ if ($found)
+ {
+ self::loadAliasFor($class);
+ }
+
+ // If the class doesn't exists, we probably have a class alias available
+ if (!class_exists($class, false))
+ {
+ // Search the alias class, first none namespaced and then namespaced
+ $original = array_search($class, self::$classAliases) ? : array_search('\\' . $class, self::$classAliases);
+
+ // When we have an original and the class exists an alias should be created
+ if ($original && class_exists($original, false))
+ {
+ class_alias($original, $class);
+ }
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Directly register a class to the autoload list.
+ *
+ * @param string $class The class name to register.
+ * @param string $path Full path to the file that holds the class to register.
+ * @param boolean $force True to overwrite the autoload path value for the class if it already exists.
+ *
+ * @return void
+ *
+ * @since 1.7.0
+ * @deprecated 5.0 Classes should be autoloaded. Use JLoader::registerPrefix() or JLoader::registerNamespace() to register an autoloader for
+ * your files.
+ */
+ public static function register($class, $path, $force = true)
+ {
+ // When an alias exists, register it as well
+ if (array_key_exists(strtolower($class), self::$classAliases))
+ {
+ self::register(self::stripFirstBackslash(self::$classAliases[strtolower($class)]), $path, $force);
+ }
+
+ // Sanitize class name.
+ $class = strtolower($class);
+
+ // Only attempt to register the class if the name and file exist.
+ if (!empty($class) && is_file($path))
+ {
+ // Register the class with the autoloader if not already registered or the force flag is set.
+ if ($force || empty(self::$classes[$class]))
+ {
+ self::$classes[$class] = $path;
+ }
+ }
+ }
+
+ /**
+ * Register a class prefix with lookup path. This will allow developers to register library
+ * packages with different class prefixes to the system autoloader. More than one lookup path
+ * may be registered for the same class prefix, but if this method is called with the reset flag
+ * set to true then any registered lookups for the given prefix will be overwritten with the current
+ * lookup path. When loaded, prefix paths are searched in a "last in, first out" order.
+ *
+ * @param string $prefix The class prefix to register.
+ * @param string $path Absolute file path to the library root where classes with the given prefix can be found.
+ * @param boolean $reset True to reset the prefix with only the given lookup path.
+ * @param boolean $prepend If true, push the path to the beginning of the prefix lookup paths array.
+ *
+ * @return void
+ *
+ * @throws RuntimeException
+ *
+ * @since 3.0.0
+ */
+ public static function registerPrefix($prefix, $path, $reset = false, $prepend = false)
+ {
+ // Verify the library path exists.
+ if (!is_dir($path))
+ {
+ $path = (str_replace(JPATH_ROOT, '', $path) == $path) ? basename($path) : str_replace(JPATH_ROOT, '', $path);
+
+ throw new RuntimeException('Library path ' . $path . ' cannot be found.', 500);
+ }
+
+ // If the prefix is not yet registered or we have an explicit reset flag then set set the path.
+ if ($reset || !isset(self::$prefixes[$prefix]))
+ {
+ self::$prefixes[$prefix] = array($path);
+ }
+ // Otherwise we want to simply add the path to the prefix.
+ else
+ {
+ if ($prepend)
+ {
+ array_unshift(self::$prefixes[$prefix], $path);
+ }
+ else
+ {
+ self::$prefixes[$prefix][] = $path;
+ }
+ }
+ }
+
+ /**
+ * Offers the ability for "just in time" usage of `class_alias()`.
+ * You cannot overwrite an existing alias.
+ *
+ * @param string $alias The alias name to register.
+ * @param string $original The original class to alias.
+ * @param string|boolean $version The version in which the alias will no longer be present.
+ *
+ * @return boolean True if registration was successful. False if the alias already exists.
+ *
+ * @since 3.2
+ */
+ public static function registerAlias($alias, $original, $version = false)
+ {
+ // PHP is case insensitive so support all kind of alias combination
+ $lowercasedAlias = strtolower($alias);
+
+ if (!isset(self::$classAliases[$lowercasedAlias]))
+ {
+ self::$classAliases[$lowercasedAlias] = $original;
+
+ $original = self::stripFirstBackslash($original);
+
+ if (!isset(self::$classAliasesInverse[$original]))
+ {
+ self::$classAliasesInverse[$original] = array($lowercasedAlias);
+ }
+ else
+ {
+ self::$classAliasesInverse[$original][] = $lowercasedAlias;
+ }
+
+ // If given a version, log this alias as deprecated
+ if ($version)
+ {
+ self::$deprecatedAliases[] = array('old' => $alias, 'new' => $original, 'version' => $version);
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Register a namespace to the autoloader. When loaded, namespace paths are searched in a "last in, first out" order.
+ *
+ * @param string $namespace A case sensitive Namespace to register.
+ * @param string $path A case sensitive absolute file path to the library root where classes of the given namespace can be found.
+ * @param boolean $reset True to reset the namespace with only the given lookup path.
+ * @param boolean $prepend If true, push the path to the beginning of the namespace lookup paths array.
+ *
+ * @return void
+ *
+ * @throws RuntimeException
+ *
+ * @since 3.1.4
+ */
+ public static function registerNamespace($namespace, $path, $reset = false, $prepend = false)
+ {
+ // Verify the library path exists.
+ if (!is_dir($path))
+ {
+ $path = (str_replace(JPATH_ROOT, '', $path) == $path) ? basename($path) : str_replace(JPATH_ROOT, '', $path);
+
+ throw new RuntimeException('Library path ' . $path . ' cannot be found.', 500);
+ }
+
+ // Trim leading and trailing backslashes from namespace, allowing "\Parent\Child", "Parent\Child\" and "\Parent\Child\" to be treated the same way.
+ $namespace = trim($namespace, '\\');
+
+ // If the namespace is not yet registered or we have an explicit reset flag then set the path.
+ if ($reset || !isset(self::$namespaces[$namespace]))
+ {
+ self::$namespaces[$namespace] = array($path);
+ }
+
+ // Otherwise we want to simply add the path to the namespace.
+ else
+ {
+ if ($prepend)
+ {
+ array_unshift(self::$namespaces[$namespace], $path);
+ }
+ else
+ {
+ self::$namespaces[$namespace][] = $path;
+ }
+ }
+ }
+
+ /**
+ * Method to setup the autoloaders for the Joomla Platform.
+ * Since the SPL autoloaders are called in a queue we will add our explicit
+ * class-registration based loader first, then fall back on the autoloader based on conventions.
+ * This will allow people to register a class in a specific location and override platform libraries
+ * as was previously possible.
+ *
+ * @param boolean $enablePsr True to enable autoloading based on PSR-0.
+ * @param boolean $enablePrefixes True to enable prefix based class loading (needed to auto load the Joomla core).
+ * @param boolean $enableClasses True to enable class map based class loading (needed to auto load the Joomla core).
+ *
+ * @return void
+ *
+ * @since 3.1.4
+ */
+ public static function setup($enablePsr = true, $enablePrefixes = true, $enableClasses = true)
+ {
+ if ($enableClasses)
+ {
+ // Register the class map based autoloader.
+ spl_autoload_register(array('JLoader', 'load'));
+ }
+
+ if ($enablePrefixes)
+ {
+ // Register the prefix autoloader.
+ spl_autoload_register(array('JLoader', '_autoload'));
+ }
+
+ if ($enablePsr)
+ {
+ // Register the PSR based autoloader.
+ spl_autoload_register(array('JLoader', 'loadByPsr'));
+ spl_autoload_register(array('JLoader', 'loadByAlias'));
+ }
+ }
+
+ /**
+ * Method to autoload classes that are namespaced to the PSR-4 standard.
+ *
+ * @param string $class The fully qualified class name to autoload.
+ *
+ * @return boolean True on success, false otherwise.
+ *
+ * @since 3.7.0
+ * @deprecated 5.0 Use JLoader::loadByPsr instead
+ */
+ public static function loadByPsr4($class)
+ {
+ return self::loadByPsr($class);
+ }
+
+ /**
+ * Method to autoload classes that are namespaced to the PSR-4 standard.
+ *
+ * @param string $class The fully qualified class name to autoload.
+ *
+ * @return boolean True on success, false otherwise.
+ *
+ * @since 4.0.0
+ */
+ public static function loadByPsr($class)
+ {
+ $class = self::stripFirstBackslash($class);
+
+ // Find the location of the last NS separator.
+ $pos = strrpos($class, '\\');
+
+ // If one is found, we're dealing with a NS'd class.
+ if ($pos !== false)
+ {
+ $classPath = str_replace('\\', DIRECTORY_SEPARATOR, substr($class, 0, $pos)) . DIRECTORY_SEPARATOR;
+ $className = substr($class, $pos + 1);
+ }
+ // If not, no need to parse path.
+ else
+ {
+ $classPath = null;
+ $className = $class;
+ }
+
+ $classPath .= $className . '.php';
+
+ // Loop through registered namespaces until we find a match.
+ foreach (self::$namespaces as $ns => $paths)
+ {
+ if (strpos($class, "{$ns}\\") === 0)
+ {
+ $nsPath = trim(str_replace('\\', DIRECTORY_SEPARATOR, $ns), DIRECTORY_SEPARATOR);
+
+ // Loop through paths registered to this namespace until we find a match.
+ foreach ($paths as $path)
+ {
+ $classFilePath = realpath($path . DIRECTORY_SEPARATOR . substr_replace($classPath, '', 0, strlen($nsPath) + 1));
+
+ // We do not allow files outside the namespace root to be loaded
+ if (strpos($classFilePath, realpath($path)) !== 0)
+ {
+ continue;
+ }
+
+ // We check for class_exists to handle case-sensitive file systems
+ if (is_file($classFilePath) && !class_exists($class, false))
+ {
+ $found = (bool) include_once $classFilePath;
+
+ if ($found)
+ {
+ self::loadAliasFor($class);
+ }
+
+ return $found;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Method to autoload classes that have been aliased using the registerAlias method.
+ *
+ * @param string $class The fully qualified class name to autoload.
+ *
+ * @return boolean True on success, false otherwise.
+ *
+ * @since 3.2
+ */
+ public static function loadByAlias($class)
+ {
+ $class = strtolower(self::stripFirstBackslash($class));
+
+ if (isset(self::$classAliases[$class]))
+ {
+ // Force auto-load of the regular class
+ class_exists(self::$classAliases[$class], true);
+
+ // Normally this shouldn't execute as the autoloader will execute applyAliasFor when the regular class is
+ // auto-loaded above.
+ if (!class_exists($class, false) && !interface_exists($class, false))
+ {
+ class_alias(self::$classAliases[$class], $class);
+ }
+ }
+ }
+
+ /**
+ * Applies a class alias for an already loaded class, if a class alias was created for it.
+ *
+ * @param string $class We'll look for and register aliases for this (real) class name
+ *
+ * @return void
+ *
+ * @since 3.4
+ */
+ public static function applyAliasFor($class)
+ {
+ $class = self::stripFirstBackslash($class);
+
+ if (isset(self::$classAliasesInverse[$class]))
+ {
+ foreach (self::$classAliasesInverse[$class] as $alias)
+ {
+ class_alias($class, $alias);
+ }
+ }
+ }
+
+ /**
+ * Autoload a class based on name.
+ *
+ * @param string $class The class to be loaded.
+ *
+ * @return boolean True if the class was loaded, false otherwise.
+ *
+ * @since 1.7.3
+ */
+ public static function _autoload($class)
+ {
+ foreach (self::$prefixes as $prefix => $lookup)
+ {
+ $chr = strlen($prefix) < strlen($class) ? $class[strlen($prefix)] : 0;
+
+ if (strpos($class, $prefix) === 0 && ($chr === strtoupper($chr)))
+ {
+ return self::_load(substr($class, strlen($prefix)), $lookup);
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Load a class based on name and lookup array.
+ *
+ * @param string $class The class to be loaded (without prefix).
+ * @param array $lookup The array of base paths to use for finding the class file.
+ *
+ * @return boolean True if the class was loaded, false otherwise.
+ *
+ * @since 3.0.0
+ */
+ private static function _load($class, $lookup)
+ {
+ // Split the class name into parts separated by camelCase.
+ $parts = preg_split('/(?<=[a-z0-9])(?=[A-Z])/x', $class);
+ $partsCount = count($parts);
+
+ foreach ($lookup as $base)
+ {
+ // Generate the path based on the class name parts.
+ $path = realpath($base . '/' . implode('/', array_map('strtolower', $parts)) . '.php');
+
+ // Load the file if it exists and is in the lookup path.
+ if (strpos($path, realpath($base)) === 0 && is_file($path))
+ {
+ $found = (bool) include_once $path;
+
+ if ($found)
+ {
+ self::loadAliasFor($class);
+ }
+
+ return $found;
+ }
+
+ // Backwards compatibility patch
+
+ // If there is only one part we want to duplicate that part for generating the path.
+ if ($partsCount === 1)
+ {
+ // Generate the path based on the class name parts.
+ $path = realpath($base . '/' . implode('/', array_map('strtolower', array($parts[0], $parts[0]))) . '.php');
+
+ // Load the file if it exists and is in the lookup path.
+ if (strpos($path, realpath($base)) === 0 && is_file($path))
+ {
+ $found = (bool) include_once $path;
+
+ if ($found)
+ {
+ self::loadAliasFor($class);
+ }
+
+ return $found;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Loads the aliases for the given class.
+ *
+ * @param string $class The class.
+ *
+ * @return void
+ *
+ * @since 3.8.0
+ */
+ private static function loadAliasFor($class)
+ {
+ if (!array_key_exists($class, self::$classAliasesInverse))
+ {
+ return;
+ }
+
+ foreach (self::$classAliasesInverse[$class] as $alias)
+ {
+ // Force auto-load of the alias class
+ class_exists($alias, true);
+ }
+ }
+
+ /**
+ * Strips the first backslash from the given class if present.
+ *
+ * @param string $class The class to strip the first prefix from.
+ *
+ * @return string The striped class name.
+ *
+ * @since 3.8.0
+ */
+ private static function stripFirstBackslash($class)
+ {
+ return $class && $class[0] === '\\' ? substr($class, 1) : $class;
+ }
+}
+
+// Check if jexit is defined first (our unit tests mock this)
+if (!function_exists('jexit'))
+{
+ /**
+ * Global application exit.
+ *
+ * This function provides a single exit point for the platform.
+ *
+ * @param mixed $message Exit code or string. Defaults to zero.
+ *
+ * @return void
+ *
+ * @codeCoverageIgnore
+ * @since 1.7.0
+ */
+ function jexit($message = 0)
+ {
+ exit($message);
+ }
+}
+
+/**
+ * Intelligent file importer.
+ *
+ * @param string $path A dot syntax path.
+ * @param string $base Search this directory for the class.
+ *
+ * @return boolean True on success.
+ *
+ * @since 1.7.0
+ * @deprecated 5.0 Classes should be autoloaded. Use JLoader::registerPrefix() or JLoader::registerNamespace() to register an autoloader for
+ * your files.
+ */
+function jimport($path, $base = null)
+{
+ return JLoader::import($path, $base);
+}
diff --git a/week-03/project/libraries/src/Application/AdminApplication.php b/week-03/project/libraries/src/Application/AdminApplication.php
new file mode 100644
index 0000000..ff6b81c
--- /dev/null
+++ b/week-03/project/libraries/src/Application/AdminApplication.php
@@ -0,0 +1,85 @@
+controllerResolver = $controllerResolver;
+ $this->router = $router;
+
+ // Call the constructor as late as possible (it runs `initialise`).
+ parent::__construct($input, $config, $client, $response);
+ }
+
+ /**
+ * Method to run the application routines.
+ *
+ * @return void
+ */
+ protected function doExecute(): void
+ {
+ $route = $this->router->parseRoute($this->get('uri.route'), $this->input->getMethod());
+
+ // Add variables to the input if not already set
+ foreach ($route->getRouteVariables() as $key => $value)
+ {
+ $this->input->def($key, $value);
+ }
+
+ \call_user_func($this->controllerResolver->resolve($route));
+ }
+}
diff --git a/week-03/project/libraries/src/Application/IdentityAwareTrait.php b/week-03/project/libraries/src/Application/IdentityAwareTrait.php
new file mode 100644
index 0000000..03152ac
--- /dev/null
+++ b/week-03/project/libraries/src/Application/IdentityAwareTrait.php
@@ -0,0 +1,80 @@
+
+ * @license GNU General Public License version 2 or later; see LICENSE
+ */
+
+namespace Joomla\CMS\Application;
+
+\defined('JPATH_PLATFORM') or die;
+
+use Joomla\CMS\User\User;
+use Joomla\CMS\User\UserFactoryInterface;
+
+/**
+ * Trait for application classes which are identity (user) aware
+ *
+ * @since 4.0.0
+ */
+trait IdentityAware
+{
+ /**
+ * The application identity object.
+ *
+ * @var User
+ * @since 4.0.0
+ */
+ protected $identity;
+
+ /**
+ * UserFactoryInterface
+ *
+ * @var UserFactoryInterface
+ * @since 4.0.0
+ */
+ private $userFactory;
+
+ /**
+ * Get the application identity.
+ *
+ * @return User
+ *
+ * @since 4.0.0
+ */
+ public function getIdentity()
+ {
+ return $this->identity;
+ }
+
+ /**
+ * Allows the application to load a custom or default identity.
+ *
+ * @param User $identity An optional identity object. If omitted, a null user object is created.
+ *
+ * @return $this
+ *
+ * @since 4.0.0
+ */
+ public function loadIdentity(User $identity = null)
+ {
+ $this->identity = $identity ?: $this->userFactory->loadUserById(0);
+
+ return $this;
+ }
+
+ /**
+ * Set the user factory to use.
+ *
+ * @param UserFactoryInterface $userFactory The user factory to use
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function setUserFactory(UserFactoryInterface $userFactory)
+ {
+ $this->userFactory = $userFactory;
+ }
+}
diff --git a/week-03/project/libraries/src/Application/SiteApplication.php b/week-03/project/libraries/src/Application/SiteApplication.php
new file mode 100644
index 0000000..ff6b81c
--- /dev/null
+++ b/week-03/project/libraries/src/Application/SiteApplication.php
@@ -0,0 +1,85 @@
+controllerResolver = $controllerResolver;
+ $this->router = $router;
+
+ // Call the constructor as late as possible (it runs `initialise`).
+ parent::__construct($input, $config, $client, $response);
+ }
+
+ /**
+ * Method to run the application routines.
+ *
+ * @return void
+ */
+ protected function doExecute(): void
+ {
+ $route = $this->router->parseRoute($this->get('uri.route'), $this->input->getMethod());
+
+ // Add variables to the input if not already set
+ foreach ($route->getRouteVariables() as $key => $value)
+ {
+ $this->input->def($key, $value);
+ }
+
+ \call_user_func($this->controllerResolver->resolve($route));
+ }
+}
diff --git a/week-03/project/libraries/src/Asset/MixPathPackage.php b/week-03/project/libraries/src/Asset/MixPathPackage.php
new file mode 100644
index 0000000..a1626e7
--- /dev/null
+++ b/week-03/project/libraries/src/Asset/MixPathPackage.php
@@ -0,0 +1,70 @@
+decoratedPackage = $decoratedPackage;
+ }
+
+ /**
+ * Returns an absolute or root-relative public path.
+ *
+ * @param string $path A path
+ *
+ * @return string The public path
+ */
+ public function getUrl($path)
+ {
+ if ($this->isAbsoluteUrl($path))
+ {
+ return $path;
+ }
+
+ $versionedPath = $this->getVersionStrategy()->applyVersion("/$path");
+
+ if ($versionedPath === $path)
+ {
+ return $this->decoratedPackage->getUrl($path);
+ }
+
+ return $this->getBasePath() . ltrim($versionedPath, '/');
+ }
+}
diff --git a/week-03/project/libraries/src/Autoload/ClassLoader.php b/week-03/project/libraries/src/Autoload/ClassLoader.php
new file mode 100644
index 0000000..918028e
--- /dev/null
+++ b/week-03/project/libraries/src/Autoload/ClassLoader.php
@@ -0,0 +1,63 @@
+
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+namespace Joomla\CMS\Autoload;
+
+\defined('_JEXEC') or die;
+
+use Composer\Autoload\ClassLoader as ComposerClassLoader;
+
+/**
+ * Decorate Composer ClassLoader for Joomla!
+ *
+ * For backward compatibility due to class aliasing in the CMS, the loadClass() method was modified to call
+ * the JLoader::applyAliasFor() method.
+ *
+ * @since 3.4
+ */
+class ClassLoader
+{
+ /**
+ * The Composer class loader
+ *
+ * @var ComposerClassLoader
+ * @since 3.4
+ */
+ private $loader;
+
+ /**
+ * Constructor
+ *
+ * @param ComposerClassLoader $loader Composer autoloader
+ *
+ * @since 3.4
+ */
+ public function __construct(ComposerClassLoader $loader)
+ {
+ $this->loader = $loader;
+ }
+
+ /**
+ * Loads the given class or interface.
+ *
+ * @param string $class The name of the class
+ *
+ * @return boolean|null True if loaded, null otherwise
+ *
+ * @since 3.4
+ */
+ public function loadClass($class)
+ {
+ if ($result = $this->loader->loadClass($class))
+ {
+ \JLoader::applyAliasFor($class);
+ }
+
+ return $result;
+ }
+}
diff --git a/week-03/project/libraries/src/Controller/DashboardController.php b/week-03/project/libraries/src/Controller/DashboardController.php
new file mode 100644
index 0000000..58bcc79
--- /dev/null
+++ b/week-03/project/libraries/src/Controller/DashboardController.php
@@ -0,0 +1,62 @@
+view = $view;
+ }
+
+ /**
+ * Execute the controller.
+ *
+ * @return boolean
+ */
+ public function execute(): bool
+ {
+ // Enable browser caching
+ $this->getApplication()->allowCache(true);
+
+ $this->view->setPackage($this->getInput()->getString('package'));
+
+ $this->getApplication()->setResponse(new HtmlResponse($this->view->render()));
+
+ return true;
+ }
+}
diff --git a/week-03/project/libraries/src/Controller/HomepageController.php b/week-03/project/libraries/src/Controller/HomepageController.php
new file mode 100644
index 0000000..58bcc79
--- /dev/null
+++ b/week-03/project/libraries/src/Controller/HomepageController.php
@@ -0,0 +1,62 @@
+view = $view;
+ }
+
+ /**
+ * Execute the controller.
+ *
+ * @return boolean
+ */
+ public function execute(): bool
+ {
+ // Enable browser caching
+ $this->getApplication()->allowCache(true);
+
+ $this->view->setPackage($this->getInput()->getString('package'));
+
+ $this->getApplication()->setResponse(new HtmlResponse($this->view->render()));
+
+ return true;
+ }
+}
diff --git a/week-03/project/libraries/src/Controller/LoginController.php b/week-03/project/libraries/src/Controller/LoginController.php
new file mode 100644
index 0000000..58bcc79
--- /dev/null
+++ b/week-03/project/libraries/src/Controller/LoginController.php
@@ -0,0 +1,62 @@
+view = $view;
+ }
+
+ /**
+ * Execute the controller.
+ *
+ * @return boolean
+ */
+ public function execute(): bool
+ {
+ // Enable browser caching
+ $this->getApplication()->allowCache(true);
+
+ $this->view->setPackage($this->getInput()->getString('package'));
+
+ $this->getApplication()->setResponse(new HtmlResponse($this->view->render()));
+
+ return true;
+ }
+}
diff --git a/week-03/project/libraries/src/Controller/PageController.php b/week-03/project/libraries/src/Controller/PageController.php
new file mode 100644
index 0000000..58bcc79
--- /dev/null
+++ b/week-03/project/libraries/src/Controller/PageController.php
@@ -0,0 +1,62 @@
+view = $view;
+ }
+
+ /**
+ * Execute the controller.
+ *
+ * @return boolean
+ */
+ public function execute(): bool
+ {
+ // Enable browser caching
+ $this->getApplication()->allowCache(true);
+
+ $this->view->setPackage($this->getInput()->getString('package'));
+
+ $this->getApplication()->setResponse(new HtmlResponse($this->view->render()));
+
+ return true;
+ }
+}
diff --git a/week-03/project/libraries/src/Controller/WrongCmsController.php b/week-03/project/libraries/src/Controller/WrongCmsController.php
new file mode 100644
index 0000000..8ab023e
--- /dev/null
+++ b/week-03/project/libraries/src/Controller/WrongCmsController.php
@@ -0,0 +1,38 @@
+getApplication()->allowCache(true);
+
+ $response = new TextResponse("This isn't the CMS you're looking for.", 404);
+
+ $this->getApplication()->setResponse($response);
+
+ return true;
+ }
+}
diff --git a/week-03/project/libraries/src/Date/Date.php b/week-03/project/libraries/src/Date/Date.php
new file mode 100644
index 0000000..c26cc0f
--- /dev/null
+++ b/week-03/project/libraries/src/Date/Date.php
@@ -0,0 +1,485 @@
+
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+namespace Joomla\CMS\Date;
+
+\defined('JPATH_PLATFORM') or die;
+
+use Joomla\CMS\Factory;
+use Joomla\CMS\Language\Text;
+use Joomla\Database\DatabaseDriver;
+
+/**
+ * Date is a class that stores a date and provides logic to manipulate
+ * and render that date in a variety of formats.
+ *
+ * @method Date|bool add(\DateInterval $interval) Adds an amount of days, months, years, hours, minutes and seconds to a Date object.
+ * @method Date|bool sub(\DateInterval $interval) Subtracts an amount of days, months, years, hours, minutes and seconds from a Date object.
+ * @method Date|bool modify(string $modify) Alter the timestamp of this object by incre/decre-menting in a format accepted by strtotime().
+ *
+ * @property-read string $daysinmonth t - Number of days in the given month.
+ * @property-read string $dayofweek N - ISO-8601 numeric representation of the day of the week.
+ * @property-read string $dayofyear z - The day of the year (starting from 0).
+ * @property-read boolean $isleapyear L - Whether it's a leap year.
+ * @property-read string $day d - Day of the month, 2 digits with leading zeros.
+ * @property-read string $hour H - 24-hour format of an hour with leading zeros.
+ * @property-read string $minute i - Minutes with leading zeros.
+ * @property-read string $second s - Seconds with leading zeros.
+ * @property-read string $microsecond u - Microseconds with leading zeros.
+ * @property-read string $month m - Numeric representation of a month, with leading zeros.
+ * @property-read string $ordinal S - English ordinal suffix for the day of the month, 2 characters.
+ * @property-read string $week W - ISO-8601 week number of year, weeks starting on Monday.
+ * @property-read string $year Y - A full numeric representation of a year, 4 digits.
+ *
+ * @since 1.7.0
+ */
+class Date extends \DateTime
+{
+ const DAY_ABBR = "\x021\x03";
+ const DAY_NAME = "\x022\x03";
+ const MONTH_ABBR = "\x023\x03";
+ const MONTH_NAME = "\x024\x03";
+
+ /**
+ * The format string to be applied when using the __toString() magic method.
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ public static $format = 'Y-m-d H:i:s';
+
+ /**
+ * Placeholder for a \DateTimeZone object with GMT as the time zone.
+ *
+ * @var object
+ * @since 1.7.0
+ *
+ * @deprecated 5.0 Without replacement
+ */
+ protected static $gmt;
+
+ /**
+ * Placeholder for a \DateTimeZone object with the default server
+ * time zone as the time zone.
+ *
+ * @var object
+ * @since 1.7.0
+ *
+ * @deprecated 5.0 Without replacement
+ */
+ protected static $stz;
+
+ /**
+ * The \DateTimeZone object for usage in rending dates as strings.
+ *
+ * @var \DateTimeZone
+ * @since 3.0.0
+ */
+ protected $tz;
+
+ /**
+ * Constructor.
+ *
+ * @param string $date String in a format accepted by strtotime(), defaults to "now".
+ * @param mixed $tz Time zone to be used for the date. Might be a string or a DateTimeZone object.
+ *
+ * @since 1.7.0
+ */
+ public function __construct($date = 'now', $tz = null)
+ {
+ // Create the base GMT and server time zone objects.
+ if (empty(self::$gmt) || empty(self::$stz))
+ {
+ // @TODO: This code block stays here only for B/C, can be removed in 5.0
+ self::$gmt = new \DateTimeZone('GMT');
+ self::$stz = new \DateTimeZone(@date_default_timezone_get());
+ }
+
+ // If the time zone object is not set, attempt to build it.
+ if (!($tz instanceof \DateTimeZone))
+ {
+ if (\is_string($tz))
+ {
+ $tz = new \DateTimeZone($tz);
+ }
+ else
+ {
+ $tz = new \DateTimeZone('UTC');
+ }
+ }
+
+ // Backup active time zone
+ $activeTZ = date_default_timezone_get();
+
+ // Force UTC timezone for correct time handling
+ date_default_timezone_set('UTC');
+
+ // If the date is numeric assume a unix timestamp and convert it.
+ $date = is_numeric($date) ? date('c', $date) : $date;
+
+ // Call the DateTime constructor.
+ parent::__construct($date, $tz);
+
+ // Restore previously active timezone
+ date_default_timezone_set($activeTZ);
+
+ // Set the timezone object for access later.
+ $this->tz = $tz;
+ }
+
+ /**
+ * Magic method to access properties of the date given by class to the format method.
+ *
+ * @param string $name The name of the property.
+ *
+ * @return mixed A value if the property name is valid, null otherwise.
+ *
+ * @since 1.7.0
+ */
+ public function __get($name)
+ {
+ $value = null;
+
+ switch ($name)
+ {
+ case 'daysinmonth':
+ $value = $this->format('t', true);
+ break;
+
+ case 'dayofweek':
+ $value = $this->format('N', true);
+ break;
+
+ case 'dayofyear':
+ $value = $this->format('z', true);
+ break;
+
+ case 'isleapyear':
+ $value = (boolean) $this->format('L', true);
+ break;
+
+ case 'day':
+ $value = $this->format('d', true);
+ break;
+
+ case 'hour':
+ $value = $this->format('H', true);
+ break;
+
+ case 'minute':
+ $value = $this->format('i', true);
+ break;
+
+ case 'second':
+ $value = $this->format('s', true);
+ break;
+
+ case 'month':
+ $value = $this->format('m', true);
+ break;
+
+ case 'ordinal':
+ $value = $this->format('S', true);
+ break;
+
+ case 'week':
+ $value = $this->format('W', true);
+ break;
+
+ case 'year':
+ $value = $this->format('Y', true);
+ break;
+
+ default:
+ $trace = debug_backtrace();
+ trigger_error(
+ 'Undefined property via __get(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'],
+ E_USER_NOTICE
+ );
+ }
+
+ return $value;
+ }
+
+ /**
+ * Magic method to render the date object in the format specified in the public
+ * static member Date::$format.
+ *
+ * @return string The date as a formatted string.
+ *
+ * @since 1.7.0
+ */
+ public function __toString()
+ {
+ return (string) parent::format(self::$format);
+ }
+
+ /**
+ * Proxy for new Date().
+ *
+ * @param string $date String in a format accepted by strtotime(), defaults to "now".
+ * @param mixed $tz Time zone to be used for the date.
+ *
+ * @return Date
+ *
+ * @since 1.7.3
+ */
+ public static function getInstance($date = 'now', $tz = null)
+ {
+ return new static($date, $tz);
+ }
+
+ /**
+ * Translates day of week number to a string.
+ *
+ * @param integer $day The numeric day of the week.
+ * @param boolean $abbr Return the abbreviated day string?
+ *
+ * @return string The day of the week.
+ *
+ * @since 1.7.0
+ */
+ public function dayToString($day, $abbr = false)
+ {
+ switch ($day)
+ {
+ case 0:
+ return $abbr ? Text::_('SUN') : Text::_('SUNDAY');
+ case 1:
+ return $abbr ? Text::_('MON') : Text::_('MONDAY');
+ case 2:
+ return $abbr ? Text::_('TUE') : Text::_('TUESDAY');
+ case 3:
+ return $abbr ? Text::_('WED') : Text::_('WEDNESDAY');
+ case 4:
+ return $abbr ? Text::_('THU') : Text::_('THURSDAY');
+ case 5:
+ return $abbr ? Text::_('FRI') : Text::_('FRIDAY');
+ case 6:
+ return $abbr ? Text::_('SAT') : Text::_('SATURDAY');
+ }
+ }
+
+ /**
+ * Gets the date as a formatted string in a local calendar.
+ *
+ * @param string $format The date format specification string (see {@link PHP_MANUAL#date})
+ * @param boolean $local True to return the date string in the local time zone, false to return it in GMT.
+ * @param boolean $translate True to translate localised strings
+ *
+ * @return string The date string in the specified format format.
+ *
+ * @since 1.7.0
+ */
+ public function calendar($format, $local = false, $translate = true)
+ {
+ return $this->format($format, $local, $translate);
+ }
+
+ /**
+ * Gets the date as a formatted string.
+ *
+ * @param string $format The date format specification string (see {@link PHP_MANUAL#date})
+ * @param boolean $local True to return the date string in the local time zone, false to return it in GMT.
+ * @param boolean $translate True to translate localised strings
+ *
+ * @return string The date string in the specified format format.
+ *
+ * @since 1.7.0
+ */
+ #[\ReturnTypeWillChange]
+ public function format($format, $local = false, $translate = true)
+ {
+ if ($translate)
+ {
+ // Do string replacements for date format options that can be translated.
+ $format = preg_replace('/(^|[^\\\])D/', "\\1" . self::DAY_ABBR, $format);
+ $format = preg_replace('/(^|[^\\\])l/', "\\1" . self::DAY_NAME, $format);
+ $format = preg_replace('/(^|[^\\\])M/', "\\1" . self::MONTH_ABBR, $format);
+ $format = preg_replace('/(^|[^\\\])F/', "\\1" . self::MONTH_NAME, $format);
+ }
+
+ // If the returned time should not be local use UTC.
+ if ($local == false)
+ {
+ parent::setTimezone(new \DateTimeZone('UTC'));
+ }
+
+ // Format the date.
+ $return = parent::format($format);
+
+ if ($translate)
+ {
+ // Manually modify the month and day strings in the formatted time.
+ if (strpos($return, self::DAY_ABBR) !== false)
+ {
+ $return = str_replace(self::DAY_ABBR, $this->dayToString(parent::format('w'), true), $return);
+ }
+
+ if (strpos($return, self::DAY_NAME) !== false)
+ {
+ $return = str_replace(self::DAY_NAME, $this->dayToString(parent::format('w')), $return);
+ }
+
+ if (strpos($return, self::MONTH_ABBR) !== false)
+ {
+ $return = str_replace(self::MONTH_ABBR, $this->monthToString(parent::format('n'), true), $return);
+ }
+
+ if (strpos($return, self::MONTH_NAME) !== false)
+ {
+ $return = str_replace(self::MONTH_NAME, $this->monthToString(parent::format('n')), $return);
+ }
+ }
+
+ if ($local == false && $this->tz !== null)
+ {
+ parent::setTimezone($this->tz);
+ }
+
+ return $return;
+ }
+
+ /**
+ * Get the time offset from GMT in hours or seconds.
+ *
+ * @param boolean $hours True to return the value in hours.
+ *
+ * @return float The time offset from GMT either in hours or in seconds.
+ *
+ * @since 1.7.0
+ */
+ public function getOffsetFromGmt($hours = false)
+ {
+ return (float) $hours ? ($this->tz->getOffset($this) / 3600) : $this->tz->getOffset($this);
+ }
+
+ /**
+ * Translates month number to a string.
+ *
+ * @param integer $month The numeric month of the year.
+ * @param boolean $abbr If true, return the abbreviated month string
+ *
+ * @return string The month of the year.
+ *
+ * @since 1.7.0
+ */
+ public function monthToString($month, $abbr = false)
+ {
+ switch ($month)
+ {
+ case 1:
+ return $abbr ? Text::_('JANUARY_SHORT') : Text::_('JANUARY');
+ case 2:
+ return $abbr ? Text::_('FEBRUARY_SHORT') : Text::_('FEBRUARY');
+ case 3:
+ return $abbr ? Text::_('MARCH_SHORT') : Text::_('MARCH');
+ case 4:
+ return $abbr ? Text::_('APRIL_SHORT') : Text::_('APRIL');
+ case 5:
+ return $abbr ? Text::_('MAY_SHORT') : Text::_('MAY');
+ case 6:
+ return $abbr ? Text::_('JUNE_SHORT') : Text::_('JUNE');
+ case 7:
+ return $abbr ? Text::_('JULY_SHORT') : Text::_('JULY');
+ case 8:
+ return $abbr ? Text::_('AUGUST_SHORT') : Text::_('AUGUST');
+ case 9:
+ return $abbr ? Text::_('SEPTEMBER_SHORT') : Text::_('SEPTEMBER');
+ case 10:
+ return $abbr ? Text::_('OCTOBER_SHORT') : Text::_('OCTOBER');
+ case 11:
+ return $abbr ? Text::_('NOVEMBER_SHORT') : Text::_('NOVEMBER');
+ case 12:
+ return $abbr ? Text::_('DECEMBER_SHORT') : Text::_('DECEMBER');
+ }
+ }
+
+ /**
+ * Method to wrap the setTimezone() function and set the internal time zone object.
+ *
+ * @param \DateTimeZone $tz The new \DateTimeZone object.
+ *
+ * @return Date
+ *
+ * @since 1.7.0
+ * @note This method can't be type hinted due to a PHP bug: https://bugs.php.net/bug.php?id=61483
+ */
+ #[\ReturnTypeWillChange]
+ public function setTimezone($tz)
+ {
+ $this->tz = $tz;
+
+ return parent::setTimezone($tz);
+ }
+
+ /**
+ * Gets the date as an ISO 8601 string. IETF RFC 3339 defines the ISO 8601 format
+ * and it can be found at the IETF Web site.
+ *
+ * @param boolean $local True to return the date string in the local time zone, false to return it in GMT.
+ *
+ * @return string The date string in ISO 8601 format.
+ *
+ * @link http://www.ietf.org/rfc/rfc3339.txt
+ * @since 1.7.0
+ */
+ public function toISO8601($local = false)
+ {
+ return $this->format(\DateTimeInterface::RFC3339, $local, false);
+ }
+
+ /**
+ * Gets the date as an SQL datetime string.
+ *
+ * @param boolean $local True to return the date string in the local time zone, false to return it in GMT.
+ * @param DatabaseDriver $db The database driver or null to use Factory::getDbo()
+ *
+ * @return string The date string in SQL datetime format.
+ *
+ * @link http://dev.mysql.com/doc/refman/5.0/en/datetime.html
+ * @since 2.5.0
+ */
+ public function toSql($local = false, DatabaseDriver $db = null)
+ {
+ if ($db === null)
+ {
+ $db = Factory::getDbo();
+ }
+
+ return $this->format($db->getDateFormat(), $local, false);
+ }
+
+ /**
+ * Gets the date as an RFC 822 string. IETF RFC 2822 supercedes RFC 822 and its definition
+ * can be found at the IETF Web site.
+ *
+ * @param boolean $local True to return the date string in the local time zone, false to return it in GMT.
+ *
+ * @return string The date string in RFC 822 format.
+ *
+ * @link http://www.ietf.org/rfc/rfc2822.txt
+ * @since 1.7.0
+ */
+ public function toRFC822($local = false)
+ {
+ return $this->format(\DateTimeInterface::RFC2822, $local, false);
+ }
+
+ /**
+ * Gets the date as UNIX time stamp.
+ *
+ * @return integer The date as a UNIX timestamp.
+ *
+ * @since 1.7.0
+ */
+ public function toUnix()
+ {
+ return (int) parent::format('U');
+ }
+}
diff --git a/week-03/project/libraries/src/EventListener/ErrorSubscriber.php b/week-03/project/libraries/src/EventListener/ErrorSubscriber.php
new file mode 100644
index 0000000..a233439
--- /dev/null
+++ b/week-03/project/libraries/src/EventListener/ErrorSubscriber.php
@@ -0,0 +1,194 @@
+renderer = $renderer;
+ }
+
+ /**
+ * Returns an array of events this subscriber will listen to.
+ *
+ * @return array
+ */
+ public static function getSubscribedEvents(): array
+ {
+ return [
+ ApplicationEvents::ERROR => 'handleWebError',
+ ConsoleEvents::APPLICATION_ERROR => 'handleConsoleError',
+ ];
+ }
+
+ /**
+ * Handle console application errors.
+ *
+ * @param ConsoleApplicationErrorEvent $event Event object
+ *
+ * @return void
+ */
+ public function handleConsoleError(ConsoleApplicationErrorEvent $event): void
+ {
+ $this->logError($event->getError());
+ }
+
+ /**
+ * Handle web application errors.
+ *
+ * @param ApplicationErrorEvent $event Event object
+ *
+ * @return void
+ */
+ public function handleWebError(ApplicationErrorEvent $event): void
+ {
+ /** @var WebApplication $app */
+ $app = $event->getApplication();
+
+ switch (true)
+ {
+ case $event->getError() instanceof MethodNotAllowedException :
+ // Log the error for reference
+ $this->logger->error(
+ sprintf('Route `%s` not supported by method `%s`', $app->get('uri.route'), $app->input->getMethod()),
+ ['exception' => $event->getError()]
+ );
+
+ $this->prepareResponse($event);
+
+ $app->setHeader('Allow', implode(', ', $event->getError()->getAllowedMethods()));
+
+ break;
+
+ case $event->getError() instanceof RouteNotFoundException :
+ // Log the error for reference
+ $this->logger->error(
+ sprintf('Route `%s` not found', $app->get('uri.route')),
+ ['exception' => $event->getError()]
+ );
+
+ $this->prepareResponse($event);
+
+ break;
+
+ default:
+ $this->logError($event->getError());
+
+ $this->prepareResponse($event);
+
+ break;
+ }
+ }
+
+ /**
+ * Log the error.
+ *
+ * @param \Throwable $throwable The error being processed
+ *
+ * @return void
+ */
+ private function logError(\Throwable $throwable): void
+ {
+ $this->logger->error(
+ sprintf('Uncaught Throwable of type %s caught.', \get_class($throwable)),
+ ['exception' => $throwable]
+ );
+ }
+
+ /**
+ * Prepare the response for the event
+ *
+ * @param ApplicationErrorEvent $event Event object
+ *
+ * @return void
+ */
+ private function prepareResponse(ApplicationErrorEvent $event): void
+ {
+ /** @var WebApplication $app */
+ $app = $event->getApplication();
+
+ $app->allowCache(false);
+
+ switch (true)
+ {
+ case $app->input->getString('_format', 'html') === 'json' :
+ case $app->mimeType === 'application/json' :
+ case $app->getResponse() instanceof JsonResponse :
+ $data = [
+ 'code' => $event->getError()->getCode(),
+ 'message' => $event->getError()->getMessage(),
+ 'error' => true,
+ ];
+
+ $response = new JsonResponse($data);
+
+ break;
+
+ default :
+ $response = new HtmlResponse(
+ $this->renderer->render('exception.twig', ['exception' => $event->getError()])
+ );
+
+ break;
+ }
+
+ switch ($event->getError()->getCode())
+ {
+ case 404 :
+ $response = $response->withStatus(404);
+
+ break;
+
+ case 405 :
+ $response = $response->withStatus(405);
+
+ break;
+
+ case 500 :
+ default :
+ $response = $response->withStatus(500);
+
+ break;
+ }
+
+ $app->setResponse($response);
+ }
+}
diff --git a/week-03/project/libraries/src/Factory.php b/week-03/project/libraries/src/Factory.php
new file mode 100644
index 0000000..dbc52d5
--- /dev/null
+++ b/week-03/project/libraries/src/Factory.php
@@ -0,0 +1,850 @@
+
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+namespace Joomla\CMS;
+
+\defined('JPATH_PLATFORM') or die;
+
+use Joomla\CMS\Application\CMSApplicationInterface;
+use Joomla\CMS\Cache\Cache;
+use Joomla\CMS\Cache\CacheControllerFactoryInterface;
+use Joomla\CMS\Client\ClientHelper;
+use Joomla\CMS\Date\Date;
+use Joomla\CMS\Document\Document;
+use Joomla\CMS\Document\FactoryInterface;
+use Joomla\CMS\Filesystem\Stream;
+use Joomla\CMS\Language\Language;
+use Joomla\CMS\Language\LanguageFactoryInterface;
+use Joomla\CMS\Log\Log;
+use Joomla\CMS\Mail\Mail;
+use Joomla\CMS\Mail\MailHelper;
+use Joomla\CMS\Session\Session;
+use Joomla\CMS\User\User;
+use Joomla\Database\DatabaseDriver;
+use Joomla\Database\DatabaseInterface;
+use Joomla\DI\Container;
+use Joomla\Registry\Registry;
+use PHPMailer\PHPMailer\Exception as phpmailerException;
+
+/**
+ * Joomla Platform Factory class.
+ *
+ * @since 1.7.0
+ */
+abstract class Factory
+{
+ /**
+ * Global application object
+ *
+ * @var CMSApplicationInterface
+ * @since 1.7.0
+ */
+ public static $application = null;
+
+ /**
+ * Global cache object
+ *
+ * @var Cache
+ * @since 1.7.0
+ */
+ public static $cache = null;
+
+ /**
+ * Global configuration object
+ *
+ * @var \JConfig
+ * @since 1.7.0
+ * @deprecated 5.0 Use the configuration object within the application
+ */
+ public static $config = null;
+
+ /**
+ * Global container object
+ *
+ * @var Container
+ * @since 4.0.0
+ */
+ public static $container = null;
+
+ /**
+ * Container for Date instances
+ *
+ * @var array
+ * @since 1.7.3
+ */
+ public static $dates = array();
+
+ /**
+ * Global session object
+ *
+ * @var Session
+ * @since 1.7.0
+ * @deprecated 5.0 Use the session service in the DI container
+ */
+ public static $session = null;
+
+ /**
+ * Global language object
+ *
+ * @var Language
+ * @since 1.7.0
+ * @deprecated 5.0 Use the language service in the DI container
+ */
+ public static $language = null;
+
+ /**
+ * Global document object
+ *
+ * @var Document
+ * @since 1.7.0
+ * @deprecated 5.0 Use the document service in the DI container
+ */
+ public static $document = null;
+
+ /**
+ * Global database object
+ *
+ * @var DatabaseDriver
+ * @since 1.7.0
+ * @deprecated 5.0 Use the database service in the DI container
+ */
+ public static $database = null;
+
+ /**
+ * Global mailer object
+ *
+ * @var Mail
+ * @since 1.7.0
+ */
+ public static $mailer = null;
+
+ /**
+ * Get the global application object. When the global application doesn't exist, an exception is thrown.
+ *
+ * @return CMSApplicationInterface object
+ *
+ * @since 1.7.0
+ * @throws \Exception
+ */
+ public static function getApplication()
+ {
+ if (!self::$application)
+ {
+ throw new \Exception('Failed to start application', 500);
+ }
+
+ return self::$application;
+ }
+
+ /**
+ * Get a configuration object
+ *
+ * Returns the global {@link \JConfig} object, only creating it if it doesn't already exist.
+ *
+ * @param string $file The path to the configuration file
+ * @param string $type The type of the configuration file
+ * @param string $namespace The namespace of the configuration file
+ *
+ * @return Registry
+ *
+ * @see Registry
+ * @since 1.7.0
+ * @deprecated 5.0 Use the configuration object within the application.
+ */
+ public static function getConfig($file = null, $type = 'PHP', $namespace = '')
+ {
+ @trigger_error(
+ sprintf(
+ '%s() is deprecated. The configuration object should be read from the application.',
+ __METHOD__
+ ),
+ E_USER_DEPRECATED
+ );
+
+ /**
+ * If there is an application object, fetch the configuration from there.
+ * Check it's not null because LanguagesModel can make it null and if it's null
+ * we would want to re-init it from configuration.php.
+ */
+ if (self::$application && self::$application->getConfig() !== null)
+ {
+ return self::$application->getConfig();
+ }
+
+ if (!self::$config)
+ {
+ if ($file === null)
+ {
+ $file = JPATH_CONFIGURATION . '/configuration.php';
+ }
+
+ self::$config = self::createConfig($file, $type, $namespace);
+ }
+
+ return self::$config;
+ }
+
+ /**
+ * Get a container object
+ *
+ * Returns the global service container object, only creating it if it doesn't already exist.
+ *
+ * This method is only suggested for use in code whose responsibility is to create new services
+ * and needs to be able to resolve the dependencies, and should therefore only be used when the
+ * container is not accessible by other means. Valid uses of this method include:
+ *
+ * - A static `getInstance()` method calling a factory service from the container,
+ * see `Joomla\CMS\Toolbar\Toolbar::getInstance()` as an example
+ * - An application front controller loading and executing the Joomla application class,
+ * see the `cli/joomla.php` file as an example
+ * - Retrieving optional constructor dependencies when not injected into a class during a transitional
+ * period to retain backward compatibility, in this case a deprecation notice should also be emitted to
+ * notify developers of changes needed in their code
+ *
+ * This method is not suggested for use as a one-for-one replacement of static calls, such as
+ * replacing calls to `Factory::getDbo()` with calls to `Factory::getContainer()->get('db')`, code
+ * should be refactored to support dependency injection instead of making this change.
+ *
+ * @return Container
+ *
+ * @since 4.0.0
+ */
+ public static function getContainer(): Container
+ {
+ if (!self::$container)
+ {
+ self::$container = self::createContainer();
+ }
+
+ return self::$container;
+ }
+
+ /**
+ * Get a session object.
+ *
+ * Returns the global {@link Session} object, only creating it if it doesn't already exist.
+ *
+ * @param array $options An array containing session options
+ *
+ * @return Session object
+ *
+ * @see Session
+ * @since 1.7.0
+ * @deprecated 5.0 Load the session service from the dependency injection container or via $app->getSession()
+ */
+ public static function getSession(array $options = array())
+ {
+ @trigger_error(
+ sprintf(
+ '%1$s() is deprecated. Load the session from the dependency injection container or via %2$s::getApplication()->getSession().',
+ __METHOD__,
+ __CLASS__
+ ),
+ E_USER_DEPRECATED
+ );
+
+ return self::getApplication()->getSession();
+ }
+
+ /**
+ * Get a language object.
+ *
+ * Returns the global {@link Language} object, only creating it if it doesn't already exist.
+ *
+ * @return Language object
+ *
+ * @see Language
+ * @since 1.7.0
+ * @deprecated 5.0 Load the language service from the dependency injection container or via $app->getLanguage()
+ */
+ public static function getLanguage()
+ {
+ @trigger_error(
+ sprintf(
+ '%1$s() is deprecated. Load the language from the dependency injection container or via %2$s::getApplication()->getLanguage().',
+ __METHOD__,
+ __CLASS__
+ ),
+ E_USER_DEPRECATED
+ );
+
+ if (!self::$language)
+ {
+ self::$language = self::createLanguage();
+ }
+
+ return self::$language;
+ }
+
+ /**
+ * Get a document object.
+ *
+ * Returns the global {@link \Joomla\CMS\Document\Document} object, only creating it if it doesn't already exist.
+ *
+ * @return Document object
+ *
+ * @see Document
+ * @since 1.7.0
+ * @deprecated 5.0 Load the document service from the dependency injection container or via $app->getDocument()
+ */
+ public static function getDocument()
+ {
+ @trigger_error(
+ sprintf(
+ '%1$s() is deprecated. Load the document from the dependency injection container or via %2$s::getApplication()->getDocument().',
+ __METHOD__,
+ __CLASS__
+ ),
+ E_USER_DEPRECATED
+ );
+
+ if (!self::$document)
+ {
+ self::$document = self::createDocument();
+ }
+
+ return self::$document;
+ }
+
+ /**
+ * Get a user object.
+ *
+ * Returns the global {@link User} object, only creating it if it doesn't already exist.
+ *
+ * @param integer $id The user to load - Can be an integer or string - If string, it is converted to ID automatically.
+ *
+ * @return User object
+ *
+ * @see User
+ * @since 1.7.0
+ * @deprecated 5.0 Load the user service from the dependency injection container or via $app->getIdentity()
+ */
+ public static function getUser($id = null)
+ {
+ @trigger_error(
+ sprintf(
+ '%1$s() is deprecated. Load the user from the dependency injection container or via %2$s::getApplication()->getIdentity().',
+ __METHOD__,
+ __CLASS__
+ ),
+ E_USER_DEPRECATED
+ );
+
+ $instance = self::getApplication()->getSession()->get('user');
+
+ if (\is_null($id))
+ {
+ if (!($instance instanceof User))
+ {
+ $instance = User::getInstance();
+ }
+ }
+ // Check if we have a string as the id or if the numeric id is the current instance
+ elseif (!($instance instanceof User) || \is_string($id) || $instance->id !== $id)
+ {
+ $instance = User::getInstance($id);
+ }
+
+ return $instance;
+ }
+
+ /**
+ * Get a cache object
+ *
+ * Returns the global {@link CacheController} object
+ *
+ * @param string $group The cache group name
+ * @param string $handler The handler to use
+ * @param string $storage The storage method
+ *
+ * @return \Joomla\CMS\Cache\CacheController object
+ *
+ * @see Cache
+ * @since 1.7.0
+ * @deprecated 5.0 Use the cache controller factory instead
+ */
+ public static function getCache($group = '', $handler = 'callback', $storage = null)
+ {
+ @trigger_error(
+ sprintf(
+ '%s() is deprecated. The cache controller should be fetched from the factory.',
+ __METHOD__
+ ),
+ E_USER_DEPRECATED
+ );
+
+ $hash = md5($group . $handler . $storage);
+
+ if (isset(self::$cache[$hash]))
+ {
+ return self::$cache[$hash];
+ }
+
+ $handler = ($handler === 'function') ? 'callback' : $handler;
+
+ $options = array('defaultgroup' => $group);
+
+ if (isset($storage))
+ {
+ $options['storage'] = $storage;
+ }
+
+ $cache = self::getContainer()->get(CacheControllerFactoryInterface::class)->createCacheController($handler, $options);
+
+ self::$cache[$hash] = $cache;
+
+ return self::$cache[$hash];
+ }
+
+ /**
+ * Get a database object.
+ *
+ * Returns the global {@link DatabaseDriver} object, only creating it if it doesn't already exist.
+ *
+ * @return DatabaseDriver
+ *
+ * @see DatabaseDriver
+ * @since 1.7.0
+ * @deprecated 5.0 Load the database service from the dependency injection container
+ */
+ public static function getDbo()
+ {
+ @trigger_error(
+ sprintf(
+ '%1$s() is deprecated. Load the database from the dependency injection container.',
+ __METHOD__
+ ),
+ E_USER_DEPRECATED
+ );
+
+ if (!self::$database)
+ {
+ if (self::getContainer()->has('DatabaseDriver'))
+ {
+ self::$database = self::getContainer()->get('DatabaseDriver');
+ }
+ else
+ {
+ self::$database = self::createDbo();
+ }
+ }
+
+ return self::$database;
+ }
+
+ /**
+ * Get a mailer object.
+ *
+ * Returns the global {@link Mail} object, only creating it if it doesn't already exist.
+ *
+ * @return Mail object
+ *
+ * @see Mail
+ * @since 1.7.0
+ */
+ public static function getMailer()
+ {
+ if (!self::$mailer)
+ {
+ self::$mailer = self::createMailer();
+ }
+
+ $copy = clone self::$mailer;
+
+ return $copy;
+ }
+
+ /**
+ * Return the {@link Date} object
+ *
+ * @param mixed $time The initial time for the Date object
+ * @param mixed $tzOffset The timezone offset.
+ *
+ * @return Date object
+ *
+ * @see Date
+ * @since 1.7.0
+ */
+ public static function getDate($time = 'now', $tzOffset = null)
+ {
+ static $classname;
+ static $mainLocale;
+
+ $language = self::getLanguage();
+ $locale = $language->getTag();
+
+ if (!isset($classname) || $locale != $mainLocale)
+ {
+ // Store the locale for future reference
+ $mainLocale = $locale;
+
+ if ($mainLocale !== false)
+ {
+ $classname = str_replace('-', '_', $mainLocale) . 'Date';
+
+ if (!class_exists($classname))
+ {
+ // The class does not exist, default to Date
+ $classname = 'Joomla\\CMS\\Date\\Date';
+ }
+ }
+ else
+ {
+ // No tag, so default to Date
+ $classname = 'Joomla\\CMS\\Date\\Date';
+ }
+ }
+
+ $key = $time . '-' . ($tzOffset instanceof \DateTimeZone ? $tzOffset->getName() : (string) $tzOffset);
+
+ if (!isset(self::$dates[$classname][$key]))
+ {
+ self::$dates[$classname][$key] = new $classname($time, $tzOffset);
+ }
+
+ $date = clone self::$dates[$classname][$key];
+
+ return $date;
+ }
+
+ /**
+ * Create a configuration object
+ *
+ * @param string $file The path to the configuration file.
+ * @param string $type The type of the configuration file.
+ * @param string $namespace The namespace of the configuration file.
+ *
+ * @return Registry
+ *
+ * @see Registry
+ * @since 1.7.0
+ * @deprecated 5.0 Use the configuration object within the application.
+ */
+ protected static function createConfig($file, $type = 'PHP', $namespace = '')
+ {
+ @trigger_error(
+ sprintf(
+ '%s() is deprecated. The configuration object should be read from the application.',
+ __METHOD__
+ ),
+ E_USER_DEPRECATED
+ );
+
+ if (is_file($file))
+ {
+ include_once $file;
+ }
+
+ // Create the registry with a default namespace of config
+ $registry = new Registry;
+
+ // Sanitize the namespace.
+ $namespace = ucfirst((string) preg_replace('/[^A-Z_]/i', '', $namespace));
+
+ // Build the config name.
+ $name = 'JConfig' . $namespace;
+
+ // Handle the PHP configuration type.
+ if ($type === 'PHP' && class_exists($name))
+ {
+ // Create the JConfig object
+ $config = new $name;
+
+ // Load the configuration values into the registry
+ $registry->loadObject($config);
+ }
+
+ return $registry;
+ }
+
+ /**
+ * Create a container object
+ *
+ * @return Container
+ *
+ * @since 4.0.0
+ */
+ protected static function createContainer(): Container
+ {
+ $container = (new Container)
+ ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Application)
+ ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Authentication)
+ ->registerServiceProvider(new \Joomla\CMS\Service\Provider\CacheController)
+ ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Config)
+ ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Console)
+ ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Database)
+ ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Dispatcher)
+ ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Document)
+ ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Form)
+ ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Logger)
+ ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Language)
+ ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Menu)
+ ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Pathway)
+ ->registerServiceProvider(new \Joomla\CMS\Service\Provider\HTMLRegistry)
+ ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Session)
+ ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Toolbar)
+ ->registerServiceProvider(new \Joomla\CMS\Service\Provider\WebAssetRegistry)
+ ->registerServiceProvider(new \Joomla\CMS\Service\Provider\ApiRouter)
+ ->registerServiceProvider(new \Joomla\CMS\Service\Provider\User);
+
+ return $container;
+ }
+
+ /**
+ * Create a database object
+ *
+ * @return DatabaseDriver
+ *
+ * @see DatabaseDriver
+ * @since 1.7.0
+ * @deprecated 5.0 Use the database service in the DI container
+ */
+ protected static function createDbo()
+ {
+ @trigger_error(
+ sprintf(
+ '%1$s() is deprecated, register a service provider to create a %2$s instance instead.',
+ __METHOD__,
+ DatabaseInterface::class
+ ),
+ E_USER_DEPRECATED
+ );
+
+ $conf = self::getConfig();
+
+ $host = $conf->get('host');
+ $user = $conf->get('user');
+ $password = $conf->get('password');
+ $database = $conf->get('db');
+ $prefix = $conf->get('dbprefix');
+ $driver = $conf->get('dbtype');
+
+ $options = array('driver' => $driver, 'host' => $host, 'user' => $user, 'password' => $password, 'database' => $database, 'prefix' => $prefix);
+
+ if ((int) $conf->get('dbencryption') !== 0)
+ {
+ $options['ssl'] = [
+ 'enable' => true,
+ 'verify_server_cert' => (bool) $conf->get('dbsslverifyservercert'),
+ ];
+
+ foreach (['cipher', 'ca', 'key', 'cert'] as $value)
+ {
+ $confVal = trim($conf->get('dbssl' . $value, ''));
+
+ if ($confVal !== '')
+ {
+ $options['ssl'][$value] = $confVal;
+ }
+ }
+ }
+
+ try
+ {
+ $db = DatabaseDriver::getInstance($options);
+ }
+ catch (\RuntimeException $e)
+ {
+ if (!headers_sent())
+ {
+ header('HTTP/1.1 500 Internal Server Error');
+ }
+
+ jexit('Database Error: ' . $e->getMessage());
+ }
+
+ return $db;
+ }
+
+ /**
+ * Create a mailer object
+ *
+ * @return Mail object
+ *
+ * @see Mail
+ * @since 1.7.0
+ */
+ protected static function createMailer()
+ {
+ $conf = self::getConfig();
+
+ $smtpauth = ($conf->get('smtpauth') == 0) ? null : 1;
+ $smtpuser = $conf->get('smtpuser');
+ $smtppass = $conf->get('smtppass');
+ $smtphost = $conf->get('smtphost');
+ $smtpsecure = $conf->get('smtpsecure');
+ $smtpport = $conf->get('smtpport');
+ $mailfrom = $conf->get('mailfrom');
+ $fromname = $conf->get('fromname');
+ $mailer = $conf->get('mailer');
+
+ // Create a Mail object
+ $mail = Mail::getInstance();
+
+ // Clean the email address
+ $mailfrom = MailHelper::cleanLine($mailfrom);
+
+ // Set default sender without Reply-to if the mailfrom is a valid address
+ if (MailHelper::isEmailAddress($mailfrom))
+ {
+ // Wrap in try/catch to catch phpmailerExceptions if it is throwing them
+ try
+ {
+ // Check for a false return value if exception throwing is disabled
+ if ($mail->setFrom($mailfrom, MailHelper::cleanLine($fromname), false) === false)
+ {
+ Log::add(__METHOD__ . '() could not set the sender data.', Log::WARNING, 'mail');
+ }
+ }
+ catch (phpmailerException $e)
+ {
+ Log::add(__METHOD__ . '() could not set the sender data.', Log::WARNING, 'mail');
+ }
+ }
+
+ // Default mailer is to use PHP's mail function
+ switch ($mailer)
+ {
+ case 'smtp':
+ $mail->useSmtp($smtpauth, $smtphost, $smtpuser, $smtppass, $smtpsecure, $smtpport);
+ break;
+
+ case 'sendmail':
+ $mail->isSendmail();
+ break;
+
+ default:
+ $mail->isMail();
+ break;
+ }
+
+ return $mail;
+ }
+
+ /**
+ * Create a language object
+ *
+ * @return Language object
+ *
+ * @see Language
+ * @since 1.7.0
+ * @deprecated 5.0 Load the language service from the dependency injection container or via $app->getLanguage()
+ */
+ protected static function createLanguage()
+ {
+ @trigger_error(
+ sprintf(
+ '%1$s() is deprecated. Load the language from the dependency injection container or via %2$s::getApplication()->getLanguage().',
+ __METHOD__,
+ __CLASS__
+ ),
+ E_USER_DEPRECATED
+ );
+
+ $conf = self::getConfig();
+ $locale = $conf->get('language');
+ $debug = $conf->get('debug_lang');
+ $lang = self::getContainer()->get(LanguageFactoryInterface::class)->createLanguage($locale, $debug);
+
+ return $lang;
+ }
+
+ /**
+ * Create a document object
+ *
+ * @return Document object
+ *
+ * @see Document
+ * @since 1.7.0
+ * @deprecated 5.0 Load the document service from the dependency injection container or via $app->getDocument()
+ */
+ protected static function createDocument()
+ {
+ @trigger_error(
+ sprintf(
+ '%1$s() is deprecated. Load the document from the dependency injection container or via %2$s::getApplication()->getDocument().',
+ __METHOD__,
+ __CLASS__
+ ),
+ E_USER_DEPRECATED
+ );
+
+ $lang = self::getLanguage();
+
+ $input = self::getApplication()->input;
+ $type = $input->get('format', 'html', 'cmd');
+
+ $version = new Version;
+
+ $attributes = array(
+ 'charset' => 'utf-8',
+ 'lineend' => 'unix',
+ 'tab' => "\t",
+ 'language' => $lang->getTag(),
+ 'direction' => $lang->isRtl() ? 'rtl' : 'ltr',
+ 'mediaversion' => $version->getMediaVersion(),
+ );
+
+ return self::getContainer()->get(FactoryInterface::class)->createDocument($type, $attributes);
+ }
+
+ /**
+ * Creates a new stream object with appropriate prefix
+ *
+ * @param boolean $usePrefix Prefix the connections for writing
+ * @param boolean $useNetwork Use network if available for writing; use false to disable (e.g. FTP, SCP)
+ * @param string $userAgentSuffix String to append to user agent
+ * @param boolean $maskUserAgent User agent masking (prefix Mozilla)
+ *
+ * @return Stream
+ *
+ * @see Stream
+ * @since 1.7.0
+ */
+ public static function getStream($usePrefix = true, $useNetwork = true, $userAgentSuffix = 'Joomla', $maskUserAgent = false)
+ {
+ // Setup the context; Joomla! UA and overwrite
+ $context = array();
+ $version = new Version;
+
+ // Set the UA for HTTP and overwrite for FTP
+ $context['http']['user_agent'] = $version->getUserAgent($userAgentSuffix, $maskUserAgent);
+ $context['ftp']['overwrite'] = true;
+
+ if ($usePrefix)
+ {
+ $FTPOptions = ClientHelper::getCredentials('ftp');
+ $SCPOptions = ClientHelper::getCredentials('scp');
+
+ if ($FTPOptions['enabled'] == 1 && $useNetwork)
+ {
+ $prefix = 'ftp://' . $FTPOptions['user'] . ':' . $FTPOptions['pass'] . '@' . $FTPOptions['host'];
+ $prefix .= $FTPOptions['port'] ? ':' . $FTPOptions['port'] : '';
+ $prefix .= $FTPOptions['root'];
+ }
+ elseif ($SCPOptions['enabled'] == 1 && $useNetwork)
+ {
+ $prefix = 'ssh2.sftp://' . $SCPOptions['user'] . ':' . $SCPOptions['pass'] . '@' . $SCPOptions['host'];
+ $prefix .= $SCPOptions['port'] ? ':' . $SCPOptions['port'] : '';
+ $prefix .= $SCPOptions['root'];
+ }
+ else
+ {
+ $prefix = JPATH_ROOT . '/';
+ }
+
+ $retval = new Stream($prefix, JPATH_ROOT, $context);
+ }
+ else
+ {
+ $retval = new Stream('', '', $context);
+ }
+
+ return $retval;
+ }
+}
diff --git a/week-03/project/libraries/src/Filter/InputFilter.php b/week-03/project/libraries/src/Filter/InputFilter.php
new file mode 100644
index 0000000..b654049
--- /dev/null
+++ b/week-03/project/libraries/src/Filter/InputFilter.php
@@ -0,0 +1,529 @@
+
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+namespace Joomla\CMS\Filter;
+
+\defined('JPATH_PLATFORM') or die;
+
+use Joomla\CMS\String\PunycodeHelper;
+use Joomla\Filter\InputFilter as BaseInputFilter;
+
+/**
+ * InputFilter is a class for filtering input from any data source
+ *
+ * Forked from the php input filter library by: Daniel Morris
+ * Original Contributors: Gianpaolo Racca, Ghislain Picard, Marco Wandschneider, Chris Tobin and Andrew Eddie.
+ *
+ * @since 1.7.0
+ */
+class InputFilter extends BaseInputFilter
+{
+ /**
+ * An array containing a list of extensions for files that are typically
+ * executable directly in the webserver context, potentially resulting in code executions
+ *
+ * @since 4.0.0
+ */
+ public const FORBIDDEN_FILE_EXTENSIONS = [
+ 'php', 'phps', 'pht', 'phtml', 'php3', 'php4', 'php5', 'php6', 'php7', 'asp',
+ 'php8', 'phar', 'inc', 'pl', 'cgi', 'fcgi', 'java', 'jar', 'py', 'aspx'
+ ];
+
+ /**
+ * A flag for Unicode Supplementary Characters (4-byte Unicode character) stripping.
+ *
+ * @var integer
+ * @since 3.5
+ */
+ private $stripUSC = 0;
+
+ /**
+ * A container for InputFilter instances.
+ *
+ * @var InputFilter[]
+ * @since 4.0.0
+ */
+ protected static $instances = array();
+ /**
+ * Constructor for inputFilter class. Only first parameter is required.
+ *
+ * @param array $tagsArray List of user-defined tags
+ * @param array $attrArray List of user-defined attributes
+ * @param integer $tagsMethod The constant static::ONLY_ALLOW_DEFINED_TAGS or static::BLOCK_DEFINED_TAGS
+ * @param integer $attrMethod The constant static::ONLY_ALLOW_DEFINED_ATTRIBUTES or static::BLOCK_DEFINED_ATTRIBUTES
+ * @param integer $xssAuto Only auto clean essentials = 0, Allow clean blocked tags/attributes = 1
+ * @param integer $stripUSC Strip 4-byte unicode characters = 1, no strip = 0
+ *
+ * @since 1.7.0
+ */
+ public function __construct($tagsArray = array(), $attrArray = array(), $tagsMethod = 0, $attrMethod = 0, $xssAuto = 1, $stripUSC = 0)
+ {
+ parent::__construct($tagsArray, $attrArray, $tagsMethod, $attrMethod, $xssAuto);
+
+ // Assign member variables
+ $this->stripUSC = $stripUSC;
+ }
+
+ /**
+ * Returns an input filter object, only creating it if it doesn't already exist.
+ *
+ * @param array $tagsArray List of user-defined tags
+ * @param array $attrArray List of user-defined attributes
+ * @param integer $tagsMethod The constant static::ONLY_ALLOW_DEFINED_TAGS or static::BLOCK_DEFINED_TAGS
+ * @param integer $attrMethod The constant static::ONLY_ALLOW_DEFINED_ATTRIBUTES or static::BLOCK_DEFINED_ATTRIBUTES
+ * @param integer $xssAuto Only auto clean essentials = 0, Allow clean blocked tags/attributes = 1
+ * @param integer $stripUSC Strip 4-byte unicode characters = 1, no strip = 0
+ *
+ * @return InputFilter The InputFilter object.
+ *
+ * @since 1.7.0
+ */
+ public static function getInstance($tagsArray = array(), $attrArray = array(), $tagsMethod = 0, $attrMethod = 0, $xssAuto = 1, $stripUSC = 0)
+ {
+ $sig = md5(serialize(array($tagsArray, $attrArray, $tagsMethod, $attrMethod, $xssAuto)));
+
+ if (empty(self::$instances[$sig]))
+ {
+ self::$instances[$sig] = new InputFilter($tagsArray, $attrArray, $tagsMethod, $attrMethod, $xssAuto, $stripUSC);
+ }
+
+ return self::$instances[$sig];
+ }
+
+ /**
+ * Method to be called by another php script. Processes for XSS and
+ * specified bad code.
+ *
+ * @param mixed $source Input string/array-of-string to be 'cleaned'
+ * @param string $type The return type for the variable:
+ * INT: An integer, or an array of integers,
+ * UINT: An unsigned integer, or an array of unsigned integers,
+ * FLOAT: A floating point number, or an array of floating point numbers,
+ * BOOLEAN: A boolean value,
+ * WORD: A string containing A-Z or underscores only (not case sensitive),
+ * ALNUM: A string containing A-Z or 0-9 only (not case sensitive),
+ * CMD: A string containing A-Z, 0-9, underscores, periods or hyphens (not case sensitive),
+ * BASE64: A string containing A-Z, 0-9, forward slashes, plus or equals (not case sensitive),
+ * STRING: A fully decoded and sanitised string (default),
+ * HTML: A sanitised string,
+ * ARRAY: An array,
+ * PATH: A sanitised file path, or an array of sanitised file paths,
+ * TRIM: A string trimmed from normal, non-breaking and multibyte spaces
+ * USERNAME: Do not use (use an application specific filter),
+ * RAW: The raw string is returned with no filtering,
+ * unknown: An unknown filter will act like STRING. If the input is an array it will return an
+ * array of fully decoded and sanitised strings.
+ *
+ * @return mixed 'Cleaned' version of input parameter
+ *
+ * @since 1.7.0
+ */
+ public function clean($source, $type = 'string')
+ {
+ // Strip Unicode Supplementary Characters when requested to do so
+ if ($this->stripUSC)
+ {
+ // Alternatively: preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xE2\xAF\x91", $source) but it'd be slower.
+ $source = $this->stripUSC($source);
+ }
+
+ return parent::clean($source, $type);
+ }
+
+ /**
+ * Function to punyencode utf8 mail when saving content
+ *
+ * @param string $text The strings to encode
+ *
+ * @return string The punyencoded mail
+ *
+ * @since 3.5
+ */
+ public function emailToPunycode($text)
+ {
+ $pattern = '/(("mailto:)+[\w\.\-\+]+\@[^"?]+\.+[^."?]+("|\?))/';
+
+ if (preg_match_all($pattern, $text, $matches))
+ {
+ foreach ($matches[0] as $match)
+ {
+ $match = (string) str_replace(array('?', '"'), '', $match);
+ $text = (string) str_replace($match, PunycodeHelper::emailToPunycode($match), $text);
+ }
+ }
+
+ return $text;
+ }
+
+ /**
+ * Checks an uploaded for suspicious naming and potential PHP contents which could indicate a hacking attempt.
+ *
+ * The options you can define are:
+ * null_byte Prevent files with a null byte in their name (buffer overflow attack)
+ * forbidden_extensions Do not allow these strings anywhere in the file's extension
+ * php_tag_in_content Do not allow ` true,
+
+ // Forbidden string in extension (e.g. php matched .php, .xxx.php, .php.xxx and so on)
+ 'forbidden_extensions' => self::FORBIDDEN_FILE_EXTENSIONS,
+
+ // true,
+
+ // tag in file contents
+ 'shorttag_in_content' => true,
+
+ // __HALT_COMPILER()
+ 'phar_stub_in_content' => true,
+
+ // Which file extensions to scan for short tags
+ 'shorttag_extensions' => array(
+ 'inc', 'phps', 'class', 'php3', 'php4', 'php5', 'php6', 'php7', 'php8', 'txt', 'dat', 'tpl', 'tmpl',
+ ),
+
+ // Forbidden extensions anywhere in the content
+ 'fobidden_ext_in_content' => true,
+
+ // Which file extensions to scan for .php in the content
+ 'php_ext_content_extensions' => array('zip', 'rar', 'tar', 'gz', 'tgz', 'bz2', 'tbz', 'jpa'),
+ );
+
+ $options = array_merge($defaultOptions, $options);
+
+ // Make sure we can scan nested file descriptors
+ $descriptors = $file;
+
+ if (isset($file['name']) && isset($file['tmp_name']))
+ {
+ $descriptors = static::decodeFileData(
+ array(
+ $file['name'],
+ $file['type'],
+ $file['tmp_name'],
+ $file['error'],
+ $file['size'],
+ )
+ );
+ }
+
+ // Handle non-nested descriptors (single files)
+ if (isset($descriptors['name']))
+ {
+ $descriptors = array($descriptors);
+ }
+
+ // Scan all descriptors detected
+ foreach ($descriptors as $fileDescriptor)
+ {
+ if (!isset($fileDescriptor['name']))
+ {
+ // This is a nested descriptor. We have to recurse.
+ if (!static::isSafeFile($fileDescriptor, $options))
+ {
+ return false;
+ }
+
+ continue;
+ }
+
+ $tempNames = $fileDescriptor['tmp_name'];
+ $intendedNames = $fileDescriptor['name'];
+
+ if (!\is_array($tempNames))
+ {
+ $tempNames = array($tempNames);
+ }
+
+ if (!\is_array($intendedNames))
+ {
+ $intendedNames = array($intendedNames);
+ }
+
+ $len = \count($tempNames);
+
+ for ($i = 0; $i < $len; $i++)
+ {
+ $tempName = array_shift($tempNames);
+ $intendedName = array_shift($intendedNames);
+
+ // 1. Null byte check
+ if ($options['null_byte'])
+ {
+ if (strstr($intendedName, "\x00"))
+ {
+ return false;
+ }
+ }
+
+ // 2. PHP-in-extension check (.php, .php.xxx[.yyy[.zzz[...]]], .xxx[.yyy[.zzz[...]]].php)
+ if (!empty($options['forbidden_extensions']))
+ {
+ $explodedName = explode('.', $intendedName);
+ $explodedName = array_reverse($explodedName);
+ array_pop($explodedName);
+ $explodedName = array_map('strtolower', $explodedName);
+
+ /*
+ * DO NOT USE array_intersect HERE! array_intersect expects the two arrays to
+ * be set, i.e. they should have unique values.
+ */
+ foreach ($options['forbidden_extensions'] as $ext)
+ {
+ if (\in_array($ext, $explodedName))
+ {
+ return false;
+ }
+ }
+ }
+
+ // 3. File contents scanner (PHP tag in file contents)
+ if ($options['php_tag_in_content']
+ || $options['shorttag_in_content'] || $options['phar_stub_in_content']
+ || ($options['fobidden_ext_in_content'] && !empty($options['forbidden_extensions'])))
+ {
+ $fp = strlen($tempName) ? @fopen($tempName, 'r') : false;
+
+ if ($fp !== false)
+ {
+ $data = '';
+
+ while (!feof($fp))
+ {
+ $data .= @fread($fp, 131072);
+
+ if ($options['php_tag_in_content'] && stripos($data, ' $v)
+ {
+ $result[$k] = static::decodeFileData(array($data[0][$k], $data[1][$k], $data[2][$k], $data[3][$k], $data[4][$k]));
+ }
+
+ return $result;
+ }
+
+ return array('name' => $data[0], 'type' => $data[1], 'tmp_name' => $data[2], 'error' => $data[3], 'size' => $data[4]);
+ }
+
+ /**
+ * Try to convert to plaintext
+ *
+ * @param string $source The source string.
+ *
+ * @return string Plaintext string
+ *
+ * @since 3.5
+ */
+ protected function decode($source)
+ {
+ static $ttr;
+
+ if (!\is_array($ttr))
+ {
+ // Entity decode
+ $trans_tbl = get_html_translation_table(HTML_ENTITIES, ENT_COMPAT, 'ISO-8859-1');
+
+ foreach ($trans_tbl as $k => $v)
+ {
+ $ttr[$v] = utf8_encode($k);
+ }
+ }
+
+ $source = strtr($source, $ttr);
+
+ // Convert decimal
+ $source = preg_replace_callback(
+ '/(\d+);/m',
+ function ($m) {
+ return utf8_encode(\chr($m[1]));
+ },
+ $source
+ );
+
+ // Convert hex
+ $source = preg_replace_callback(
+ '/([a-f0-9]+);/mi',
+ function ($m) {
+ return utf8_encode(\chr('0x' . $m[1]));
+ },
+ $source
+ );
+
+ return $source;
+ }
+
+ /**
+ * Recursively strip Unicode Supplementary Characters from the source. Not: objects cannot be filtered.
+ *
+ * @param mixed $source The data to filter
+ *
+ * @return mixed The filtered result
+ *
+ * @since 3.5
+ */
+ protected function stripUSC($source)
+ {
+ if (\is_object($source))
+ {
+ return $source;
+ }
+
+ if (\is_array($source))
+ {
+ $filteredArray = array();
+
+ foreach ($source as $k => $v)
+ {
+ $filteredArray[$k] = $this->stripUSC($v);
+ }
+
+ return $filteredArray;
+ }
+
+ return preg_replace('/[\xF0-\xF7].../s', "\xE2\xAF\x91", $source);
+ }
+}
diff --git a/week-03/project/libraries/src/Model/DashboardModel.php b/week-03/project/libraries/src/Model/DashboardModel.php
new file mode 100644
index 0000000..f2213c9
--- /dev/null
+++ b/week-03/project/libraries/src/Model/DashboardModel.php
@@ -0,0 +1,172 @@
+setDb($db);
+ }
+
+ /**
+ * Add a package
+ *
+ * @param string $packageName The package name as registered with Packagist
+ * @param string $displayName The package's display name
+ * @param string $repoName The package's repo name
+ * @param boolean $isStable Flag indicating the package is stable
+ * @param boolean $isDeprecated Flag indicating the package is deprecated
+ * @param boolean $isAbandoned Flag indicating the package is abandoned
+ *
+ * @return void
+ */
+ public function addPackage(string $packageName, string $displayName, string $repoName, bool $isStable, bool $isDeprecated, bool $isAbandoned): void
+ {
+ $db = $this->getDb();
+
+ $data = (object) [
+ 'package' => $packageName,
+ 'display' => $displayName,
+ 'repo' => $repoName,
+ 'stable' => (int) $isStable,
+ 'deprecated' => (int) $isDeprecated,
+ 'abandoned' => (int) $isAbandoned,
+ ];
+
+ $db->insertObject('#__packages', $data);
+ }
+
+ /**
+ * Get the active package data
+ *
+ * @return array
+ */
+ public function getActivePackages(): array
+ {
+ $abandoned = false;
+
+ $db = $this->getDb();
+
+ $query = $db->getQuery(true)
+ ->select('*')
+ ->from($db->quoteName('#__packages'))
+ ->where($db->quoteName('abandoned') . ' = :abandoned')
+ ->bind('abandoned', $abandoned, ParameterType::INTEGER);
+
+ return $db->setQuery($query)->loadObjectList('id');
+ }
+
+ /**
+ * Get a package's data
+ *
+ * @param string $packageName The package to lookup
+ *
+ * @return \stdClass
+ *
+ * @throws \RuntimeException
+ */
+ public function getPackage(string $packageName): \stdClass
+ {
+ $db = $this->getDb();
+
+ $query = $db->getQuery(true)
+ ->select('*')
+ ->from($db->quoteName('#__packages'))
+ ->where($db->quoteName('package') . ' = :package');
+
+ $query->bind('package', $packageName, ParameterType::STRING);
+
+ $package = $db->setQuery($query)->loadObject();
+
+ if (!$package)
+ {
+ throw new \RuntimeException(sprintf('Unable to find release data for the `%s` package', $package->display), 404);
+ }
+
+ return $package;
+ }
+
+ /**
+ * Get the known package names
+ *
+ * @return array
+ */
+ public function getPackageNames(): array
+ {
+ $db = $this->getDb();
+
+ $query = $db->getQuery(true)
+ ->select(['id', 'package'])
+ ->from($db->quoteName('#__packages'));
+
+ return $db->setQuery($query)->loadAssocList('id', 'package');
+ }
+
+ /**
+ * Get the known package data
+ *
+ * @return array
+ */
+ public function getPackages(): array
+ {
+ $db = $this->getDb();
+
+ $query = $db->getQuery(true)
+ ->select('*')
+ ->from($db->quoteName('#__packages'));
+
+ return $db->setQuery($query)->loadObjectList('id');
+ }
+
+ /**
+ * Update a package
+ *
+ * @param integer $packageId The local package ID
+ * @param string $packageName The package name as registered with Packagist
+ * @param string $displayName The package's display name
+ * @param string $repoName The package's repo name
+ * @param boolean $isStable Flag indicating the package is stable
+ * @param boolean $isDeprecated Flag indicating the package is deprecated
+ * @param boolean $isAbandoned Flag indicating the package is abandoned
+ *
+ * @return void
+ */
+ public function updatePackage(int $packageId, string $packageName, string $displayName, string $repoName, bool $isStable, bool $isDeprecated, bool $isAbandoned): void
+ {
+ $db = $this->getDb();
+
+ $data = (object) [
+ 'id' => $packageId,
+ 'package' => $packageName,
+ 'display' => $displayName,
+ 'repo' => $repoName,
+ 'stable' => (int) $isStable,
+ 'deprecated' => (int) $isDeprecated,
+ 'abandoned' => (int) $isAbandoned,
+ ];
+
+ $db->updateObject('#__packages', $data, 'id');
+ }
+}
diff --git a/week-03/project/libraries/src/Model/PageModel.php b/week-03/project/libraries/src/Model/PageModel.php
new file mode 100644
index 0000000..f2213c9
--- /dev/null
+++ b/week-03/project/libraries/src/Model/PageModel.php
@@ -0,0 +1,172 @@
+setDb($db);
+ }
+
+ /**
+ * Add a package
+ *
+ * @param string $packageName The package name as registered with Packagist
+ * @param string $displayName The package's display name
+ * @param string $repoName The package's repo name
+ * @param boolean $isStable Flag indicating the package is stable
+ * @param boolean $isDeprecated Flag indicating the package is deprecated
+ * @param boolean $isAbandoned Flag indicating the package is abandoned
+ *
+ * @return void
+ */
+ public function addPackage(string $packageName, string $displayName, string $repoName, bool $isStable, bool $isDeprecated, bool $isAbandoned): void
+ {
+ $db = $this->getDb();
+
+ $data = (object) [
+ 'package' => $packageName,
+ 'display' => $displayName,
+ 'repo' => $repoName,
+ 'stable' => (int) $isStable,
+ 'deprecated' => (int) $isDeprecated,
+ 'abandoned' => (int) $isAbandoned,
+ ];
+
+ $db->insertObject('#__packages', $data);
+ }
+
+ /**
+ * Get the active package data
+ *
+ * @return array
+ */
+ public function getActivePackages(): array
+ {
+ $abandoned = false;
+
+ $db = $this->getDb();
+
+ $query = $db->getQuery(true)
+ ->select('*')
+ ->from($db->quoteName('#__packages'))
+ ->where($db->quoteName('abandoned') . ' = :abandoned')
+ ->bind('abandoned', $abandoned, ParameterType::INTEGER);
+
+ return $db->setQuery($query)->loadObjectList('id');
+ }
+
+ /**
+ * Get a package's data
+ *
+ * @param string $packageName The package to lookup
+ *
+ * @return \stdClass
+ *
+ * @throws \RuntimeException
+ */
+ public function getPackage(string $packageName): \stdClass
+ {
+ $db = $this->getDb();
+
+ $query = $db->getQuery(true)
+ ->select('*')
+ ->from($db->quoteName('#__packages'))
+ ->where($db->quoteName('package') . ' = :package');
+
+ $query->bind('package', $packageName, ParameterType::STRING);
+
+ $package = $db->setQuery($query)->loadObject();
+
+ if (!$package)
+ {
+ throw new \RuntimeException(sprintf('Unable to find release data for the `%s` package', $package->display), 404);
+ }
+
+ return $package;
+ }
+
+ /**
+ * Get the known package names
+ *
+ * @return array
+ */
+ public function getPackageNames(): array
+ {
+ $db = $this->getDb();
+
+ $query = $db->getQuery(true)
+ ->select(['id', 'package'])
+ ->from($db->quoteName('#__packages'));
+
+ return $db->setQuery($query)->loadAssocList('id', 'package');
+ }
+
+ /**
+ * Get the known package data
+ *
+ * @return array
+ */
+ public function getPackages(): array
+ {
+ $db = $this->getDb();
+
+ $query = $db->getQuery(true)
+ ->select('*')
+ ->from($db->quoteName('#__packages'));
+
+ return $db->setQuery($query)->loadObjectList('id');
+ }
+
+ /**
+ * Update a package
+ *
+ * @param integer $packageId The local package ID
+ * @param string $packageName The package name as registered with Packagist
+ * @param string $displayName The package's display name
+ * @param string $repoName The package's repo name
+ * @param boolean $isStable Flag indicating the package is stable
+ * @param boolean $isDeprecated Flag indicating the package is deprecated
+ * @param boolean $isAbandoned Flag indicating the package is abandoned
+ *
+ * @return void
+ */
+ public function updatePackage(int $packageId, string $packageName, string $displayName, string $repoName, bool $isStable, bool $isDeprecated, bool $isAbandoned): void
+ {
+ $db = $this->getDb();
+
+ $data = (object) [
+ 'id' => $packageId,
+ 'package' => $packageName,
+ 'display' => $displayName,
+ 'repo' => $repoName,
+ 'stable' => (int) $isStable,
+ 'deprecated' => (int) $isDeprecated,
+ 'abandoned' => (int) $isAbandoned,
+ ];
+
+ $db->updateObject('#__packages', $data, 'id');
+ }
+}
diff --git a/week-03/project/libraries/src/Renderer/ApplicationContext.php b/week-03/project/libraries/src/Renderer/ApplicationContext.php
new file mode 100644
index 0000000..9347d04
--- /dev/null
+++ b/week-03/project/libraries/src/Renderer/ApplicationContext.php
@@ -0,0 +1,61 @@
+app = $app;
+ }
+
+ /**
+ * Gets the base path.
+ *
+ * @return string The base path
+ */
+ public function getBasePath()
+ {
+ return rtrim($this->app->get('uri.base.path'), '/');
+ }
+
+ /**
+ * Checks whether the request is secure or not.
+ *
+ * @return boolean
+ */
+ public function isSecure()
+ {
+ if ($this->app instanceof AbstractWebApplication)
+ {
+ return $this->app->isSslConnection();
+ }
+
+ return false;
+ }
+}
diff --git a/week-03/project/libraries/src/Renderer/FrameworkExtension.php b/week-03/project/libraries/src/Renderer/FrameworkExtension.php
new file mode 100644
index 0000000..a3535b0
--- /dev/null
+++ b/week-03/project/libraries/src/Renderer/FrameworkExtension.php
@@ -0,0 +1,62 @@
+ ['html']]),
+ new TwigFunction('url', [FrameworkTwigRuntime::class, 'getRouteUrl']),
+ ];
+ }
+
+ /**
+ * Removes the application root path defined by the constant "JPATH_ROOT"
+ *
+ * @param string $string The string to process
+ *
+ * @return string
+ */
+ public function stripRootPath(string $string): string
+ {
+ return str_replace(JPATH_ROOT . DIRECTORY_SEPARATOR, '', $string);
+ }
+}
diff --git a/week-03/project/libraries/src/Renderer/FrameworkTwigRuntime.php b/week-03/project/libraries/src/Renderer/FrameworkTwigRuntime.php
new file mode 100644
index 0000000..41a8733
--- /dev/null
+++ b/week-03/project/libraries/src/Renderer/FrameworkTwigRuntime.php
@@ -0,0 +1,160 @@
+app = $app;
+ $this->preloadManager = $preloadManager;
+ $this->sriManifestPath = $sriManifestPath;
+ }
+
+ /**
+ * Retrieves the current URI
+ *
+ * @return string
+ */
+ public function getRequestUri(): string
+ {
+ return $this->app->get('uri.request');
+ }
+
+ /**
+ * Get the URI for a route
+ *
+ * @param string $route Route to get the path for
+ *
+ * @return string
+ */
+ public function getRouteUri(string $route = ''): string
+ {
+ return $this->app->get('uri.base.path') . $route;
+ }
+
+ /**
+ * Get the full URL for a route
+ *
+ * @param string $route Route to get the URL for
+ *
+ * @return string
+ */
+ public function getRouteUrl(string $route = ''): string
+ {
+ return $this->app->get('uri.base.host') . $this->getRouteUri($route);
+ }
+
+ /**
+ * Get the SRI attributes for an asset
+ *
+ * @param string $path A public path
+ *
+ * @return string
+ */
+ public function getSriAttributes(string $path): string
+ {
+ if ($this->sriManifestData === null)
+ {
+ if (!file_exists($this->sriManifestPath))
+ {
+ throw new \RuntimeException(sprintf('SRI manifest file "%s" does not exist.', $this->sriManifestPath));
+ }
+
+ $sriManifestContents = file_get_contents($this->sriManifestPath);
+
+ if ($sriManifestContents === false)
+ {
+ throw new \RuntimeException(sprintf('Could not read SRI manifest file "%s".', $this->sriManifestPath));
+ }
+
+ $this->sriManifestData = json_decode($sriManifestContents, true);
+
+ if (0 < json_last_error())
+ {
+ throw new \RuntimeException(sprintf('Error parsing JSON from SRI manifest file "%s" - %s', $this->sriManifestPath, json_last_error_msg()));
+ }
+ }
+
+ $assetKey = "/$path";
+
+ if (!isset($this->sriManifestData[$assetKey]))
+ {
+ return '';
+ }
+
+ $attributes = '';
+
+ foreach ($this->sriManifestData[$assetKey] as $key => $value)
+ {
+ $attributes .= ' ' . $key . '="' . $value . '"';
+ }
+
+ return $attributes;
+ }
+
+ /**
+ * Preload a resource
+ *
+ * @param string $uri The URI for the resource to preload
+ * @param string $linkType The preload method to apply
+ * @param array $attributes The attributes of this link (e.g. "array('as' => true)", "array('pr' => 0.5)")
+ *
+ * @return string
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function preloadAsset(string $uri, string $linkType = 'preload', array $attributes = []): string
+ {
+ $this->preloadManager->link($uri, $linkType, $attributes);
+
+ return $uri;
+ }
+}
diff --git a/week-03/project/libraries/src/Service/AdminApplicationProvider.php b/week-03/project/libraries/src/Service/AdminApplicationProvider.php
new file mode 100644
index 0000000..648b1bf
--- /dev/null
+++ b/week-03/project/libraries/src/Service/AdminApplicationProvider.php
@@ -0,0 +1,748 @@
+share(ConsoleApplication::class, [$this, 'getConsoleApplicationService'], true);
+
+ // This service cannot be protected as it is decorated when the debug bar is available
+ $container->alias(WebApplication::class, AbstractWebApplication::class)
+ ->share(AbstractWebApplication::class, [$this, 'getWebApplicationClassService']);
+
+ /*
+ * Application Helpers and Dependencies
+ */
+
+ $container->alias(Analytics::class, 'analytics')
+ ->share('analytics', [$this, 'getAnalyticsService'], true);
+
+ $container->alias(ContainerLoader::class, LoaderInterface::class)
+ ->share(LoaderInterface::class, [$this, 'getCommandLoaderService'], true);
+
+ // This service cannot be protected as it is decorated when the debug bar is available
+ $container->alias(ContainerControllerResolver::class, ControllerResolverInterface::class)
+ ->share(ControllerResolverInterface::class, [$this, 'getControllerResolverService']);
+
+ $container->alias(Helper::class, 'application.helper')
+ ->share('application.helper', [$this, 'getApplicationHelperService'], true);
+
+ $container->alias(PackagistHelper::class, 'application.helper.packagist')
+ ->share('application.helper.packagist', [$this, 'getApplicationHelperPackagistService'], true);
+
+ $container->share('application.packages', [$this, 'getApplicationPackagesService'], true);
+
+ $container->share(WebClient::class, [$this, 'getWebClientService'], true);
+
+ // This service cannot be protected as it is decorated when the debug bar is available
+ $container->alias(RouterInterface::class, 'application.router')
+ ->alias(Router::class, 'application.router')
+ ->share('application.router', [$this, 'getApplicationRouterService']);
+
+ $container->share(Input::class, [$this, 'getInputClassService'], true);
+
+ /*
+ * Console Commands
+ */
+
+ $container->share(DebugEventDispatcherCommand::class, [$this, 'getDebugEventDispatcherCommandService'], true);
+ $container->share(DebugRouterCommand::class, [$this, 'getDebugRouterCommandService'], true);
+ $container->share(DownloadsCommand::class, [$this, 'getDownloadsCommandService'], true);
+ $container->share(GenerateSriCommand::class, [$this, 'getGenerateSriCommandService'], true);
+ $container->share(PackageSyncCommand::class, [$this, 'getPackageSyncCommandService'], true);
+ $container->share(PackagistSyncCommand::class, [$this, 'getPackagistSyncCommandService'], true);
+ $container->share(ResetCacheCommand::class, [$this, 'getResetCacheCommandService'], true);
+ $container->share(UpdateCommand::class, [$this, 'getUpdateCommandService'], true);
+
+ /*
+ * MVC Layer
+ */
+
+ // Controllers
+ $container->alias(PackageControllerGet::class, 'controller.api.package')
+ ->share('controller.api.package', [$this, 'getControllerApiPackageService'], true);
+
+ $container->alias(StatusControllerGet::class, 'controller.api.status')
+ ->share('controller.api.status', [$this, 'getControllerApiStatusService'], true);
+
+ $container->alias(HomepageController::class, 'controller.homepage')
+ ->share('controller.homepage', [$this, 'getControllerHomepageService'], true);
+
+ $container->alias(PackageController::class, 'controller.package')
+ ->share('controller.package', [$this, 'getControllerPackageService'], true);
+
+ $container->alias(PageController::class, 'controller.page')
+ ->share('controller.page', [$this, 'getControllerPageService'], true);
+
+ $container->alias(StatusController::class, 'controller.status')
+ ->share('controller.status', [$this, 'getControllerStatusService'], true);
+
+ $container->alias(WrongCmsController::class, 'controller.wrong.cms')
+ ->share('controller.wrong.cms', [$this, 'getControllerWrongCmsService'], true);
+
+ // Models
+ $container->alias(PackageModel::class, 'model.package')
+ ->share('model.package', [$this, 'getModelPackageService'], true);
+
+ $container->alias(ReleaseModel::class, 'model.release')
+ ->share('model.release', [$this, 'getModelReleaseService'], true);
+
+ // Views
+ $container->alias(PackageHtmlView::class, 'view.package.html')
+ ->share('view.package.html', [$this, 'getViewPackageHtmlService'], true);
+
+ $container->alias(PackageJsonView::class, 'view.package.json')
+ ->share('view.package.json', [$this, 'getViewPackageJsonService'], true);
+
+ $container->alias(StatusHtmlView::class, 'view.status.html')
+ ->share('view.status.html', [$this, 'getViewStatusHtmlService'], true);
+
+ $container->alias(StatusJsonView::class, 'view.status.json')
+ ->share('view.status.json', [$this, 'getViewStatusJsonService'], true);
+ }
+
+ /**
+ * Get the Analytics class service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return Analytics
+ */
+ public function getAnalyticsService(Container $container)
+ {
+ return new Analytics(true);
+ }
+
+ /**
+ * Get the `application.helper` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return Helper
+ */
+ public function getApplicationHelperService(Container $container): Helper
+ {
+ $helper = new Helper;
+ $helper->setPackages($container->get('application.packages'));
+
+ return $helper;
+ }
+
+ /**
+ * Get the `application.helper.packagist` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return PackagistHelper
+ */
+ public function getApplicationHelperPackagistService(Container $container): PackagistHelper
+ {
+ $helper = new PackagistHelper($container->get(Http::class), $container->get(DatabaseInterface::class));
+ $helper->setPackages($container->get('application.packages'));
+
+ return $helper;
+ }
+
+ /**
+ * Get the `application.packages` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return Registry
+ */
+ public function getApplicationPackagesService(Container $container): Registry
+ {
+ return (new Registry)->loadFile(JPATH_ROOT . '/packages.yml', 'YAML');
+ }
+
+ /**
+ * Get the `application.router` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return RouterInterface
+ */
+ public function getApplicationRouterService(Container $container): RouterInterface
+ {
+ $router = new Router;
+
+ /*
+ * CMS Admin Panels
+ */
+ $router->get(
+ '/administrator',
+ WrongCmsController::class
+ );
+
+ $router->get(
+ '/administrator/*',
+ WrongCmsController::class
+ );
+
+ $router->get(
+ '/wp-admin',
+ WrongCmsController::class
+ );
+
+ $router->get(
+ '/wp-admin/*',
+ WrongCmsController::class
+ );
+
+ $router->get(
+ 'wp-login.php',
+ WrongCmsController::class
+ );
+
+ /*
+ * Web routes
+ */
+ $router->addRoute(new Route(['GET', 'HEAD'], '/', HomepageController::class));
+
+ $router->get(
+ '/status',
+ StatusController::class
+ );
+
+ $router->get(
+ '/:view',
+ PageController::class
+ );
+
+ $router->get(
+ '/status/:package',
+ PackageController::class
+ );
+
+ /*
+ * API routes
+ */
+ $router->get(
+ '/api/v1/packages',
+ StatusControllerGet::class,
+ [],
+ [
+ '_format' => 'json',
+ ]
+ );
+
+ $router->get(
+ '/api/v1/packages/:package',
+ PackageControllerGet::class,
+ [],
+ [
+ '_format' => 'json',
+ ]
+ );
+
+ return $router;
+ }
+
+ /**
+ * Get the LoaderInterface service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return LoaderInterface
+ */
+ public function getCommandLoaderService(Container $container): LoaderInterface
+ {
+ $mapping = [
+ DebugEventDispatcherCommand::getDefaultName() => DebugEventDispatcherCommand::class,
+ DebugRouterCommand::getDefaultName() => DebugRouterCommand::class,
+ DownloadsCommand::getDefaultName() => DownloadsCommand::class,
+ PackageSyncCommand::getDefaultName() => PackageSyncCommand::class,
+ PackagistSyncCommand::getDefaultName() => PackagistSyncCommand::class,
+ GenerateSriCommand::getDefaultName() => GenerateSriCommand::class,
+ ResetCacheCommand::getDefaultName() => ResetCacheCommand::class,
+ UpdateCommand::getDefaultName() => UpdateCommand::class,
+ ];
+
+ return new ContainerLoader($container, $mapping);
+ }
+
+ /**
+ * Get the ConsoleApplication service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return ConsoleApplication
+ */
+ public function getConsoleApplicationService(Container $container): ConsoleApplication
+ {
+ $application = new ConsoleApplication(new ArgvInput, new ConsoleOutput, $container->get('config'));
+
+ $application->setCommandLoader($container->get(LoaderInterface::class));
+ $application->setDispatcher($container->get(DispatcherInterface::class));
+ $application->setLogger($container->get(LoggerInterface::class));
+ $application->setName('Joomla! Framework Website');
+
+ return $application;
+ }
+
+ /**
+ * Get the `controller.api.package` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return PackageControllerGet
+ */
+ public function getControllerApiPackageService(Container $container): PackageControllerGet
+ {
+ $controller = new PackageControllerGet(
+ $container->get(PackageJsonView::class),
+ $container->get(Analytics::class),
+ $container->get(Input::class),
+ $container->get(WebApplication::class)
+ );
+
+ $controller->setLogger($container->get(LoggerInterface::class));
+
+ return $controller;
+ }
+
+ /**
+ * Get the `controller.api.status` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return StatusControllerGet
+ */
+ public function getControllerApiStatusService(Container $container): StatusControllerGet
+ {
+ $controller = new StatusControllerGet(
+ $container->get(StatusJsonView::class),
+ $container->get(Analytics::class),
+ $container->get(Input::class),
+ $container->get(WebApplication::class)
+ );
+
+ $controller->setLogger($container->get(LoggerInterface::class));
+
+ return $controller;
+ }
+
+ /**
+ * Get the `controller.homepage` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return HomepageController
+ */
+ public function getControllerHomepageService(Container $container): HomepageController
+ {
+ return new HomepageController(
+ $container->get(RendererInterface::class),
+ $container->get(Input::class),
+ $container->get(WebApplication::class)
+ );
+ }
+
+ /**
+ * Get the `controller.package` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return PackageController
+ */
+ public function getControllerPackageService(Container $container): PackageController
+ {
+ return new PackageController(
+ $container->get(PackageHtmlView::class),
+ $container->get(Input::class),
+ $container->get(WebApplication::class)
+ );
+ }
+
+ /**
+ * Get the `controller.page` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return PageController
+ */
+ public function getControllerPageService(Container $container): PageController
+ {
+ return new PageController(
+ $container->get(RendererInterface::class),
+ $container->get(Input::class),
+ $container->get(WebApplication::class)
+ );
+ }
+
+ /**
+ * Get the controller resolver service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return ControllerResolverInterface
+ */
+ public function getControllerResolverService(Container $container): ControllerResolverInterface
+ {
+ return new ContainerControllerResolver($container);
+ }
+
+ /**
+ * Get the `controller.status` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return StatusController
+ */
+ public function getControllerStatusService(Container $container): StatusController
+ {
+ return new StatusController(
+ $container->get(StatusHtmlView::class),
+ $container->get(Input::class),
+ $container->get(WebApplication::class)
+ );
+ }
+
+ /**
+ * Get the `controller.wrong.cms` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return WrongCmsController
+ */
+ public function getControllerWrongCmsService(Container $container): WrongCmsController
+ {
+ return new WrongCmsController(
+ $container->get(Input::class),
+ $container->get(WebApplication::class)
+ );
+ }
+
+ /**
+ * Get the DebugEventDispatcherCommand service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return DebugEventDispatcherCommand
+ */
+ public function getDebugEventDispatcherCommandService(Container $container): DebugEventDispatcherCommand
+ {
+ return new DebugEventDispatcherCommand(
+ $container->get(DispatcherInterface::class)
+ );
+ }
+
+ /**
+ * Get the DebugRouterCommand service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return DebugRouterCommand
+ */
+ public function getDebugRouterCommandService(Container $container): DebugRouterCommand
+ {
+ return new DebugRouterCommand(
+ $container->get(Router::class)
+ );
+ }
+
+ /**
+ * Get the DownloadsCommand service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return DownloadsCommand
+ */
+ public function getDownloadsCommandService(Container $container): DownloadsCommand
+ {
+ return new DownloadsCommand(
+ $container->get(PackagistHelper::class)
+ );
+ }
+
+ /**
+ * Get the GenerateSriCommand service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return GenerateSriCommand
+ */
+ public function getGenerateSriCommandService(Container $container): GenerateSriCommand
+ {
+ return new GenerateSriCommand;
+ }
+
+ /**
+ * Get the Input class service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return Input
+ */
+ public function getInputClassService(Container $container): Input
+ {
+ return new Input($_REQUEST);
+ }
+
+ /**
+ * Get the `model.package` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return PackageModel
+ */
+ public function getModelPackageService(Container $container): PackageModel
+ {
+ return new PackageModel($container->get(DatabaseInterface::class));
+ }
+
+ /**
+ * Get the `model.release` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return ReleaseModel
+ */
+ public function getModelReleaseService(Container $container): ReleaseModel
+ {
+ return new ReleaseModel($container->get(DatabaseInterface::class));
+ }
+
+ /**
+ * Get the PackageSyncCommand service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return PackageSyncCommand
+ */
+ public function getPackageSyncCommandService(Container $container): PackageSyncCommand
+ {
+ return new PackageSyncCommand(
+ $container->get(Helper::class),
+ $container->get(PackageModel::class)
+ );
+ }
+
+ /**
+ * Get the PackagistSyncCommand service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return PackagistSyncCommand
+ */
+ public function getPackagistSyncCommandService(Container $container): PackagistSyncCommand
+ {
+ return new PackagistSyncCommand(
+ $container->get(Http::class),
+ $container->get(PackageModel::class),
+ $container->get(ReleaseModel::class)
+ );
+ }
+
+ /**
+ * Get the ResetCacheCommand service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return ResetCacheCommand
+ */
+ public function getResetCacheCommandService(Container $container): ResetCacheCommand
+ {
+ return new ResetCacheCommand(
+ $container->get(TwigRenderer::class),
+ $container->get('config')
+ );
+ }
+
+ /**
+ * Get the UpdateCommand service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return UpdateCommand
+ */
+ public function getUpdateCommandService(Container $container): UpdateCommand
+ {
+ return new UpdateCommand;
+ }
+
+ /**
+ * Get the `view.package.html` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return PackageHtmlView
+ */
+ public function getViewPackageHtmlService(Container $container): PackageHtmlView
+ {
+ $view = new PackageHtmlView(
+ $container->get('model.package'),
+ $container->get('model.release'),
+ $container->get(Helper::class),
+ $container->get('renderer')
+ );
+
+ $view->setLayout('package.twig');
+
+ return $view;
+ }
+
+ /**
+ * Get the `view.package.json` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return PackageJsonView
+ */
+ public function getViewPackageJsonService(Container $container): PackageJsonView
+ {
+ return new PackageJsonView(
+ $container->get('model.package'),
+ $container->get('model.release')
+ );
+ }
+
+ /**
+ * Get the `view.status.html` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return StatusHtmlView
+ */
+ public function getViewStatusHtmlService(Container $container): StatusHtmlView
+ {
+ $view = new StatusHtmlView(
+ $container->get('model.package'),
+ $container->get('model.release'),
+ $container->get('renderer')
+ );
+
+ $view->setLayout('status.twig');
+
+ return $view;
+ }
+
+ /**
+ * Get the `view.status.json` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return StatusJsonView
+ */
+ public function getViewStatusJsonService(Container $container): StatusJsonView
+ {
+ return new StatusJsonView(
+ $container->get('model.package'),
+ $container->get('model.release')
+ );
+ }
+
+ /**
+ * Get the WebApplication class service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return WebApplication
+ */
+ public function getWebApplicationClassService(Container $container): WebApplication
+ {
+ $application = new WebApplication(
+ $container->get(ControllerResolverInterface::class),
+ $container->get(RouterInterface::class),
+ $container->get(Input::class),
+ $container->get('config'),
+ $container->get(WebClient::class)
+ );
+
+ $application->httpVersion = '2';
+
+ // Inject extra services
+ $application->setDispatcher($container->get(DispatcherInterface::class));
+ $application->setLogger($container->get(LoggerInterface::class));
+
+ return $application;
+ }
+
+ /**
+ * Get the web client service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return WebClient
+ */
+ public function getWebClientService(Container $container): WebClient
+ {
+ /** @var Input $input */
+ $input = $container->get(Input::class);
+ $userAgent = $input->server->getString('HTTP_USER_AGENT', '');
+ $acceptEncoding = $input->server->getString('HTTP_ACCEPT_ENCODING', '');
+ $acceptLanguage = $input->server->getString('HTTP_ACCEPT_LANGUAGE', '');
+
+ return new WebClient($userAgent, $acceptEncoding, $acceptLanguage);
+ }
+}
diff --git a/week-03/project/libraries/src/Service/ConfigurationProvider.php b/week-03/project/libraries/src/Service/ConfigurationProvider.php
new file mode 100644
index 0000000..455c294
--- /dev/null
+++ b/week-03/project/libraries/src/Service/ConfigurationProvider.php
@@ -0,0 +1,66 @@
+config = (new Registry)->loadFile($file);
+
+ // Hardcode database driver option
+ $this->config->set('database.driver', 'mysql');
+ }
+
+ /**
+ * Registers the service provider with a DI container.
+ *
+ * @param Container $container The DI container.
+ *
+ * @return void
+ */
+ public function register(Container $container): void
+ {
+ $container->share(
+ 'config',
+ function (): Registry
+ {
+ return $this->config;
+ },
+ true
+ );
+ }
+}
diff --git a/week-03/project/libraries/src/Service/EventProvider.php b/week-03/project/libraries/src/Service/EventProvider.php
new file mode 100644
index 0000000..4fdfa8d
--- /dev/null
+++ b/week-03/project/libraries/src/Service/EventProvider.php
@@ -0,0 +1,74 @@
+alias(Dispatcher::class, DispatcherInterface::class)
+ ->share(DispatcherInterface::class, [$this, 'getDispatcherService']);
+
+ $container->share(ErrorSubscriber::class, [$this, 'getErrorSubscriber'], true)
+ ->tag('event.subscriber', [ErrorSubscriber::class]);
+ }
+
+ /**
+ * Get the DispatcherInterface service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return DispatcherInterface
+ */
+ public function getDispatcherService(Container $container): DispatcherInterface
+ {
+ $dispatcher = new Dispatcher;
+
+ foreach ($container->getTagged('event.subscriber') as $subscriber)
+ {
+ $dispatcher->addSubscriber($subscriber);
+ }
+
+ return $dispatcher;
+ }
+
+ /**
+ * Get the ErrorSubscriber service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return ErrorSubscriber
+ */
+ public function getErrorSubscriber(Container $container): ErrorSubscriber
+ {
+ $subscriber = new ErrorSubscriber($container->get(RendererInterface::class));
+ $subscriber->setLogger($container->get(LoggerInterface::class));
+
+ return $subscriber;
+ }
+}
diff --git a/week-03/project/libraries/src/Service/HttpProvider.php b/week-03/project/libraries/src/Service/HttpProvider.php
new file mode 100644
index 0000000..4758caf
--- /dev/null
+++ b/week-03/project/libraries/src/Service/HttpProvider.php
@@ -0,0 +1,63 @@
+alias(Http::class, 'http')
+ ->share('http', [$this, 'getHttpService'], true);
+
+ $container->alias(HttpFactory::class, 'http.factory')
+ ->share('http.factory', [$this, 'getHttpFactoryService'], true);
+ }
+
+ /**
+ * Get the `http` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return Http
+ */
+ public function getHttpService(Container $container): Http
+ {
+ /** @var HttpFactory $factory */
+ $factory = $container->get('http.factory');
+
+ return $factory->getHttp();
+ }
+
+ /**
+ * Get the `http.factory` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return HttpFactory
+ */
+ public function getHttpFactoryService(Container $container): HttpFactory
+ {
+ return new HttpFactory;
+ }
+}
diff --git a/week-03/project/libraries/src/Service/LoggingProvider.php b/week-03/project/libraries/src/Service/LoggingProvider.php
new file mode 100644
index 0000000..e479906
--- /dev/null
+++ b/week-03/project/libraries/src/Service/LoggingProvider.php
@@ -0,0 +1,131 @@
+share('monolog.handler.application', [$this, 'getMonologHandlerApplicationService'], true);
+
+ /*
+ * Monolog Processors
+ */
+ $container->share('monolog.processor.psr3', [$this, 'getMonologProcessorPsr3Service'], true);
+ $container->share('monolog.processor.web', [$this, 'getMonologProcessorWebService'], true);
+
+ /*
+ * Application Loggers
+ */
+ $container->share('monolog.logger.application.cli', [$this, 'getMonologLoggerApplicationCliService'], true);
+ $container->share('monolog.logger.application.web', [$this, 'getMonologLoggerApplicationWebService'], true);
+ }
+
+ /**
+ * Get the `monolog.handler.application` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return StreamHandler
+ */
+ public function getMonologHandlerApplicationService(Container $container): StreamHandler
+ {
+ /** @var \Joomla\Registry\Registry $config */
+ $config = $container->get('config');
+
+ $level = strtoupper($config->get('log.application', $config->get('log.level', 'error')));
+
+ return new StreamHandler(JPATH_ROOT . '/logs/framework.log', \constant('\\Monolog\\Logger::' . $level));
+ }
+
+ /**
+ * Get the `monolog.logger.application.cli` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return Logger
+ */
+ public function getMonologLoggerApplicationCliService(Container $container): Logger
+ {
+ return new Logger(
+ 'Framework',
+ [
+ $container->get('monolog.handler.application'),
+ ],
+ [
+ $container->get('monolog.processor.psr3'),
+ ]
+ );
+ }
+
+ /**
+ * Get the `monolog.logger.application.web` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return Logger
+ */
+ public function getMonologLoggerApplicationWebService(Container $container): Logger
+ {
+ return new Logger(
+ 'Framework',
+ [
+ $container->get('monolog.handler.application'),
+ ],
+ [
+ $container->get('monolog.processor.psr3'),
+ $container->get('monolog.processor.web'),
+ ]
+ );
+ }
+
+ /**
+ * Get the `monolog.processor.psr3` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return PsrLogMessageProcessor
+ */
+ public function getMonologProcessorPsr3Service(Container $container): PsrLogMessageProcessor
+ {
+ return new PsrLogMessageProcessor;
+ }
+
+ /**
+ * Get the `monolog.processor.web` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return WebProcessor
+ */
+ public function getMonologProcessorWebService(Container $container): WebProcessor
+ {
+ return new WebProcessor;
+ }
+}
diff --git a/week-03/project/libraries/src/Service/SessionProvider.php b/week-03/project/libraries/src/Service/SessionProvider.php
new file mode 100644
index 0000000..82b06a6
--- /dev/null
+++ b/week-03/project/libraries/src/Service/SessionProvider.php
@@ -0,0 +1,360 @@
+
+ * @license GNU General Public License version 2 or later; see LICENSE
+ */
+
+namespace Joomla\CMS\Service\Provider;
+
+\defined('JPATH_PLATFORM') or die;
+
+use Joomla\CMS\Application\AdministratorApplication;
+use Joomla\CMS\Application\ApplicationHelper;
+use Joomla\CMS\Application\CMSApplicationInterface;
+use Joomla\CMS\Application\ConsoleApplication;
+use Joomla\CMS\Application\SiteApplication;
+use Joomla\CMS\Factory;
+use Joomla\CMS\Installation\Application\InstallationApplication;
+use Joomla\CMS\Session\EventListener\MetadataManagerListener;
+use Joomla\CMS\Session\MetadataManager;
+use Joomla\CMS\Session\SessionFactory;
+use Joomla\CMS\Session\SessionManager;
+use Joomla\CMS\Session\Storage\JoomlaStorage;
+use Joomla\Database\DatabaseInterface;
+use Joomla\DI\Container;
+use Joomla\DI\Exception\DependencyResolutionException;
+use Joomla\DI\ServiceProviderInterface;
+use Joomla\Event\DispatcherInterface;
+use Joomla\Event\LazyServiceEventListener;
+use Joomla\Event\Priority;
+use Joomla\Registry\Registry;
+use Joomla\Session\HandlerInterface;
+use Joomla\Session\SessionEvents;
+use Joomla\Session\SessionInterface;
+use Joomla\Session\Storage\RuntimeStorage;
+use Joomla\Session\StorageInterface;
+
+/**
+ * Service provider for the application's session dependency
+ *
+ * @since 4.0.0
+ */
+class Session implements ServiceProviderInterface
+{
+ /**
+ * Registers the service provider with a DI container.
+ *
+ * @param Container $container The DI container.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function register(Container $container)
+ {
+ $container->share(
+ 'session.web.administrator',
+ function (Container $container)
+ {
+ /** @var Registry $config */
+ $config = $container->get('config');
+ $app = Factory::getApplication();
+
+ // Generate a session name.
+ $name = ApplicationHelper::getHash($config->get('session_name', AdministratorApplication::class));
+
+ // Calculate the session lifetime.
+ $lifetime = $config->get('lifetime') ? $config->get('lifetime') * 60 : 900;
+
+ // Initialize the options for the Session object.
+ $options = [
+ 'name' => $name,
+ 'expire' => $lifetime,
+ ];
+
+ if ($config->get('force_ssl') >= 1)
+ {
+ $options['force_ssl'] = true;
+ }
+
+ $handler = $container->get('session.factory')->createSessionHandler($options);
+
+ if (!$container->has('session.handler'))
+ {
+ $this->registerSessionHandlerAsService($container, $handler);
+ }
+
+ return $this->buildSession(
+ new JoomlaStorage($app->input, $handler),
+ $app,
+ $container->get(DispatcherInterface::class),
+ $options
+ );
+ },
+ true
+ );
+
+ $container->share(
+ 'session.web.installation',
+ function (Container $container)
+ {
+ /** @var Registry $config */
+ $config = $container->get('config');
+ $app = Factory::getApplication();
+
+ /**
+ * Session handler for the session is always filesystem so it doesn't flip to the database after
+ * configuration.php has been written to
+ */
+ $config->set('session_handler', 'filesystem');
+
+ /**
+ * Generate a session name - unlike all the other apps we don't have either a secret or a session name
+ * (that's not the app name) until we complete installation which then leads to us dropping things like
+ * language preferences after installation as the app refreshes.
+ */
+ $name = md5(serialize(JPATH_ROOT . InstallationApplication::class));
+
+ // Calculate the session lifetime.
+ $lifetime = $config->get('lifetime') ? $config->get('lifetime') * 60 : 900;
+
+ // Initialize the options for the Session object.
+ $options = [
+ 'name' => $name,
+ 'expire' => $lifetime,
+ ];
+
+ $handler = $container->get('session.factory')->createSessionHandler($options);
+
+ if (!$container->has('session.handler'))
+ {
+ $this->registerSessionHandlerAsService($container, $handler);
+ }
+
+ return $this->buildSession(
+ new JoomlaStorage($app->input, $handler),
+ $app,
+ $container->get(DispatcherInterface::class),
+ $options
+ );
+ },
+ true
+ );
+
+ $container->share(
+ 'session.web.site',
+ function (Container $container)
+ {
+ /** @var Registry $config */
+ $config = $container->get('config');
+ $app = Factory::getApplication();
+
+ // Generate a session name.
+ $name = ApplicationHelper::getHash($config->get('session_name', SiteApplication::class));
+
+ // Calculate the session lifetime.
+ $lifetime = $config->get('lifetime') ? $config->get('lifetime') * 60 : 900;
+
+ // Initialize the options for the Session object.
+ $options = [
+ 'name' => $name,
+ 'expire' => $lifetime,
+ ];
+
+ if ($config->get('force_ssl') == 2)
+ {
+ $options['force_ssl'] = true;
+ }
+
+ $handler = $container->get('session.factory')->createSessionHandler($options);
+
+ if (!$container->has('session.handler'))
+ {
+ $this->registerSessionHandlerAsService($container, $handler);
+ }
+
+ return $this->buildSession(
+ new JoomlaStorage($app->input, $handler),
+ $app,
+ $container->get(DispatcherInterface::class),
+ $options
+ );
+ },
+ true
+ );
+
+ $container->share(
+ 'session.cli',
+ function (Container $container)
+ {
+ /** @var Registry $config */
+ $config = $container->get('config');
+ $app = Factory::getApplication();
+
+ // Generate a session name.
+ $name = ApplicationHelper::getHash($config->get('session_name', ConsoleApplication::class));
+
+ // Calculate the session lifetime.
+ $lifetime = $config->get('lifetime') ? $config->get('lifetime') * 60 : 900;
+
+ // Initialize the options for the Session object.
+ $options = [
+ 'name' => $name,
+ 'expire' => $lifetime,
+ ];
+
+ // Unlike the web apps, we will only toggle the force SSL setting based on it being enabled and not based on client
+ if ($config->get('force_ssl') >= 1)
+ {
+ $options['force_ssl'] = true;
+ }
+
+ $handler = $container->get('session.factory')->createSessionHandler($options);
+
+ if (!$container->has('session.handler'))
+ {
+ $this->registerSessionHandlerAsService($container, $handler);
+ }
+
+ return $this->buildSession(
+ new RuntimeStorage,
+ $app,
+ $container->get(DispatcherInterface::class),
+ $options
+ );
+ },
+ true
+ );
+
+ $container->alias(SessionFactory::class, 'session.factory')
+ ->share(
+ 'session.factory',
+ function (Container $container)
+ {
+ $factory = new SessionFactory;
+ $factory->setContainer($container);
+
+ return $factory;
+ },
+ true
+ );
+
+ $container->alias(SessionManager::class, 'session.manager')
+ ->share(
+ 'session.manager',
+ function (Container $container)
+ {
+ if (!$container->has('session.handler'))
+ {
+ throw new DependencyResolutionException(
+ 'The "session.handler" service has not been created, make sure you have created the "session" service first.'
+ );
+ }
+
+ return new SessionManager($container->get('session.handler'));
+ },
+ true
+ );
+
+ $container->alias(MetadataManager::class, 'session.metadata_manager')
+ ->share(
+ 'session.metadata_manager',
+ function (Container $container)
+ {
+ /*
+ * Normally we should inject the application as a dependency via $container->get() however there is not
+ * a 'app' or CMSApplicationInterface::class key for the primary application of the request so we need to
+ * rely on the application having been injected to the global Factory otherwise we cannot build the service
+ */
+ if (!Factory::$application)
+ {
+ throw new DependencyResolutionException(
+ sprintf(
+ 'Creating the "session.metadata_manager" service requires %s::$application be initialised.',
+ Factory::class
+ )
+ );
+ }
+
+ return new MetadataManager(Factory::$application, $container->get(DatabaseInterface::class));
+ },
+ true
+ );
+
+ $container->alias(MetadataManagerListener::class, 'session.event_listener.metadata_manager')
+ ->share(
+ 'session.event_listener.metadata_manager',
+ function (Container $container)
+ {
+ return new MetadataManagerListener($container->get(MetadataManager::class), $container->get('config'));
+ },
+ true
+ );
+
+ $listener = new LazyServiceEventListener($container, 'session.event_listener.metadata_manager', 'onAfterSessionStart');
+
+ /** @var DispatcherInterface $dispatcher */
+ $dispatcher = $container->get(DispatcherInterface::class);
+ $dispatcher->addListener(SessionEvents::START, $listener);
+ }
+
+ /**
+ * Build the root session service
+ *
+ * @param StorageInterface $storage The session storage engine.
+ * @param CMSApplicationInterface $app The application instance.
+ * @param DispatcherInterface $dispatcher The event dispatcher.
+ * @param array $options The configured session options.
+ *
+ * @return SessionInterface
+ *
+ * @since 4.0.0
+ */
+ private function buildSession(
+ StorageInterface $storage,
+ CMSApplicationInterface $app,
+ DispatcherInterface $dispatcher,
+ array $options
+ ): SessionInterface
+ {
+ $input = $app->input;
+
+ if (method_exists($app, 'afterSessionStart'))
+ {
+ $dispatcher->addListener(SessionEvents::START, [$app, 'afterSessionStart'], Priority::HIGH);
+ }
+
+ $session = new \Joomla\CMS\Session\Session($storage, $dispatcher, $options);
+
+ return $session;
+ }
+
+ /**
+ * Registers the session handler as a service
+ *
+ * @param Container $container The container to register the service to.
+ * @param \SessionHandlerInterface $sessionHandler The session handler.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ private function registerSessionHandlerAsService(Container $container, \SessionHandlerInterface $sessionHandler): void
+ {
+ // Alias the session handler to the core SessionHandlerInterface for improved autowiring and discoverability
+ $container->alias(\SessionHandlerInterface::class, 'session.handler')
+ ->share(
+ 'session.handler',
+ $sessionHandler,
+ true
+ );
+
+ // If the session handler implements the extended interface, register an alias for that as well
+ if ($sessionHandler instanceof HandlerInterface)
+ {
+ $container->alias(HandlerInterface::class, 'session.handler');
+ }
+ }
+}
diff --git a/week-03/project/libraries/src/Service/SiteApplicationProvider.php b/week-03/project/libraries/src/Service/SiteApplicationProvider.php
new file mode 100644
index 0000000..648b1bf
--- /dev/null
+++ b/week-03/project/libraries/src/Service/SiteApplicationProvider.php
@@ -0,0 +1,748 @@
+share(ConsoleApplication::class, [$this, 'getConsoleApplicationService'], true);
+
+ // This service cannot be protected as it is decorated when the debug bar is available
+ $container->alias(WebApplication::class, AbstractWebApplication::class)
+ ->share(AbstractWebApplication::class, [$this, 'getWebApplicationClassService']);
+
+ /*
+ * Application Helpers and Dependencies
+ */
+
+ $container->alias(Analytics::class, 'analytics')
+ ->share('analytics', [$this, 'getAnalyticsService'], true);
+
+ $container->alias(ContainerLoader::class, LoaderInterface::class)
+ ->share(LoaderInterface::class, [$this, 'getCommandLoaderService'], true);
+
+ // This service cannot be protected as it is decorated when the debug bar is available
+ $container->alias(ContainerControllerResolver::class, ControllerResolverInterface::class)
+ ->share(ControllerResolverInterface::class, [$this, 'getControllerResolverService']);
+
+ $container->alias(Helper::class, 'application.helper')
+ ->share('application.helper', [$this, 'getApplicationHelperService'], true);
+
+ $container->alias(PackagistHelper::class, 'application.helper.packagist')
+ ->share('application.helper.packagist', [$this, 'getApplicationHelperPackagistService'], true);
+
+ $container->share('application.packages', [$this, 'getApplicationPackagesService'], true);
+
+ $container->share(WebClient::class, [$this, 'getWebClientService'], true);
+
+ // This service cannot be protected as it is decorated when the debug bar is available
+ $container->alias(RouterInterface::class, 'application.router')
+ ->alias(Router::class, 'application.router')
+ ->share('application.router', [$this, 'getApplicationRouterService']);
+
+ $container->share(Input::class, [$this, 'getInputClassService'], true);
+
+ /*
+ * Console Commands
+ */
+
+ $container->share(DebugEventDispatcherCommand::class, [$this, 'getDebugEventDispatcherCommandService'], true);
+ $container->share(DebugRouterCommand::class, [$this, 'getDebugRouterCommandService'], true);
+ $container->share(DownloadsCommand::class, [$this, 'getDownloadsCommandService'], true);
+ $container->share(GenerateSriCommand::class, [$this, 'getGenerateSriCommandService'], true);
+ $container->share(PackageSyncCommand::class, [$this, 'getPackageSyncCommandService'], true);
+ $container->share(PackagistSyncCommand::class, [$this, 'getPackagistSyncCommandService'], true);
+ $container->share(ResetCacheCommand::class, [$this, 'getResetCacheCommandService'], true);
+ $container->share(UpdateCommand::class, [$this, 'getUpdateCommandService'], true);
+
+ /*
+ * MVC Layer
+ */
+
+ // Controllers
+ $container->alias(PackageControllerGet::class, 'controller.api.package')
+ ->share('controller.api.package', [$this, 'getControllerApiPackageService'], true);
+
+ $container->alias(StatusControllerGet::class, 'controller.api.status')
+ ->share('controller.api.status', [$this, 'getControllerApiStatusService'], true);
+
+ $container->alias(HomepageController::class, 'controller.homepage')
+ ->share('controller.homepage', [$this, 'getControllerHomepageService'], true);
+
+ $container->alias(PackageController::class, 'controller.package')
+ ->share('controller.package', [$this, 'getControllerPackageService'], true);
+
+ $container->alias(PageController::class, 'controller.page')
+ ->share('controller.page', [$this, 'getControllerPageService'], true);
+
+ $container->alias(StatusController::class, 'controller.status')
+ ->share('controller.status', [$this, 'getControllerStatusService'], true);
+
+ $container->alias(WrongCmsController::class, 'controller.wrong.cms')
+ ->share('controller.wrong.cms', [$this, 'getControllerWrongCmsService'], true);
+
+ // Models
+ $container->alias(PackageModel::class, 'model.package')
+ ->share('model.package', [$this, 'getModelPackageService'], true);
+
+ $container->alias(ReleaseModel::class, 'model.release')
+ ->share('model.release', [$this, 'getModelReleaseService'], true);
+
+ // Views
+ $container->alias(PackageHtmlView::class, 'view.package.html')
+ ->share('view.package.html', [$this, 'getViewPackageHtmlService'], true);
+
+ $container->alias(PackageJsonView::class, 'view.package.json')
+ ->share('view.package.json', [$this, 'getViewPackageJsonService'], true);
+
+ $container->alias(StatusHtmlView::class, 'view.status.html')
+ ->share('view.status.html', [$this, 'getViewStatusHtmlService'], true);
+
+ $container->alias(StatusJsonView::class, 'view.status.json')
+ ->share('view.status.json', [$this, 'getViewStatusJsonService'], true);
+ }
+
+ /**
+ * Get the Analytics class service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return Analytics
+ */
+ public function getAnalyticsService(Container $container)
+ {
+ return new Analytics(true);
+ }
+
+ /**
+ * Get the `application.helper` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return Helper
+ */
+ public function getApplicationHelperService(Container $container): Helper
+ {
+ $helper = new Helper;
+ $helper->setPackages($container->get('application.packages'));
+
+ return $helper;
+ }
+
+ /**
+ * Get the `application.helper.packagist` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return PackagistHelper
+ */
+ public function getApplicationHelperPackagistService(Container $container): PackagistHelper
+ {
+ $helper = new PackagistHelper($container->get(Http::class), $container->get(DatabaseInterface::class));
+ $helper->setPackages($container->get('application.packages'));
+
+ return $helper;
+ }
+
+ /**
+ * Get the `application.packages` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return Registry
+ */
+ public function getApplicationPackagesService(Container $container): Registry
+ {
+ return (new Registry)->loadFile(JPATH_ROOT . '/packages.yml', 'YAML');
+ }
+
+ /**
+ * Get the `application.router` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return RouterInterface
+ */
+ public function getApplicationRouterService(Container $container): RouterInterface
+ {
+ $router = new Router;
+
+ /*
+ * CMS Admin Panels
+ */
+ $router->get(
+ '/administrator',
+ WrongCmsController::class
+ );
+
+ $router->get(
+ '/administrator/*',
+ WrongCmsController::class
+ );
+
+ $router->get(
+ '/wp-admin',
+ WrongCmsController::class
+ );
+
+ $router->get(
+ '/wp-admin/*',
+ WrongCmsController::class
+ );
+
+ $router->get(
+ 'wp-login.php',
+ WrongCmsController::class
+ );
+
+ /*
+ * Web routes
+ */
+ $router->addRoute(new Route(['GET', 'HEAD'], '/', HomepageController::class));
+
+ $router->get(
+ '/status',
+ StatusController::class
+ );
+
+ $router->get(
+ '/:view',
+ PageController::class
+ );
+
+ $router->get(
+ '/status/:package',
+ PackageController::class
+ );
+
+ /*
+ * API routes
+ */
+ $router->get(
+ '/api/v1/packages',
+ StatusControllerGet::class,
+ [],
+ [
+ '_format' => 'json',
+ ]
+ );
+
+ $router->get(
+ '/api/v1/packages/:package',
+ PackageControllerGet::class,
+ [],
+ [
+ '_format' => 'json',
+ ]
+ );
+
+ return $router;
+ }
+
+ /**
+ * Get the LoaderInterface service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return LoaderInterface
+ */
+ public function getCommandLoaderService(Container $container): LoaderInterface
+ {
+ $mapping = [
+ DebugEventDispatcherCommand::getDefaultName() => DebugEventDispatcherCommand::class,
+ DebugRouterCommand::getDefaultName() => DebugRouterCommand::class,
+ DownloadsCommand::getDefaultName() => DownloadsCommand::class,
+ PackageSyncCommand::getDefaultName() => PackageSyncCommand::class,
+ PackagistSyncCommand::getDefaultName() => PackagistSyncCommand::class,
+ GenerateSriCommand::getDefaultName() => GenerateSriCommand::class,
+ ResetCacheCommand::getDefaultName() => ResetCacheCommand::class,
+ UpdateCommand::getDefaultName() => UpdateCommand::class,
+ ];
+
+ return new ContainerLoader($container, $mapping);
+ }
+
+ /**
+ * Get the ConsoleApplication service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return ConsoleApplication
+ */
+ public function getConsoleApplicationService(Container $container): ConsoleApplication
+ {
+ $application = new ConsoleApplication(new ArgvInput, new ConsoleOutput, $container->get('config'));
+
+ $application->setCommandLoader($container->get(LoaderInterface::class));
+ $application->setDispatcher($container->get(DispatcherInterface::class));
+ $application->setLogger($container->get(LoggerInterface::class));
+ $application->setName('Joomla! Framework Website');
+
+ return $application;
+ }
+
+ /**
+ * Get the `controller.api.package` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return PackageControllerGet
+ */
+ public function getControllerApiPackageService(Container $container): PackageControllerGet
+ {
+ $controller = new PackageControllerGet(
+ $container->get(PackageJsonView::class),
+ $container->get(Analytics::class),
+ $container->get(Input::class),
+ $container->get(WebApplication::class)
+ );
+
+ $controller->setLogger($container->get(LoggerInterface::class));
+
+ return $controller;
+ }
+
+ /**
+ * Get the `controller.api.status` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return StatusControllerGet
+ */
+ public function getControllerApiStatusService(Container $container): StatusControllerGet
+ {
+ $controller = new StatusControllerGet(
+ $container->get(StatusJsonView::class),
+ $container->get(Analytics::class),
+ $container->get(Input::class),
+ $container->get(WebApplication::class)
+ );
+
+ $controller->setLogger($container->get(LoggerInterface::class));
+
+ return $controller;
+ }
+
+ /**
+ * Get the `controller.homepage` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return HomepageController
+ */
+ public function getControllerHomepageService(Container $container): HomepageController
+ {
+ return new HomepageController(
+ $container->get(RendererInterface::class),
+ $container->get(Input::class),
+ $container->get(WebApplication::class)
+ );
+ }
+
+ /**
+ * Get the `controller.package` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return PackageController
+ */
+ public function getControllerPackageService(Container $container): PackageController
+ {
+ return new PackageController(
+ $container->get(PackageHtmlView::class),
+ $container->get(Input::class),
+ $container->get(WebApplication::class)
+ );
+ }
+
+ /**
+ * Get the `controller.page` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return PageController
+ */
+ public function getControllerPageService(Container $container): PageController
+ {
+ return new PageController(
+ $container->get(RendererInterface::class),
+ $container->get(Input::class),
+ $container->get(WebApplication::class)
+ );
+ }
+
+ /**
+ * Get the controller resolver service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return ControllerResolverInterface
+ */
+ public function getControllerResolverService(Container $container): ControllerResolverInterface
+ {
+ return new ContainerControllerResolver($container);
+ }
+
+ /**
+ * Get the `controller.status` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return StatusController
+ */
+ public function getControllerStatusService(Container $container): StatusController
+ {
+ return new StatusController(
+ $container->get(StatusHtmlView::class),
+ $container->get(Input::class),
+ $container->get(WebApplication::class)
+ );
+ }
+
+ /**
+ * Get the `controller.wrong.cms` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return WrongCmsController
+ */
+ public function getControllerWrongCmsService(Container $container): WrongCmsController
+ {
+ return new WrongCmsController(
+ $container->get(Input::class),
+ $container->get(WebApplication::class)
+ );
+ }
+
+ /**
+ * Get the DebugEventDispatcherCommand service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return DebugEventDispatcherCommand
+ */
+ public function getDebugEventDispatcherCommandService(Container $container): DebugEventDispatcherCommand
+ {
+ return new DebugEventDispatcherCommand(
+ $container->get(DispatcherInterface::class)
+ );
+ }
+
+ /**
+ * Get the DebugRouterCommand service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return DebugRouterCommand
+ */
+ public function getDebugRouterCommandService(Container $container): DebugRouterCommand
+ {
+ return new DebugRouterCommand(
+ $container->get(Router::class)
+ );
+ }
+
+ /**
+ * Get the DownloadsCommand service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return DownloadsCommand
+ */
+ public function getDownloadsCommandService(Container $container): DownloadsCommand
+ {
+ return new DownloadsCommand(
+ $container->get(PackagistHelper::class)
+ );
+ }
+
+ /**
+ * Get the GenerateSriCommand service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return GenerateSriCommand
+ */
+ public function getGenerateSriCommandService(Container $container): GenerateSriCommand
+ {
+ return new GenerateSriCommand;
+ }
+
+ /**
+ * Get the Input class service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return Input
+ */
+ public function getInputClassService(Container $container): Input
+ {
+ return new Input($_REQUEST);
+ }
+
+ /**
+ * Get the `model.package` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return PackageModel
+ */
+ public function getModelPackageService(Container $container): PackageModel
+ {
+ return new PackageModel($container->get(DatabaseInterface::class));
+ }
+
+ /**
+ * Get the `model.release` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return ReleaseModel
+ */
+ public function getModelReleaseService(Container $container): ReleaseModel
+ {
+ return new ReleaseModel($container->get(DatabaseInterface::class));
+ }
+
+ /**
+ * Get the PackageSyncCommand service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return PackageSyncCommand
+ */
+ public function getPackageSyncCommandService(Container $container): PackageSyncCommand
+ {
+ return new PackageSyncCommand(
+ $container->get(Helper::class),
+ $container->get(PackageModel::class)
+ );
+ }
+
+ /**
+ * Get the PackagistSyncCommand service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return PackagistSyncCommand
+ */
+ public function getPackagistSyncCommandService(Container $container): PackagistSyncCommand
+ {
+ return new PackagistSyncCommand(
+ $container->get(Http::class),
+ $container->get(PackageModel::class),
+ $container->get(ReleaseModel::class)
+ );
+ }
+
+ /**
+ * Get the ResetCacheCommand service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return ResetCacheCommand
+ */
+ public function getResetCacheCommandService(Container $container): ResetCacheCommand
+ {
+ return new ResetCacheCommand(
+ $container->get(TwigRenderer::class),
+ $container->get('config')
+ );
+ }
+
+ /**
+ * Get the UpdateCommand service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return UpdateCommand
+ */
+ public function getUpdateCommandService(Container $container): UpdateCommand
+ {
+ return new UpdateCommand;
+ }
+
+ /**
+ * Get the `view.package.html` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return PackageHtmlView
+ */
+ public function getViewPackageHtmlService(Container $container): PackageHtmlView
+ {
+ $view = new PackageHtmlView(
+ $container->get('model.package'),
+ $container->get('model.release'),
+ $container->get(Helper::class),
+ $container->get('renderer')
+ );
+
+ $view->setLayout('package.twig');
+
+ return $view;
+ }
+
+ /**
+ * Get the `view.package.json` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return PackageJsonView
+ */
+ public function getViewPackageJsonService(Container $container): PackageJsonView
+ {
+ return new PackageJsonView(
+ $container->get('model.package'),
+ $container->get('model.release')
+ );
+ }
+
+ /**
+ * Get the `view.status.html` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return StatusHtmlView
+ */
+ public function getViewStatusHtmlService(Container $container): StatusHtmlView
+ {
+ $view = new StatusHtmlView(
+ $container->get('model.package'),
+ $container->get('model.release'),
+ $container->get('renderer')
+ );
+
+ $view->setLayout('status.twig');
+
+ return $view;
+ }
+
+ /**
+ * Get the `view.status.json` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return StatusJsonView
+ */
+ public function getViewStatusJsonService(Container $container): StatusJsonView
+ {
+ return new StatusJsonView(
+ $container->get('model.package'),
+ $container->get('model.release')
+ );
+ }
+
+ /**
+ * Get the WebApplication class service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return WebApplication
+ */
+ public function getWebApplicationClassService(Container $container): WebApplication
+ {
+ $application = new WebApplication(
+ $container->get(ControllerResolverInterface::class),
+ $container->get(RouterInterface::class),
+ $container->get(Input::class),
+ $container->get('config'),
+ $container->get(WebClient::class)
+ );
+
+ $application->httpVersion = '2';
+
+ // Inject extra services
+ $application->setDispatcher($container->get(DispatcherInterface::class));
+ $application->setLogger($container->get(LoggerInterface::class));
+
+ return $application;
+ }
+
+ /**
+ * Get the web client service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return WebClient
+ */
+ public function getWebClientService(Container $container): WebClient
+ {
+ /** @var Input $input */
+ $input = $container->get(Input::class);
+ $userAgent = $input->server->getString('HTTP_USER_AGENT', '');
+ $acceptEncoding = $input->server->getString('HTTP_ACCEPT_ENCODING', '');
+ $acceptLanguage = $input->server->getString('HTTP_ACCEPT_LANGUAGE', '');
+
+ return new WebClient($userAgent, $acceptEncoding, $acceptLanguage);
+ }
+}
diff --git a/week-03/project/libraries/src/Service/TemplatingProvider.php b/week-03/project/libraries/src/Service/TemplatingProvider.php
new file mode 100644
index 0000000..a474465
--- /dev/null
+++ b/week-03/project/libraries/src/Service/TemplatingProvider.php
@@ -0,0 +1,311 @@
+alias(Packages::class, 'asset.packages')
+ ->share('asset.packages', [$this, 'getAssetPackagesService'], true);
+
+ $container->alias(RendererInterface::class, 'renderer')
+ ->alias(TwigRenderer::class, 'renderer')
+ ->share('renderer', [$this, 'getRendererService'], true);
+
+ $container->alias(CacheInterface::class, 'twig.cache')
+ ->alias(\Twig_CacheInterface::class, 'twig.cache')
+ ->share('twig.cache', [$this, 'getTwigCacheService'], true);
+
+ $container->alias(Environment::class, 'twig.environment')
+ ->alias(\Twig_Environment::class, 'twig.environment')
+ ->share('twig.environment', [$this, 'getTwigEnvironmentService'], true);
+
+ $container->alias(DebugExtension::class, 'twig.extension.debug')
+ ->alias(\Twig_Extension_Debug::class, 'twig.extension.debug')
+ ->share('twig.extension.debug', [$this, 'getTwigExtensionDebugService'], true);
+
+ $container->alias(FrameworkExtension::class, 'twig.extension.framework')
+ ->share('twig.extension.framework', [$this, 'getTwigExtensionFrameworkService'], true);
+
+ // This service cannot be protected as it is decorated when the debug bar is available
+ $container->alias(ProfilerExtension::class, 'twig.extension.profiler')
+ ->alias(\Twig_Extension_Profiler::class, 'twig.extension.profiler')
+ ->share('twig.extension.profiler', [$this, 'getTwigExtensionProfilerService']);
+
+ $container->alias(LoaderInterface::class, 'twig.loader')
+ ->alias(\Twig_LoaderInterface::class, 'twig.loader')
+ ->share('twig.loader', [$this, 'getTwigLoaderService'], true);
+
+ $container->alias(Profile::class, 'twig.profiler.profile')
+ ->alias(\Twig_Profiler_Profile::class, 'twig.profiler.profile')
+ ->share('twig.profiler.profile', [$this, 'getTwigProfilerProfileService'], true);
+
+ $container->alias(FrameworkTwigRuntime::class, 'twig.runtime.framework')
+ ->share('twig.runtime.framework', [$this, 'getTwigRuntimeFrameworkService'], true);
+
+ $container->alias(ContainerRuntimeLoader::class, 'twig.runtime.loader')
+ ->alias(\Twig_ContainerRuntimeLoader::class, 'twig.runtime.loader')
+ ->share('twig.runtime.loader', [$this, 'getTwigRuntimeLoaderService'], true);
+
+ $this->tagTwigExtensions($container);
+ }
+
+ /**
+ * Get the `asset.packages` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return Packages
+ */
+ public function getAssetPackagesService(Container $container): Packages
+ {
+ /** @var AbstractApplication $app */
+ $app = $container->get(AbstractApplication::class);
+
+ $context = new ApplicationContext($app);
+
+ $mediaPath = $app->get('uri.media.path', '/media/');
+
+ $defaultPackage = new PathPackage($mediaPath, new EmptyVersionStrategy, $context);
+
+ $mixStrategy = new MixPathPackage(
+ $defaultPackage,
+ $mediaPath,
+ new JsonManifestVersionStrategy(JPATH_ROOT . '/www/media/mix-manifest.json'),
+ $context
+ );
+
+ return new Packages(
+ $defaultPackage,
+ [
+ 'mix' => $mixStrategy,
+ ]
+ );
+ }
+
+ /**
+ * Get the `renderer` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return RendererInterface
+ */
+ public function getRendererService(Container $container): RendererInterface
+ {
+ return new TwigRenderer($container->get('twig.environment'));
+ }
+
+ /**
+ * Get the `twig.cache` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return \Twig_CacheInterface
+ */
+ public function getTwigCacheService(Container $container): \Twig_CacheInterface
+ {
+ /** @var \Joomla\Registry\Registry $config */
+ $config = $container->get('config');
+
+ // Pull down the renderer config
+ $cacheEnabled = $config->get('template.cache.enabled', false);
+ $cachePath = $config->get('template.cache.path', 'cache/twig');
+ $debug = $config->get('template.debug', false);
+
+ if ($debug === false && $cacheEnabled !== false)
+ {
+ return new FilesystemCache(JPATH_ROOT . '/' . $cachePath);
+ }
+
+ return new NullCache;
+ }
+
+ /**
+ * Get the `twig.environment` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return Environment
+ */
+ public function getTwigEnvironmentService(Container $container): Environment
+ {
+ /** @var \Joomla\Registry\Registry $config */
+ $config = $container->get('config');
+
+ $debug = $config->get('template.debug', false);
+
+ $environment = new Environment(
+ $container->get('twig.loader'),
+ ['debug' => $debug]
+ );
+
+ // Add the runtime loader
+ $environment->addRuntimeLoader($container->get('twig.runtime.loader'));
+
+ // Set up the environment's caching service
+ $environment->setCache($container->get('twig.cache'));
+
+ // Add the Twig extensions
+ $environment->setExtensions($container->getTagged('twig.extension'));
+
+ // Add a global tracking the debug states
+ $environment->addGlobal('appDebug', $config->get('debug', false));
+ $environment->addGlobal('fwDebug', $debug);
+
+ return $environment;
+ }
+
+ /**
+ * Get the `twig.extension.debug` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return DebugExtension
+ */
+ public function getTwigExtensionDebugService(Container $container): DebugExtension
+ {
+ return new DebugExtension;
+ }
+
+ /**
+ * Get the `twig.extension.framework` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return FrameworkExtension
+ */
+ public function getTwigExtensionFrameworkService(Container $container): FrameworkExtension
+ {
+ return new FrameworkExtension;
+ }
+
+ /**
+ * Get the `twig.extension.profiler` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return ProfilerExtension
+ */
+ public function getTwigExtensionProfilerService(Container $container): ProfilerExtension
+ {
+ return new ProfilerExtension($container->get('twig.profiler.profile'));
+ }
+
+ /**
+ * Get the `twig.loader` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return \Twig_LoaderInterface
+ */
+ public function getTwigLoaderService(Container $container): \Twig_LoaderInterface
+ {
+ return new FilesystemLoader([JPATH_TEMPLATES]);
+ }
+
+ /**
+ * Get the `twig.profiler.profile` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return Profile
+ */
+ public function getTwigProfilerProfileService(Container $container): Profile
+ {
+ return new Profile;
+ }
+
+ /**
+ * Get the `twig.runtime.framework` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return FrameworkTwigRuntime
+ */
+ public function getTwigRuntimeFrameworkService(Container $container): FrameworkTwigRuntime
+ {
+ return new FrameworkTwigRuntime(
+ $container->get(AbstractApplication::class),
+ $container->get(PreloadManager::class),
+ JPATH_ROOT . '/www/media/sri-manifest.json'
+ );
+ }
+
+ /**
+ * Get the `twig.runtime.loader` service
+ *
+ * @param Container $container The DI container.
+ *
+ * @return ContainerRuntimeLoader
+ */
+ public function getTwigRuntimeLoaderService(Container $container): ContainerRuntimeLoader
+ {
+ return new ContainerRuntimeLoader($container);
+ }
+
+ /**
+ * Tag services which are Twig extensions
+ *
+ * @param Container $container The DI container.
+ *
+ * @return void
+ */
+ private function tagTwigExtensions(Container $container): void
+ {
+ /** @var \Joomla\Registry\Registry $config */
+ $config = $container->get('config');
+
+ $debug = $config->get('template.debug', false);
+
+ $twigExtensions = ['twig.extension.framework'];
+
+ if ($debug)
+ {
+ $twigExtensions[] = 'twig.extension.debug';
+ }
+
+ $container->tag('twig.extension', $twigExtensions);
+ }
+}
diff --git a/week-03/project/libraries/src/Service/UserProvider.php b/week-03/project/libraries/src/Service/UserProvider.php
new file mode 100644
index 0000000..a05b4ef
--- /dev/null
+++ b/week-03/project/libraries/src/Service/UserProvider.php
@@ -0,0 +1,48 @@
+
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+namespace Joomla\CMS\Service\Provider;
+
+\defined('JPATH_PLATFORM') or die;
+
+use Joomla\CMS\User\UserFactory;
+use Joomla\CMS\User\UserFactoryInterface;
+use Joomla\Database\DatabaseInterface;
+use Joomla\DI\Container;
+use Joomla\DI\ServiceProviderInterface;
+
+/**
+ * Service provider for the user dependency
+ *
+ * @since 4.0.0
+ */
+class User implements ServiceProviderInterface
+{
+ /**
+ * Registers the service provider with a DI container.
+ *
+ * @param Container $container The DI container.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function register(Container $container)
+ {
+ $container->alias('user.factory', UserFactoryInterface::class)
+ ->alias(UserFactory::class, UserFactoryInterface::class)
+ ->share(
+ UserFactoryInterface::class,
+ function (Container $container)
+ {
+ return new UserFactory($container->get(DatabaseInterface::class));
+ },
+ true
+ );
+ }
+}
diff --git a/week-03/project/libraries/src/Session/MetadataManager.php b/week-03/project/libraries/src/Session/MetadataManager.php
new file mode 100644
index 0000000..4c7b620
--- /dev/null
+++ b/week-03/project/libraries/src/Session/MetadataManager.php
@@ -0,0 +1,336 @@
+
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+namespace Joomla\CMS\Session;
+
+\defined('JPATH_PLATFORM') or die;
+
+use Joomla\Application\AbstractApplication;
+use Joomla\CMS\Application\CMSApplication;
+use Joomla\CMS\User\User;
+use Joomla\Database\DatabaseInterface;
+use Joomla\Database\Exception\ExecutionFailureException;
+use Joomla\Database\ParameterType;
+use Joomla\Session\SessionInterface;
+
+/**
+ * Manager for optional session metadata.
+ *
+ * @since 3.8.6
+ * @internal
+ */
+final class MetadataManager
+{
+ /**
+ * Internal variable indicating a session record exists.
+ *
+ * @var integer
+ * @since 4.0.0
+ * @note Once PHP 7.1 is the minimum supported version this should become a private constant
+ */
+ private static $sessionRecordExists = 1;
+
+ /**
+ * Internal variable indicating a session record does not exist.
+ *
+ * @var integer
+ * @since 4.0.0
+ * @note Once PHP 7.1 is the minimum supported version this should become a private constant
+ */
+ private static $sessionRecordDoesNotExist = 0;
+
+ /**
+ * Internal variable indicating an unknown session record statue.
+ *
+ * @var integer
+ * @since 4.0.0
+ * @note Once PHP 7.1 is the minimum supported version this should become a private constant
+ */
+ private static $sessionRecordUnknown = -1;
+
+ /**
+ * Application object.
+ *
+ * @var AbstractApplication
+ * @since 3.8.6
+ */
+ private $app;
+
+ /**
+ * Database driver.
+ *
+ * @var DatabaseInterface
+ * @since 3.8.6
+ */
+ private $db;
+
+ /**
+ * MetadataManager constructor.
+ *
+ * @param AbstractApplication $app Application object.
+ * @param DatabaseInterface $db Database driver.
+ *
+ * @since 3.8.6
+ */
+ public function __construct(AbstractApplication $app, DatabaseInterface $db)
+ {
+ $this->app = $app;
+ $this->db = $db;
+ }
+
+ /**
+ * Create the metadata record if it does not exist.
+ *
+ * @param SessionInterface $session The session to create the metadata record for.
+ * @param User $user The user to associate with the record.
+ *
+ * @return void
+ *
+ * @since 3.8.6
+ * @throws \RuntimeException
+ */
+ public function createRecordIfNonExisting(SessionInterface $session, User $user)
+ {
+ $exists = $this->checkSessionRecordExists($session->getId());
+
+ // Only touch the database if the record does not already exist
+ if ($exists !== self::$sessionRecordExists)
+ {
+ return;
+ }
+
+ $this->createSessionRecord($session, $user);
+ }
+
+ /**
+ * Create the metadata record if it does not exist.
+ *
+ * @param SessionInterface $session The session to create or update the metadata record for.
+ * @param User $user The user to associate with the record.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ * @throws \RuntimeException
+ */
+ public function createOrUpdateRecord(SessionInterface $session, User $user)
+ {
+ $exists = $this->checkSessionRecordExists($session->getId());
+
+ // Do not try to touch the database if we can't determine the record state
+ if ($exists === self::$sessionRecordUnknown)
+ {
+ return;
+ }
+
+ if ($exists === self::$sessionRecordDoesNotExist)
+ {
+ $this->createSessionRecord($session, $user);
+
+ return;
+ }
+
+ $this->updateSessionRecord($session, $user);
+ }
+
+ /**
+ * Delete records with a timestamp prior to the given time.
+ *
+ * @param integer $time The time records should be deleted if expired before.
+ *
+ * @return void
+ *
+ * @since 3.8.6
+ */
+ public function deletePriorTo($time)
+ {
+ $query = $this->db->getQuery(true)
+ ->delete($this->db->quoteName('#__session'))
+ ->where($this->db->quoteName('time') . ' < :time')
+ ->bind(':time', $time, ParameterType::INTEGER);
+
+ $this->db->setQuery($query);
+
+ try
+ {
+ $this->db->execute();
+ }
+ catch (ExecutionFailureException $exception)
+ {
+ // Since garbage collection does not result in a fatal error when run in the session API, we don't allow it here either.
+ }
+ }
+
+ /**
+ * Check if the session record exists
+ *
+ * @param string $sessionId The session ID to check
+ *
+ * @return integer Status value for record presence
+ *
+ * @since 4.0.0
+ */
+ private function checkSessionRecordExists(string $sessionId): int
+ {
+ $query = $this->db->getQuery(true)
+ ->select($this->db->quoteName('session_id'))
+ ->from($this->db->quoteName('#__session'))
+ ->where($this->db->quoteName('session_id') . ' = :session_id')
+ ->bind(':session_id', $sessionId)
+ ->setLimit(1);
+
+ $this->db->setQuery($query);
+
+ try
+ {
+ $exists = $this->db->loadResult();
+ }
+ catch (ExecutionFailureException $e)
+ {
+ return self::$sessionRecordUnknown;
+ }
+
+ if ($exists)
+ {
+ return self::$sessionRecordExists;
+ }
+
+ return self::$sessionRecordDoesNotExist;
+ }
+
+ /**
+ * Create the session record
+ *
+ * @param SessionInterface $session The session to create the metadata record for.
+ * @param User $user The user to associate with the record.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ private function createSessionRecord(SessionInterface $session, User $user)
+ {
+ $query = $this->db->getQuery(true);
+
+ $time = $session->isNew() ? time() : $session->get('session.timer.start');
+
+ $columns = [
+ $this->db->quoteName('session_id'),
+ $this->db->quoteName('guest'),
+ $this->db->quoteName('time'),
+ $this->db->quoteName('userid'),
+ $this->db->quoteName('username'),
+ ];
+
+ // Add query placeholders
+ $values = [
+ ':session_id',
+ ':guest',
+ ':time',
+ ':user_id',
+ ':username',
+ ];
+
+ // Bind query values
+ $sessionId = $session->getId();
+ $userIsGuest = $user->guest;
+ $userId = $user->id;
+ $username = $user->username === null ? '' : $user->username;
+
+ $query->bind(':session_id', $sessionId)
+ ->bind(':guest', $userIsGuest, ParameterType::INTEGER)
+ ->bind(':time', $time)
+ ->bind(':user_id', $userId, ParameterType::INTEGER)
+ ->bind(':username', $username);
+
+ if ($this->app instanceof CMSApplication && !$this->app->get('shared_session', false))
+ {
+ $clientId = $this->app->getClientId();
+
+ $columns[] = $this->db->quoteName('client_id');
+ $values[] = ':client_id';
+
+ $query->bind(':client_id', $clientId, ParameterType::INTEGER);
+ }
+
+ $query->insert($this->db->quoteName('#__session'))
+ ->columns($columns)
+ ->values(implode(', ', $values));
+
+ $this->db->setQuery($query);
+
+ try
+ {
+ $this->db->execute();
+ }
+ catch (ExecutionFailureException $e)
+ {
+ // This failure isn't critical, we can go on without the metadata
+ }
+ }
+
+ /**
+ * Update the session record
+ *
+ * @param SessionInterface $session The session to update the metadata record for.
+ * @param User $user The user to associate with the record.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ private function updateSessionRecord(SessionInterface $session, User $user)
+ {
+ $query = $this->db->getQuery(true);
+
+ $time = time();
+
+ $setValues = [
+ $this->db->quoteName('guest') . ' = :guest',
+ $this->db->quoteName('time') . ' = :time',
+ $this->db->quoteName('userid') . ' = :user_id',
+ $this->db->quoteName('username') . ' = :username',
+ ];
+
+ // Bind query values
+ $sessionId = $session->getId();
+ $userIsGuest = $user->guest;
+ $userId = $user->id;
+ $username = $user->username === null ? '' : $user->username;
+
+ $query->bind(':session_id', $sessionId)
+ ->bind(':guest', $userIsGuest, ParameterType::INTEGER)
+ ->bind(':time', $time)
+ ->bind(':user_id', $userId, ParameterType::INTEGER)
+ ->bind(':username', $username);
+
+ if ($this->app instanceof CMSApplication && !$this->app->get('shared_session', false))
+ {
+ $clientId = $this->app->getClientId();
+
+ $setValues[] = $this->db->quoteName('client_id') . ' = :client_id';
+
+ $query->bind(':client_id', $clientId, ParameterType::INTEGER);
+ }
+
+ $query->update($this->db->quoteName('#__session'))
+ ->set($setValues)
+ ->where($this->db->quoteName('session_id') . ' = :session_id');
+
+ $this->db->setQuery($query);
+
+ try
+ {
+ $this->db->execute();
+ }
+ catch (ExecutionFailureException $e)
+ {
+ // This failure isn't critical, we can go on without the metadata
+ }
+ }
+}
diff --git a/week-03/project/libraries/src/String/PunycodeHelper.php b/week-03/project/libraries/src/String/PunycodeHelper.php
new file mode 100644
index 0000000..f839afe
--- /dev/null
+++ b/week-03/project/libraries/src/String/PunycodeHelper.php
@@ -0,0 +1,262 @@
+
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+namespace Joomla\CMS\String;
+
+\defined('JPATH_PLATFORM') or die;
+
+use Algo26\IdnaConvert\ToIdn;
+use Algo26\IdnaConvert\ToUnicode;
+use Joomla\Uri\UriHelper;
+
+/**
+ * Joomla Platform String Punycode Class
+ *
+ * Class for handling UTF-8 URLs
+ * Wraps the Punycode library
+ * All functions assume the validity of utf-8 URLs.
+ *
+ * @since 3.1.2
+ */
+abstract class PunycodeHelper
+{
+ /**
+ * Transforms a UTF-8 string to a Punycode string
+ *
+ * @param string $utfString The UTF-8 string to transform
+ *
+ * @return string The punycode string
+ *
+ * @since 3.1.2
+ */
+ public static function toPunycode($utfString)
+ {
+ return (new ToIdn)->convert($utfString);
+ }
+
+ /**
+ * Transforms a Punycode string to a UTF-8 string
+ *
+ * @param string $punycodeString The Punycode string to transform
+ *
+ * @return string The UF-8 URL
+ *
+ * @since 3.1.2
+ */
+ public static function fromPunycode($punycodeString)
+ {
+ return (new ToUnicode)->convert($punycodeString);
+ }
+
+ /**
+ * Transforms a UTF-8 URL to a Punycode URL
+ *
+ * @param string $uri The UTF-8 URL to transform
+ *
+ * @return string The punycode URL
+ *
+ * @since 3.1.2
+ */
+ public static function urlToPunycode($uri)
+ {
+ $parsed = UriHelper::parse_url($uri);
+
+ if (!isset($parsed['host']) || $parsed['host'] == '')
+ {
+ // If there is no host we do not need to convert it.
+ return $uri;
+ }
+
+ $host = $parsed['host'];
+ $hostExploded = explode('.', $host);
+ $newhost = '';
+
+ foreach ($hostExploded as $hostex)
+ {
+ $hostex = static::toPunycode($hostex);
+ $newhost .= $hostex . '.';
+ }
+
+ $newhost = substr($newhost, 0, -1);
+ $newuri = '';
+
+ if (!empty($parsed['scheme']))
+ {
+ // Assume :// is required although it is not always.
+ $newuri .= $parsed['scheme'] . '://';
+ }
+
+ if (!empty($newhost))
+ {
+ $newuri .= $newhost;
+ }
+
+ if (!empty($parsed['port']))
+ {
+ $newuri .= ':' . $parsed['port'];
+ }
+
+ if (!empty($parsed['path']))
+ {
+ $newuri .= $parsed['path'];
+ }
+
+ if (!empty($parsed['query']))
+ {
+ $newuri .= '?' . $parsed['query'];
+ }
+
+ if (!empty($parsed['fragment']))
+ {
+ $newuri .= '#' . $parsed['fragment'];
+ }
+
+ return $newuri;
+ }
+
+ /**
+ * Transforms a Punycode URL to a UTF-8 URL
+ *
+ * @param string $uri The Punycode URL to transform
+ *
+ * @return string The UTF-8 URL
+ *
+ * @since 3.1.2
+ */
+ public static function urlToUTF8($uri)
+ {
+ if (empty($uri))
+ {
+ return '';
+ }
+
+ $parsed = UriHelper::parse_url($uri);
+
+ if (!isset($parsed['host']) || $parsed['host'] == '')
+ {
+ // If there is no host we do not need to convert it.
+ return $uri;
+ }
+
+ $host = $parsed['host'];
+ $hostExploded = explode('.', $host);
+ $newhost = '';
+
+ foreach ($hostExploded as $hostex)
+ {
+ $hostex = self::fromPunycode($hostex);
+ $newhost .= $hostex . '.';
+ }
+
+ $newhost = substr($newhost, 0, -1);
+ $newuri = '';
+
+ if (!empty($parsed['scheme']))
+ {
+ // Assume :// is required although it is not always.
+ $newuri .= $parsed['scheme'] . '://';
+ }
+
+ if (!empty($newhost))
+ {
+ $newuri .= $newhost;
+ }
+
+ if (!empty($parsed['port']))
+ {
+ $newuri .= ':' . $parsed['port'];
+ }
+
+ if (!empty($parsed['path']))
+ {
+ $newuri .= $parsed['path'];
+ }
+
+ if (!empty($parsed['query']))
+ {
+ $newuri .= '?' . $parsed['query'];
+ }
+
+ if (!empty($parsed['fragment']))
+ {
+ $newuri .= '#' . $parsed['fragment'];
+ }
+
+ return $newuri;
+ }
+
+ /**
+ * Transforms a UTF-8 email to a Punycode email
+ * This assumes a valid email address
+ *
+ * @param string $email The UTF-8 email to transform
+ *
+ * @return string The punycode email
+ *
+ * @since 3.1.2
+ */
+ public static function emailToPunycode($email)
+ {
+ $explodedAddress = explode('@', $email);
+
+ // Not addressing UTF-8 user names
+ $newEmail = $explodedAddress[0];
+
+ if (!empty($explodedAddress[1]))
+ {
+ $domainExploded = explode('.', $explodedAddress[1]);
+ $newdomain = '';
+
+ foreach ($domainExploded as $domainex)
+ {
+ $domainex = static::toPunycode($domainex);
+ $newdomain .= $domainex . '.';
+ }
+
+ $newdomain = substr($newdomain, 0, -1);
+ $newEmail = $newEmail . '@' . $newdomain;
+ }
+
+ return $newEmail;
+ }
+
+ /**
+ * Transforms a Punycode email to a UTF-8 email
+ * This assumes a valid email address
+ *
+ * @param string $email The punycode email to transform
+ *
+ * @return string The punycode email
+ *
+ * @since 3.1.2
+ */
+ public static function emailToUTF8($email)
+ {
+ $explodedAddress = explode('@', $email);
+
+ // Not addressing UTF-8 user names
+ $newEmail = $explodedAddress[0];
+
+ if (!empty($explodedAddress[1]))
+ {
+ $domainExploded = explode('.', $explodedAddress[1]);
+ $newdomain = '';
+
+ foreach ($domainExploded as $domainex)
+ {
+ $domainex = static::fromPunycode($domainex);
+ $newdomain .= $domainex . '.';
+ }
+
+ $newdomain = substr($newdomain, 0, -1);
+ $newEmail = $newEmail . '@' . $newdomain;
+ }
+
+ return $newEmail;
+ }
+}
diff --git a/week-03/project/libraries/src/User/User.php b/week-03/project/libraries/src/User/User.php
new file mode 100644
index 0000000..85e0e83
--- /dev/null
+++ b/week-03/project/libraries/src/User/User.php
@@ -0,0 +1,934 @@
+
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+namespace Joomla\CMS\User;
+
+\defined('JPATH_PLATFORM') or die;
+
+use Joomla\CMS\Access\Access;
+use Joomla\CMS\Factory;
+use Joomla\CMS\Language\Text;
+use Joomla\CMS\Log\Log;
+use Joomla\CMS\Object\CMSObject;
+use Joomla\CMS\Plugin\PluginHelper;
+use Joomla\CMS\Table\Table;
+use Joomla\Registry\Registry;
+use Joomla\Utilities\ArrayHelper;
+
+/**
+ * User class. Handles all application interaction with a user
+ *
+ * @since 1.7.0
+ */
+class User extends CMSObject
+{
+ /**
+ * A cached switch for if this user has root access rights.
+ *
+ * @var boolean
+ * @since 1.7.0
+ */
+ protected $isRoot = null;
+
+ /**
+ * Unique id
+ *
+ * @var integer
+ * @since 1.7.0
+ */
+ public $id = null;
+
+ /**
+ * The user's real name (or nickname)
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ public $name = null;
+
+ /**
+ * The login name
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ public $username = null;
+
+ /**
+ * The email
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ public $email = null;
+
+ /**
+ * MD5 encrypted password
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ public $password = null;
+
+ /**
+ * Clear password, only available when a new password is set for a user
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ public $password_clear = '';
+
+ /**
+ * Block status
+ *
+ * @var integer
+ * @since 1.7.0
+ */
+ public $block = null;
+
+ /**
+ * Should this user receive system email
+ *
+ * @var integer
+ * @since 1.7.0
+ */
+ public $sendEmail = null;
+
+ /**
+ * Date the user was registered
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ public $registerDate = null;
+
+ /**
+ * Date of last visit
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ public $lastvisitDate = null;
+
+ /**
+ * Activation hash
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ public $activation = null;
+
+ /**
+ * User parameters
+ *
+ * @var Registry
+ * @since 1.7.0
+ */
+ public $params = null;
+
+ /**
+ * Associative array of user names => group ids
+ *
+ * @var array
+ * @since 1.7.0
+ */
+ public $groups = array();
+
+ /**
+ * Guest status
+ *
+ * @var integer
+ * @since 1.7.0
+ */
+ public $guest = null;
+
+ /**
+ * Last Reset Time
+ *
+ * @var string
+ * @since 3.0.1
+ */
+ public $lastResetTime = null;
+
+ /**
+ * Count since last Reset Time
+ *
+ * @var integer
+ * @since 3.0.1
+ */
+ public $resetCount = null;
+
+ /**
+ * Flag to require the user's password be reset
+ *
+ * @var integer
+ * @since 3.2
+ */
+ public $requireReset = null;
+
+ /**
+ * User parameters
+ *
+ * @var Registry
+ * @since 1.7.0
+ */
+ protected $_params = null;
+
+ /**
+ * Authorised access groups
+ *
+ * @var array
+ * @since 1.7.0
+ */
+ protected $_authGroups = null;
+
+ /**
+ * Authorised access levels
+ *
+ * @var array
+ * @since 1.7.0
+ */
+ protected $_authLevels = null;
+
+ /**
+ * Authorised access actions
+ *
+ * @var array
+ * @since 1.7.0
+ */
+ protected $_authActions = null;
+
+ /**
+ * Error message
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ protected $_errorMsg = null;
+
+ /**
+ * @var array User instances container.
+ * @since 1.7.3
+ */
+ protected static $instances = array();
+
+ /**
+ * Constructor activating the default information of the language
+ *
+ * @param integer $identifier The primary key of the user to load (optional).
+ *
+ * @since 1.7.0
+ */
+ public function __construct($identifier = 0)
+ {
+ // Create the user parameters object
+ $this->_params = new Registry;
+
+ // Load the user if it exists
+ if (!empty($identifier))
+ {
+ $this->load($identifier);
+ }
+ else
+ {
+ // Initialise
+ $this->id = 0;
+ $this->sendEmail = 0;
+ $this->aid = 0;
+ $this->guest = 1;
+ }
+ }
+
+ /**
+ * Returns the global User object, only creating it if it doesn't already exist.
+ *
+ * @param integer $identifier The primary key of the user to load (optional).
+ *
+ * @return User The User object.
+ *
+ * @since 1.7.0
+ * @deprecated 5.0 Load the user service from the dependency injection container or via $app->getIdentity()
+ */
+ public static function getInstance($identifier = 0)
+ {
+ @trigger_error(
+ sprintf(
+ '%1$s() is deprecated. Load the user from the dependency injection container or via %2$s::getApplication()->getIdentity().',
+ __METHOD__,
+ __CLASS__
+ ),
+ E_USER_DEPRECATED
+ );
+
+ // Find the user id
+ if (!is_numeric($identifier))
+ {
+ return Factory::getContainer()->get(UserFactoryInterface::class)->loadUserByUsername($identifier);
+ }
+ else
+ {
+ $id = $identifier;
+ }
+
+ // If the $id is zero, just return an empty User.
+ // Note: don't cache this user because it'll have a new ID on save!
+ if ($id === 0)
+ {
+ return Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById($id);
+ }
+
+ // Check if the user ID is already cached.
+ if (empty(self::$instances[$id]))
+ {
+ self::$instances[$id] = Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById($id);
+ }
+
+ return self::$instances[$id];
+ }
+
+ /**
+ * Method to get a parameter value
+ *
+ * @param string $key Parameter key
+ * @param mixed $default Parameter default value
+ *
+ * @return mixed The value or the default if it did not exist
+ *
+ * @since 1.7.0
+ */
+ public function getParam($key, $default = null)
+ {
+ return $this->_params->get($key, $default);
+ }
+
+ /**
+ * Method to set a parameter
+ *
+ * @param string $key Parameter key
+ * @param mixed $value Parameter value
+ *
+ * @return mixed Set parameter value
+ *
+ * @since 1.7.0
+ */
+ public function setParam($key, $value)
+ {
+ return $this->_params->set($key, $value);
+ }
+
+ /**
+ * Method to set a default parameter if it does not exist
+ *
+ * @param string $key Parameter key
+ * @param mixed $value Parameter value
+ *
+ * @return mixed Set parameter value
+ *
+ * @since 1.7.0
+ */
+ public function defParam($key, $value)
+ {
+ return $this->_params->def($key, $value);
+ }
+
+ /**
+ * Method to check User object authorisation against an access control
+ * object and optionally an access extension object
+ *
+ * @param string $action The name of the action to check for permission.
+ * @param string $assetname The name of the asset on which to perform the action.
+ *
+ * @return boolean True if authorised
+ *
+ * @since 1.7.0
+ */
+ public function authorise($action, $assetname = null)
+ {
+ // Make sure we only check for core.admin once during the run.
+ if ($this->isRoot === null)
+ {
+ $this->isRoot = false;
+
+ // Check for the configuration file failsafe.
+ $rootUser = Factory::getApplication()->get('root_user');
+
+ // The root_user variable can be a numeric user ID or a username.
+ if (is_numeric($rootUser) && $this->id > 0 && $this->id == $rootUser)
+ {
+ $this->isRoot = true;
+ }
+ elseif ($this->username && $this->username == $rootUser)
+ {
+ $this->isRoot = true;
+ }
+ elseif ($this->id > 0)
+ {
+ // Get all groups against which the user is mapped.
+ $identities = $this->getAuthorisedGroups();
+ array_unshift($identities, $this->id * -1);
+
+ if (Access::getAssetRules(1)->allow('core.admin', $identities))
+ {
+ $this->isRoot = true;
+
+ return true;
+ }
+ }
+ }
+
+ return $this->isRoot ? true : (bool) Access::check($this->id, $action, $assetname);
+ }
+
+ /**
+ * Method to return a list of all categories that a user has permission for a given action
+ *
+ * @param string $component The component from which to retrieve the categories
+ * @param string $action The name of the section within the component from which to retrieve the actions.
+ *
+ * @return array List of categories that this group can do this action to (empty array if none). Categories must be published.
+ *
+ * @since 1.7.0
+ */
+ public function getAuthorisedCategories($component, $action)
+ {
+ // Brute force method: get all published category rows for the component and check each one
+ // @todo: Modify the way permissions are stored in the db to allow for faster implementation and better scaling
+ $db = Factory::getDbo();
+
+ $subQuery = $db->getQuery(true)
+ ->select($db->quoteName(['id', 'asset_id']))
+ ->from($db->quoteName('#__categories'))
+ ->where(
+ [
+ $db->quoteName('extension') . ' = :component',
+ $db->quoteName('published') . ' = 1',
+ ]
+ );
+
+ $query = $db->getQuery(true)
+ ->select($db->quoteName(['c.id', 'a.name']))
+ ->from('(' . $subQuery . ') AS ' . $db->quoteName('c'))
+ ->join('INNER', $db->quoteName('#__assets', 'a'), $db->quoteName('c.asset_id') . ' = ' . $db->quoteName('a.id'))
+ ->bind(':component', $component);
+ $db->setQuery($query);
+ $allCategories = $db->loadObjectList('id');
+ $allowedCategories = array();
+
+ foreach ($allCategories as $category)
+ {
+ if ($this->authorise($action, $category->name))
+ {
+ $allowedCategories[] = (int) $category->id;
+ }
+ }
+
+ return $allowedCategories;
+ }
+
+ /**
+ * Gets an array of the authorised access levels for the user
+ *
+ * @return array
+ *
+ * @since 1.7.0
+ */
+ public function getAuthorisedViewLevels()
+ {
+ if ($this->_authLevels === null)
+ {
+ $this->_authLevels = array();
+ }
+
+ if (empty($this->_authLevels))
+ {
+ $this->_authLevels = Access::getAuthorisedViewLevels($this->id);
+ }
+
+ return $this->_authLevels;
+ }
+
+ /**
+ * Gets an array of the authorised user groups
+ *
+ * @return array
+ *
+ * @since 1.7.0
+ */
+ public function getAuthorisedGroups()
+ {
+ if ($this->_authGroups === null)
+ {
+ $this->_authGroups = array();
+ }
+
+ if (empty($this->_authGroups))
+ {
+ $this->_authGroups = Access::getGroupsByUser($this->id);
+ }
+
+ return $this->_authGroups;
+ }
+
+ /**
+ * Clears the access rights cache of this user
+ *
+ * @return void
+ *
+ * @since 3.4.0
+ */
+ public function clearAccessRights()
+ {
+ $this->_authLevels = null;
+ $this->_authGroups = null;
+ $this->isRoot = null;
+ Access::clearStatics();
+ }
+
+ /**
+ * Pass through method to the table for setting the last visit date
+ *
+ * @param integer $timestamp The timestamp, defaults to 'now'.
+ *
+ * @return boolean True on success.
+ *
+ * @since 1.7.0
+ */
+ public function setLastVisit($timestamp = null)
+ {
+ // Create the user table object
+ /** @var \Joomla\CMS\Table\User $table */
+ $table = $this->getTable();
+ $table->load($this->id);
+
+ return $table->setLastVisit($timestamp);
+ }
+
+ /**
+ * Method to get the user timezone.
+ *
+ * If the user didn't set a timezone, it will return the server timezone
+ *
+ * @return \DateTimeZone
+ *
+ * @since 3.7.0
+ */
+ public function getTimezone()
+ {
+ $timezone = $this->getParam('timezone', Factory::getApplication()->get('offset', 'GMT'));
+
+ return new \DateTimeZone($timezone);
+ }
+
+ /**
+ * Method to get the user parameters
+ *
+ * @param object $params The user parameters object
+ *
+ * @return void
+ *
+ * @since 1.7.0
+ */
+ public function setParameters($params)
+ {
+ $this->_params = $params;
+ }
+
+ /**
+ * Method to get the user table object
+ *
+ * This function uses a static variable to store the table name of the user table to
+ * instantiate. You can call this function statically to set the table name if
+ * needed.
+ *
+ * @param string $type The user table name to be used
+ * @param string $prefix The user table prefix to be used
+ *
+ * @return Table The user table object
+ *
+ * @note At 4.0 this method will no longer be static
+ * @since 1.7.0
+ */
+ public static function getTable($type = null, $prefix = 'JTable')
+ {
+ static $tabletype;
+
+ // Set the default tabletype;
+ if (!isset($tabletype))
+ {
+ $tabletype['name'] = 'user';
+ $tabletype['prefix'] = 'JTable';
+ }
+
+ // Set a custom table type is defined
+ if (isset($type))
+ {
+ $tabletype['name'] = $type;
+ $tabletype['prefix'] = $prefix;
+ }
+
+ // Create the user table object
+ return Table::getInstance($tabletype['name'], $tabletype['prefix']);
+ }
+
+ /**
+ * Method to bind an associative array of data to a user object
+ *
+ * @param array &$array The associative array to bind to the object
+ *
+ * @return boolean True on success
+ *
+ * @since 1.7.0
+ */
+ public function bind(&$array)
+ {
+ // Let's check to see if the user is new or not
+ if (empty($this->id))
+ {
+ // Check the password and create the crypted password
+ if (empty($array['password']))
+ {
+ $array['password'] = UserHelper::genRandomPassword(32);
+ $array['password2'] = $array['password'];
+ }
+
+ // Not all controllers check the password, although they should.
+ // Hence this code is required:
+ if (isset($array['password2']) && $array['password'] != $array['password2'])
+ {
+ Factory::getApplication()->enqueueMessage(Text::_('JLIB_USER_ERROR_PASSWORD_NOT_MATCH'), 'error');
+
+ return false;
+ }
+
+ $this->password_clear = ArrayHelper::getValue($array, 'password', '', 'string');
+
+ $array['password'] = UserHelper::hashPassword($array['password']);
+
+ // Set the registration timestamp
+ $this->set('registerDate', Factory::getDate()->toSql());
+ }
+ else
+ {
+ // Updating an existing user
+ if (!empty($array['password']))
+ {
+ if ($array['password'] != $array['password2'])
+ {
+ $this->setError(Text::_('JLIB_USER_ERROR_PASSWORD_NOT_MATCH'));
+
+ return false;
+ }
+
+ $this->password_clear = ArrayHelper::getValue($array, 'password', '', 'string');
+
+ // Check if the user is reusing the current password if required to reset their password
+ if ($this->requireReset == 1 && UserHelper::verifyPassword($this->password_clear, $this->password))
+ {
+ $this->setError(Text::_('JLIB_USER_ERROR_CANNOT_REUSE_PASSWORD'));
+
+ return false;
+ }
+
+ $array['password'] = UserHelper::hashPassword($array['password']);
+
+ // Reset the change password flag
+ $array['requireReset'] = 0;
+ }
+ else
+ {
+ $array['password'] = $this->password;
+ }
+
+ // Prevent updating internal fields
+ unset($array['registerDate']);
+ unset($array['lastvisitDate']);
+ unset($array['lastResetTime']);
+ unset($array['resetCount']);
+ }
+
+ if (\array_key_exists('params', $array))
+ {
+ $this->_params->loadArray($array['params']);
+
+ if (\is_array($array['params']))
+ {
+ $params = (string) $this->_params;
+ }
+ else
+ {
+ $params = $array['params'];
+ }
+
+ $this->params = $params;
+ }
+
+ // Bind the array
+ if (!$this->setProperties($array))
+ {
+ $this->setError(Text::_('JLIB_USER_ERROR_BIND_ARRAY'));
+
+ return false;
+ }
+
+ // Make sure its an integer
+ $this->id = (int) $this->id;
+
+ return true;
+ }
+
+ /**
+ * Method to save the User object to the database
+ *
+ * @param boolean $updateOnly Save the object only if not a new user
+ * Currently only used in the user reset password method.
+ *
+ * @return boolean True on success
+ *
+ * @since 1.7.0
+ * @throws \RuntimeException
+ */
+ public function save($updateOnly = false)
+ {
+ // Create the user table object
+ $table = $this->getTable();
+ $this->params = (string) $this->_params;
+ $table->bind($this->getProperties());
+
+ // Allow an exception to be thrown.
+ try
+ {
+ // Check and store the object.
+ if (!$table->check())
+ {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ // If user is made a Super Admin group and user is NOT a Super Admin
+
+ // @todo ACL - this needs to be acl checked
+
+ $my = Factory::getUser();
+
+ // Are we creating a new user
+ $isNew = empty($this->id);
+
+ // If we aren't allowed to create new users return
+ if ($isNew && $updateOnly)
+ {
+ return true;
+ }
+
+ // Get the old user
+ $oldUser = new User($this->id);
+
+ // Access Checks
+
+ // The only mandatory check is that only Super Admins can operate on other Super Admin accounts.
+ // To add additional business rules, use a user plugin and throw an Exception with onUserBeforeSave.
+
+ // Check if I am a Super Admin
+ $iAmSuperAdmin = $my->authorise('core.admin');
+
+ $iAmRehashingSuperadmin = false;
+
+ if (($my->id == 0 && !$isNew) && $this->id == $oldUser->id && $oldUser->authorise('core.admin') && $oldUser->password != $this->password)
+ {
+ $iAmRehashingSuperadmin = true;
+ }
+
+ // Check if we are using a CLI application
+ $isCli = false;
+
+ if (Factory::getApplication()->isCli())
+ {
+ $isCli = true;
+ }
+
+ // We are only worried about edits to this account if I am not a Super Admin.
+ if ($iAmSuperAdmin != true && $iAmRehashingSuperadmin != true && $isCli != true)
+ {
+ // I am not a Super Admin, and this one is, so fail.
+ if (!$isNew && Access::check($this->id, 'core.admin'))
+ {
+ throw new \RuntimeException('User not Super Administrator');
+ }
+
+ if ($this->groups != null)
+ {
+ // I am not a Super Admin and I'm trying to make one.
+ foreach ($this->groups as $groupId)
+ {
+ if (Access::checkGroup($groupId, 'core.admin'))
+ {
+ throw new \RuntimeException('User not Super Administrator');
+ }
+ }
+ }
+ }
+
+ // Fire the onUserBeforeSave event.
+ PluginHelper::importPlugin('user');
+
+ $result = Factory::getApplication()->triggerEvent('onUserBeforeSave', array($oldUser->getProperties(), $isNew, $this->getProperties()));
+
+ if (\in_array(false, $result, true))
+ {
+ // Plugin will have to raise its own error or throw an exception.
+ return false;
+ }
+
+ // Store the user data in the database
+ $result = $table->store();
+
+ // Set the id for the User object in case we created a new user.
+ if (empty($this->id))
+ {
+ $this->id = $table->get('id');
+ }
+
+ if ($my->id == $table->id)
+ {
+ $registry = new Registry($table->params);
+ $my->setParameters($registry);
+ }
+
+ // Fire the onUserAfterSave event
+ Factory::getApplication()->triggerEvent('onUserAfterSave', array($this->getProperties(), $isNew, $result, $this->getError()));
+ }
+ catch (\Exception $e)
+ {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Method to delete the User object from the database
+ *
+ * @return boolean True on success
+ *
+ * @since 1.7.0
+ */
+ public function delete()
+ {
+ PluginHelper::importPlugin('user');
+
+ // Trigger the onUserBeforeDelete event
+ Factory::getApplication()->triggerEvent('onUserBeforeDelete', array($this->getProperties()));
+
+ // Create the user table object
+ $table = $this->getTable();
+
+ if (!$result = $table->delete($this->id))
+ {
+ $this->setError($table->getError());
+ }
+
+ // Trigger the onUserAfterDelete event
+ Factory::getApplication()->triggerEvent('onUserAfterDelete', array($this->getProperties(), $result, $this->getError()));
+
+ return $result;
+ }
+
+ /**
+ * Method to load a User object by user id number
+ *
+ * @param mixed $id The user id of the user to load
+ *
+ * @return boolean True on success
+ *
+ * @since 1.7.0
+ */
+ public function load($id)
+ {
+ // Create the user table object
+ $table = $this->getTable();
+
+ // Load the UserModel object based on the user id or throw a warning.
+ if (!$table->load($id))
+ {
+ // Reset to guest user
+ $this->guest = 1;
+
+ Log::add(Text::sprintf('JLIB_USER_ERROR_UNABLE_TO_LOAD_USER', $id), Log::WARNING, 'jerror');
+
+ return false;
+ }
+
+ /*
+ * Set the user parameters using the default XML file. We might want to
+ * extend this in the future to allow for the ability to have custom
+ * user parameters, but for right now we'll leave it how it is.
+ */
+
+ if ($table->params)
+ {
+ $this->_params->loadString($table->params);
+ }
+
+ // Assuming all is well at this point let's bind the data
+ $this->setProperties($table->getProperties());
+
+ // The user is no longer a guest
+ if ($this->id != 0)
+ {
+ $this->guest = 0;
+ }
+ else
+ {
+ $this->guest = 1;
+ }
+
+ return true;
+ }
+
+ /**
+ * Method to allow serialize the object with minimal properties.
+ *
+ * @return array The names of the properties to include in serialization.
+ *
+ * @since 3.6.0
+ */
+ public function __sleep()
+ {
+ return array('id');
+ }
+
+ /**
+ * Method to recover the full object on unserialize.
+ *
+ * @return void
+ *
+ * @since 3.6.0
+ */
+ public function __wakeup()
+ {
+ // Initialise some variables
+ $this->_params = new Registry;
+
+ // Load the user if it exists
+ if (!empty($this->id) && $this->load($this->id))
+ {
+ // Push user into cached instances.
+ self::$instances[$this->id] = $this;
+ }
+ else
+ {
+ // Initialise
+ $this->id = 0;
+ $this->sendEmail = 0;
+ $this->aid = 0;
+ $this->guest = 1;
+ }
+ }
+}
diff --git a/week-03/project/libraries/src/User/UserFactory.php b/week-03/project/libraries/src/User/UserFactory.php
new file mode 100644
index 0000000..fd5cb27
--- /dev/null
+++ b/week-03/project/libraries/src/User/UserFactory.php
@@ -0,0 +1,75 @@
+
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+namespace Joomla\CMS\User;
+
+\defined('_JEXEC') or die;
+
+use Joomla\Database\DatabaseInterface;
+
+/**
+ * Default factory for creating User objects
+ *
+ * @since 4.0.0
+ */
+class UserFactory implements UserFactoryInterface
+{
+ /**
+ * The database.
+ *
+ * @var DatabaseInterface
+ */
+ private $db;
+
+ /**
+ * UserFactory constructor.
+ *
+ * @param DatabaseInterface $db The database
+ */
+ public function __construct(DatabaseInterface $db)
+ {
+ $this->db = $db;
+ }
+
+ /**
+ * Method to get an instance of a user for the given id.
+ *
+ * @param int $id The id
+ *
+ * @return User
+ *
+ * @since 4.0.0
+ */
+ public function loadUserById(int $id): User
+ {
+ return new User($id);
+ }
+
+ /**
+ * Method to get an instance of a user for the given username.
+ *
+ * @param string $username The username
+ *
+ * @return User
+ *
+ * @since 4.0.0
+ */
+ public function loadUserByUsername(string $username): User
+ {
+ // Initialise some variables
+ $query = $this->db->getQuery(true)
+ ->select($this->db->quoteName('id'))
+ ->from($this->db->quoteName('#__users'))
+ ->where($this->db->quoteName('username') . ' = :username')
+ ->bind(':username', $username)
+ ->setLimit(1);
+ $this->db->setQuery($query);
+
+ return $this->loadUserById((int) $this->db->loadResult());
+ }
+}
diff --git a/week-03/project/libraries/src/User/UserFactoryInterface.php b/week-03/project/libraries/src/User/UserFactoryInterface.php
new file mode 100644
index 0000000..a9a1816
--- /dev/null
+++ b/week-03/project/libraries/src/User/UserFactoryInterface.php
@@ -0,0 +1,41 @@
+
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+namespace Joomla\CMS\User;
+
+\defined('_JEXEC') or die;
+
+/**
+ * Interface defining a factory which can create User objects
+ *
+ * @since 4.0.0
+ */
+interface UserFactoryInterface
+{
+ /**
+ * Method to get an instance of a user for the given id.
+ *
+ * @param int $id The id
+ *
+ * @return User
+ *
+ * @since 4.0.0
+ */
+ public function loadUserById(int $id): User;
+
+ /**
+ * Method to get an instance of a user for the given username.
+ *
+ * @param string $username The username
+ *
+ * @return User
+ *
+ * @since 4.0.0
+ */
+ public function loadUserByUsername(string $username): User;
+}
diff --git a/week-03/project/libraries/src/View/Admin/DashboardHtmlView.php b/week-03/project/libraries/src/View/Admin/DashboardHtmlView.php
new file mode 100644
index 0000000..a04088a
--- /dev/null
+++ b/week-03/project/libraries/src/View/Admin/DashboardHtmlView.php
@@ -0,0 +1,97 @@
+helper = $helper;
+ $this->packageModel = $packageModel;
+ $this->releaseModel = $releaseModel;
+ }
+
+ /**
+ * Method to render the view
+ *
+ * @return string The rendered view
+ */
+ public function render()
+ {
+ $package = $this->packageModel->getPackage($this->package);
+
+ $this->setData(
+ [
+ 'releases' => $this->releaseModel->getPackageHistory($package),
+ 'package' => $package,
+ ]
+ );
+
+ return parent::render();
+ }
+
+ /**
+ * Set the active package
+ *
+ * @param string $package The active package name
+ *
+ * @return void
+ */
+ public function setPackage(string $package): void
+ {
+ $this->package = $package;
+ }
+}
diff --git a/week-03/project/libraries/src/View/Page/PageHtmlView.php b/week-03/project/libraries/src/View/Page/PageHtmlView.php
new file mode 100644
index 0000000..a04088a
--- /dev/null
+++ b/week-03/project/libraries/src/View/Page/PageHtmlView.php
@@ -0,0 +1,97 @@
+helper = $helper;
+ $this->packageModel = $packageModel;
+ $this->releaseModel = $releaseModel;
+ }
+
+ /**
+ * Method to render the view
+ *
+ * @return string The rendered view
+ */
+ public function render()
+ {
+ $package = $this->packageModel->getPackage($this->package);
+
+ $this->setData(
+ [
+ 'releases' => $this->releaseModel->getPackageHistory($package),
+ 'package' => $package,
+ ]
+ );
+
+ return parent::render();
+ }
+
+ /**
+ * Set the active package
+ *
+ * @param string $package The active package name
+ *
+ * @return void
+ */
+ public function setPackage(string $package): void
+ {
+ $this->package = $package;
+ }
+}
diff --git a/week-03/project/libraries/web.config b/week-03/project/libraries/web.config
new file mode 100644
index 0000000..f7a77db
--- /dev/null
+++ b/week-03/project/libraries/web.config
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/week-03/project/media/css/index.html b/week-03/project/media/css/index.html
new file mode 100644
index 0000000..2efb97f
--- /dev/null
+++ b/week-03/project/media/css/index.html
@@ -0,0 +1 @@
+
diff --git a/week-03/project/media/css/template.css b/week-03/project/media/css/template.css
new file mode 100644
index 0000000..e69de29
diff --git a/week-03/project/media/index.html b/week-03/project/media/index.html
new file mode 100644
index 0000000..2efb97f
--- /dev/null
+++ b/week-03/project/media/index.html
@@ -0,0 +1 @@
+
diff --git a/week-03/project/media/js/index.html b/week-03/project/media/js/index.html
new file mode 100644
index 0000000..2efb97f
--- /dev/null
+++ b/week-03/project/media/js/index.html
@@ -0,0 +1 @@
+
diff --git a/week-03/project/media/js/template.js b/week-03/project/media/js/template.js
new file mode 100644
index 0000000..e69de29
diff --git a/week-03/project/media/mix-manifest.json b/week-03/project/media/mix-manifest.json
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ b/week-03/project/media/mix-manifest.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/week-03/project/media/sri-manifest.json b/week-03/project/media/sri-manifest.json
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ b/week-03/project/media/sri-manifest.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/week-03/project/robots.txt.dist b/week-03/project/robots.txt.dist
new file mode 100644
index 0000000..97b1244
--- /dev/null
+++ b/week-03/project/robots.txt.dist
@@ -0,0 +1,28 @@
+# If the Joomla site is installed within a folder
+# eg www.example.com/joomla/ then the robots.txt file
+# MUST be moved to the site root
+# eg www.example.com/robots.txt
+# AND the joomla folder name MUST be prefixed to all of the
+# paths.
+# eg the Disallow rule for the /administrator/ folder MUST
+# be changed to read
+# Disallow: /joomla/administrator/
+#
+# For more information about the robots.txt standard, see:
+# https://www.robotstxt.org/orig.html
+
+User-agent: *
+Disallow: /administrator/
+Disallow: /bin/
+Disallow: /cache/
+Disallow: /cli/
+Disallow: /components/
+Disallow: /includes/
+Disallow: /installation/
+Disallow: /language/
+Disallow: /layouts/
+Disallow: /libraries/
+Disallow: /logs/
+Disallow: /modules/
+Disallow: /plugins/
+Disallow: /tmp/
diff --git a/week-03/project/sql/index.html b/week-03/project/sql/index.html
new file mode 100644
index 0000000..2efb97f
--- /dev/null
+++ b/week-03/project/sql/index.html
@@ -0,0 +1 @@
+
diff --git a/week-03/project/sql/install.sql b/week-03/project/sql/install.sql
new file mode 100644
index 0000000..9eaa39a
--- /dev/null
+++ b/week-03/project/sql/install.sql
@@ -0,0 +1,141 @@
+--
+-- DATABASE STRUCTURE FOR GENERIC CMS (Adapted from Joomla!)
+-- -- subject to change --
+--
+
+SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
+SET time_zone = "+00:00";
+
+--
+-- Table structure for table `llewellyn_menu`
+--
+
+CREATE TABLE IF NOT EXISTS `llewellyn_menu` (
+ `id` int NOT NULL AUTO_INCREMENT,
+ `title` varchar(255) NOT NULL COMMENT 'The display title of the menu item.',
+ `alias` varchar(400) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT 'The SEF alias of the menu item.',
+ `path` varchar(1024) NOT NULL COMMENT 'The computed path of the menu item based on the alias field.',
+ `published` tinyint NOT NULL DEFAULT 0 COMMENT 'The published state of the menu link.',
+ `parent_id` int unsigned NOT NULL DEFAULT 1 COMMENT 'The parent menu item in the menu tree.',
+ `level` int unsigned NOT NULL DEFAULT 0 COMMENT 'The relative level in the tree.',
+ `item_id` int unsigned NOT NULL DEFAULT 0 COMMENT 'FK to llewellyn_item.id',
+ `checked_out` int unsigned COMMENT 'FK to llewellyn_users.id',
+ `checked_out_time` datetime COMMENT 'The time the menu item was checked out.',
+ `params` text NOT NULL COMMENT 'JSON encoded data for the menu item.',
+ `lft` int NOT NULL DEFAULT 0 COMMENT 'Nested set lft.',
+ `rgt` int NOT NULL DEFAULT 0 COMMENT 'Nested set rgt.',
+ `home` tinyint unsigned NOT NULL DEFAULT 0 COMMENT 'Indicates if this menu item is the home or default page.',
+ `publish_up` datetime,
+ `publish_down` datetime,
+ PRIMARY KEY (`id`),
+ KEY `idx_item` (`item_id`),
+ KEY `idx_left_right` (`lft`,`rgt`),
+ KEY `idx_alias` (`alias`(100)),
+ KEY `idx_path` (`path`(100))
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE=utf8mb4_unicode_ci AUTO_INCREMENT=102;
+
+--
+-- Table structure for table `llewellyn_session`
+--
+
+CREATE TABLE IF NOT EXISTS `llewellyn_session` (
+ `session_id` varbinary(192) NOT NULL,
+ `guest` tinyint unsigned DEFAULT 1,
+ `time` int NOT NULL DEFAULT 0,
+ `data` mediumtext,
+ `userid` int DEFAULT 0,
+ `username` varchar(150) DEFAULT '',
+ PRIMARY KEY (`session_id`),
+ KEY `userid` (`userid`),
+ KEY `time` (`time`),
+ KEY `guest` (`guest`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE=utf8mb4_unicode_ci;
+
+--
+-- Table structure for table `llewellyn_users`
+--
+
+CREATE TABLE IF NOT EXISTS `llewellyn_users` (
+ `id` int NOT NULL AUTO_INCREMENT,
+ `name` varchar(400) NOT NULL DEFAULT '',
+ `username` varchar(150) NOT NULL DEFAULT '',
+ `email` varchar(100) NOT NULL DEFAULT '',
+ `password` varchar(100) NOT NULL DEFAULT '',
+ `block` tinyint NOT NULL DEFAULT 0,
+ `sendEmail` tinyint DEFAULT 0,
+ `registerDate` datetime NOT NULL,
+ `lastvisitDate` datetime,
+ `activation` varchar(100) NOT NULL DEFAULT '',
+ `params` text NOT NULL,
+ `lastResetTime` datetime COMMENT 'Date of last password reset',
+ `resetCount` int NOT NULL DEFAULT 0 COMMENT 'Count of password resets since lastResetTime',
+ `requireReset` tinyint NOT NULL DEFAULT 0 COMMENT 'Require user to reset password on next login',
+ PRIMARY KEY (`id`),
+ KEY `idx_name` (`name`(100)),
+ KEY `idx_block` (`block`),
+ UNIQUE KEY `idx_username` (`username`),
+ KEY `email` (`email`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE=utf8mb4_unicode_ci;
+
+--
+-- Table structure for table `llewellyn_usergroups`
+--
+
+CREATE TABLE IF NOT EXISTS `llewellyn_usergroups` (
+ `id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'Primary Key',
+ `parent_id` int unsigned NOT NULL DEFAULT 0 COMMENT 'Adjacency List Reference Id',
+ `lft` int NOT NULL DEFAULT 0 COMMENT 'Nested set lft.',
+ `rgt` int NOT NULL DEFAULT 0 COMMENT 'Nested set rgt.',
+ `title` varchar(100) NOT NULL DEFAULT '',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `idx_usergroup_parent_title_lookup` (`parent_id`,`title`),
+ KEY `idx_usergroup_title_lookup` (`title`),
+ KEY `idx_usergroup_adjacency_lookup` (`parent_id`),
+ KEY `idx_usergroup_nested_set_lookup` (`lft`,`rgt`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE=utf8mb4_unicode_ci;
+
+--
+-- Table structure for table `llewellyn_user_usergroup_map`
+--
+
+CREATE TABLE IF NOT EXISTS `llewellyn_user_usergroup_map` (
+ `user_id` int unsigned NOT NULL DEFAULT 0 COMMENT 'Foreign Key to llewellyn_users.id',
+ `group_id` int unsigned NOT NULL DEFAULT 0 COMMENT 'Foreign Key to llewellyn_usergroups.id',
+ PRIMARY KEY (`user_id`,`group_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE=utf8mb4_unicode_ci;
+
+--
+-- Table structure for table `llewellyn_item`
+--
+
+CREATE TABLE IF NOT EXISTS `llewellyn_item` (
+ `id` int unsigned NOT NULL AUTO_INCREMENT,
+ `title` varchar(255) NOT NULL DEFAULT '',
+ `alias` varchar(400) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT '',
+ `introtext` mediumtext NOT NULL,
+ `fulltext` mediumtext NOT NULL,
+ `state` tinyint NOT NULL DEFAULT 0,
+ `created` datetime NOT NULL,
+ `created_by` int unsigned NOT NULL DEFAULT 0,
+ `created_by_alias` varchar(255) NOT NULL DEFAULT '',
+ `modified` datetime NOT NULL,
+ `modified_by` int unsigned NOT NULL DEFAULT 0,
+ `checked_out` int unsigned,
+ `checked_out_time` datetime NULL DEFAULT NULL,
+ `publish_up` datetime NULL DEFAULT NULL,
+ `publish_down` datetime NULL DEFAULT NULL,
+ `version` int unsigned NOT NULL DEFAULT 1,
+ `ordering` int NOT NULL DEFAULT 0,
+ `metakey` text,
+ `metadesc` text NOT NULL,
+ `hits` int unsigned NOT NULL DEFAULT 0,
+ `metadata` text NOT NULL,
+ `featured` tinyint unsigned NOT NULL DEFAULT 0 COMMENT 'Set if article is featured.',
+ PRIMARY KEY (`id`),
+ KEY `idx_checkout` (`checked_out`),
+ KEY `idx_state` (`state`),
+ KEY `idx_createdby` (`created_by`),
+ KEY `idx_alias` (`alias`(191))
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE=utf8mb4_unicode_ci;
+
+
diff --git a/week-03/project/web.config.txt b/week-03/project/web.config.txt
new file mode 100644
index 0000000..e15eb64
--- /dev/null
+++ b/week-03/project/web.config.txt
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+