diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..7285864a --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +vendor/ +sik/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..f288702d --- /dev/null +++ b/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/RoboFile.php b/RoboFile.php new file mode 100644 index 00000000..716ae767 --- /dev/null +++ b/RoboFile.php @@ -0,0 +1,103 @@ +stopOnFail(true); + } + + /** + * Map into Joomla installation. + * + * @param String $target The target joomla instance + * + * @return void + */ + public function map($target) + { + (new \Joomla\Jorobo\Tasks\Map($target))->run(); + } + + /** + * Map into Joomla installation. + * + * @param String $target The target joomla instance + * + * @return void + */ + public function umap($target) + { + (new \Joomla\Jorobo\Tasks\Umap($target))->run(); + } + + /** + * Build the joomla extension package + * + * @param array $params Additional params + * + * @return void + */ + public function build($params = ['dev' => false]) + { + (new \Joomla\Jorobo\Tasks\Build($params))->run(); + } + + /** + * Generate an extension skeleton - not implemented yet + * + * @param array $extensions Extensions to build (com_xy, mod_xy) + * + * @return void + */ + public function generate($extensions) + { + (new \Joomla\Jorobo\Tasks\Generate($extensions))->run(); + } + + /** + * Update copyright headers for this project. (Set the text up in the jorobo.ini) + * + * @return void + */ + public function headers() + { + (new \Joomla\Jorobo\Tasks\CopyrightHeader)->run(); + } + + /** + * Bump Version placeholder __DEPLOY_VERSION__ in this project. (Set the version up in the jorobo.ini) + * + * @return void + * + * @since 1.0.0 + */ + public function bump() + { + (new \Joomla\Jorobo\Tasks\BumpVersion())->run(); + } +} diff --git a/agosms-update.xml b/agosms-update.xml new file mode 100644 index 00000000..f7b4c618 --- /dev/null +++ b/agosms-update.xml @@ -0,0 +1,21 @@ + + + + pkg_agosms + pkg_agosms + agosms + package + site + 1.0.26 + https://github.com/astridx/pkg_agosms/blob/v1.0.26/README.md + + https://github.com/astridx/pkg_agosms/releases/download/v1.0.26/pkg_agosm.zip + + + stable + + Astrid Günther + https://astrid-guenther.de + + + \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 00000000..d1f6fe1a --- /dev/null +++ b/composer.json @@ -0,0 +1,12 @@ +{ + "name": "astrid/plg_fields_agosmmapwithmarker", + "require": { + "joomla-projects/jorobo": "^0.7.0" + }, + "authors": [ + { + "name": "astridx", + "email": "info@astrid-guenther.de" + } + ] +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 00000000..c7ce6722 --- /dev/null +++ b/composer.lock @@ -0,0 +1,1810 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "6401eaabeae77bc9e307e9c3ed7a34d4", + "packages": [ + { + "name": "composer/ca-bundle", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/composer/ca-bundle.git", + "reference": "8afa52cd417f4ec417b4bfe86b68106538a87660" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/8afa52cd417f4ec417b4bfe86b68106538a87660", + "reference": "8afa52cd417f4ec417b4bfe86b68106538a87660", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "ext-pcre": "*", + "php": "^5.3.2 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5", + "psr/log": "^1.0", + "symfony/process": "^2.5 || ^3.0 || ^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\CaBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", + "keywords": [ + "cabundle", + "cacert", + "certificate", + "ssl", + "tls" + ], + "time": "2018-10-18T06:09:13+00:00" + }, + { + "name": "consolidation/annotated-command", + "version": "2.10.0", + "source": { + "type": "git", + "url": "https://github.com/consolidation/annotated-command.git", + "reference": "8e7d1a05230dc1159c751809e98b74f2b7f71873" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/consolidation/annotated-command/zipball/8e7d1a05230dc1159c751809e98b74f2b7f71873", + "reference": "8e7d1a05230dc1159c751809e98b74f2b7f71873", + "shasum": "" + }, + "require": { + "consolidation/output-formatters": "^3.4", + "php": ">=5.4.0", + "psr/log": "^1", + "symfony/console": "^2.8|^3|^4", + "symfony/event-dispatcher": "^2.5|^3|^4", + "symfony/finder": "^2.5|^3|^4" + }, + "require-dev": { + "g1a/composer-test-scenarios": "^2", + "phpunit/phpunit": "^6", + "satooshi/php-coveralls": "^2", + "squizlabs/php_codesniffer": "^2.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Consolidation\\AnnotatedCommand\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "description": "Initialize Symfony Console commands from annotated command class methods.", + "time": "2018-11-15T01:46:18+00:00" + }, + { + "name": "consolidation/config", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/consolidation/config.git", + "reference": "925231dfff32f05b787e1fddb265e789b939cf4c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/consolidation/config/zipball/925231dfff32f05b787e1fddb265e789b939cf4c", + "reference": "925231dfff32f05b787e1fddb265e789b939cf4c", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^1.1.0", + "grasmash/expander": "^1", + "php": ">=5.4.0" + }, + "require-dev": { + "g1a/composer-test-scenarios": "^1", + "phpunit/phpunit": "^5", + "satooshi/php-coveralls": "^1.0", + "squizlabs/php_codesniffer": "2.*", + "symfony/console": "^2.5|^3|^4", + "symfony/yaml": "^2.8.11|^3|^4" + }, + "suggest": { + "symfony/yaml": "Required to use Consolidation\\Config\\Loader\\YamlConfigLoader" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Consolidation\\Config\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "description": "Provide configuration services for a commandline tool.", + "time": "2018-10-24T17:55:35+00:00" + }, + { + "name": "consolidation/log", + "version": "1.0.6", + "source": { + "type": "git", + "url": "https://github.com/consolidation/log.git", + "reference": "dfd8189a771fe047bf3cd669111b2de5f1c79395" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/consolidation/log/zipball/dfd8189a771fe047bf3cd669111b2de5f1c79395", + "reference": "dfd8189a771fe047bf3cd669111b2de5f1c79395", + "shasum": "" + }, + "require": { + "php": ">=5.5.0", + "psr/log": "~1.0", + "symfony/console": "^2.8|^3|^4" + }, + "require-dev": { + "g1a/composer-test-scenarios": "^1", + "phpunit/phpunit": "4.*", + "satooshi/php-coveralls": "^2", + "squizlabs/php_codesniffer": "2.*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Consolidation\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "description": "Improved Psr-3 / Psr\\Log logger based on Symfony Console components.", + "time": "2018-05-25T18:14:39+00:00" + }, + { + "name": "consolidation/output-formatters", + "version": "3.4.0", + "source": { + "type": "git", + "url": "https://github.com/consolidation/output-formatters.git", + "reference": "a942680232094c4a5b21c0b7e54c20cce623ae19" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/consolidation/output-formatters/zipball/a942680232094c4a5b21c0b7e54c20cce623ae19", + "reference": "a942680232094c4a5b21c0b7e54c20cce623ae19", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^1.1.0", + "php": ">=5.4.0", + "symfony/console": "^2.8|^3|^4", + "symfony/finder": "^2.5|^3|^4" + }, + "require-dev": { + "g1a/composer-test-scenarios": "^2", + "phpunit/phpunit": "^5.7.27", + "satooshi/php-coveralls": "^2", + "squizlabs/php_codesniffer": "^2.7", + "symfony/console": "3.2.3", + "symfony/var-dumper": "^2.8|^3|^4", + "victorjonsson/markdowndocs": "^1.3" + }, + "suggest": { + "symfony/var-dumper": "For using the var_dump formatter" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Consolidation\\OutputFormatters\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "description": "Format text by applying transformations provided by plug-in formatters.", + "time": "2018-10-19T22:35:38+00:00" + }, + { + "name": "consolidation/robo", + "version": "1.3.2", + "source": { + "type": "git", + "url": "https://github.com/consolidation/Robo.git", + "reference": "a9bd9ecf00751aa92754903c0d17612c4e840ce8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/consolidation/Robo/zipball/a9bd9ecf00751aa92754903c0d17612c4e840ce8", + "reference": "a9bd9ecf00751aa92754903c0d17612c4e840ce8", + "shasum": "" + }, + "require": { + "consolidation/annotated-command": "^2.8.2", + "consolidation/config": "^1.0.10", + "consolidation/log": "~1", + "consolidation/output-formatters": "^3.1.13", + "consolidation/self-update": "^1", + "grasmash/yaml-expander": "^1.3", + "league/container": "^2.2", + "php": ">=5.5.0", + "symfony/console": "^2.8|^3|^4", + "symfony/event-dispatcher": "^2.5|^3|^4", + "symfony/filesystem": "^2.5|^3|^4", + "symfony/finder": "^2.5|^3|^4", + "symfony/process": "^2.5|^3|^4" + }, + "replace": { + "codegyre/robo": "< 1.0" + }, + "require-dev": { + "codeception/aspect-mock": "^1|^2.1.1", + "codeception/base": "^2.3.7", + "codeception/verify": "^0.3.2", + "g1a/composer-test-scenarios": "^3", + "goaop/framework": "~2.1.2", + "goaop/parser-reflection": "^1.1.0", + "natxet/cssmin": "3.0.4", + "nikic/php-parser": "^3.1.5", + "patchwork/jsqueeze": "~2", + "pear/archive_tar": "^1.4.2", + "php-coveralls/php-coveralls": "^1", + "phpunit/php-code-coverage": "~2|~4", + "squizlabs/php_codesniffer": "^2.8" + }, + "suggest": { + "henrikbjorn/lurker": "For monitoring filesystem changes in taskWatch", + "natxet/CssMin": "For minifying CSS files in taskMinify", + "patchwork/jsqueeze": "For minifying JS files in taskMinify", + "pear/archive_tar": "Allows tar archives to be created and extracted in taskPack and taskExtract, respectively." + }, + "bin": [ + "robo" + ], + "type": "library", + "extra": { + "scenarios": { + "symfony4": { + "require": { + "symfony/console": "^4" + }, + "config": { + "platform": { + "php": "7.1.3" + } + } + }, + "symfony2": { + "require": { + "symfony/console": "^2.8" + }, + "remove": [ + "goaop/framework" + ], + "config": { + "platform": { + "php": "5.5.9" + } + }, + "scenario-options": { + "create-lockfile": "false" + } + } + }, + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Robo\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Davert", + "email": "davert.php@resend.cc" + } + ], + "description": "Modern task runner", + "time": "2018-11-22T05:43:44+00:00" + }, + { + "name": "consolidation/self-update", + "version": "1.1.5", + "source": { + "type": "git", + "url": "https://github.com/consolidation/self-update.git", + "reference": "a1c273b14ce334789825a09d06d4c87c0a02ad54" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/consolidation/self-update/zipball/a1c273b14ce334789825a09d06d4c87c0a02ad54", + "reference": "a1c273b14ce334789825a09d06d4c87c0a02ad54", + "shasum": "" + }, + "require": { + "php": ">=5.5.0", + "symfony/console": "^2.8|^3|^4", + "symfony/filesystem": "^2.5|^3|^4" + }, + "bin": [ + "scripts/release" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "SelfUpdate\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + }, + { + "name": "Alexander Menk", + "email": "menk@mestrona.net" + } + ], + "description": "Provides a self:update command for Symfony Console applications.", + "time": "2018-10-28T01:52:03+00:00" + }, + { + "name": "container-interop/container-interop", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/container-interop/container-interop.git", + "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/container-interop/container-interop/zipball/79cbf1341c22ec75643d841642dd5d6acd83bdb8", + "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8", + "shasum": "" + }, + "require": { + "psr/container": "^1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Interop\\Container\\": "src/Interop/Container/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Promoting the interoperability of container objects (DIC, SL, etc.)", + "homepage": "https://github.com/container-interop/container-interop", + "time": "2017-02-14T19:40:03+00:00" + }, + { + "name": "dflydev/dot-access-data", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "3fbd874921ab2c041e899d044585a2ab9795df8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/3fbd874921ab2c041e899d044585a2ab9795df8a", + "reference": "3fbd874921ab2c041e899d044585a2ab9795df8a", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-0": { + "Dflydev\\DotAccessData": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + } + ], + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", + "keywords": [ + "access", + "data", + "dot", + "notation" + ], + "time": "2017-01-20T21:14:22+00:00" + }, + { + "name": "grasmash/expander", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/grasmash/expander.git", + "reference": "95d6037344a4be1dd5f8e0b0b2571a28c397578f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/grasmash/expander/zipball/95d6037344a4be1dd5f8e0b0b2571a28c397578f", + "reference": "95d6037344a4be1dd5f8e0b0b2571a28c397578f", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^1.1.0", + "php": ">=5.4" + }, + "require-dev": { + "greg-1-anderson/composer-test-scenarios": "^1", + "phpunit/phpunit": "^4|^5.5.4", + "satooshi/php-coveralls": "^1.0.2|dev-master", + "squizlabs/php_codesniffer": "^2.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Grasmash\\Expander\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matthew Grasmick" + } + ], + "description": "Expands internal property references in PHP arrays file.", + "time": "2017-12-21T22:14:55+00:00" + }, + { + "name": "grasmash/yaml-expander", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/grasmash/yaml-expander.git", + "reference": "3f0f6001ae707a24f4d9733958d77d92bf9693b1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/grasmash/yaml-expander/zipball/3f0f6001ae707a24f4d9733958d77d92bf9693b1", + "reference": "3f0f6001ae707a24f4d9733958d77d92bf9693b1", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^1.1.0", + "php": ">=5.4", + "symfony/yaml": "^2.8.11|^3|^4" + }, + "require-dev": { + "greg-1-anderson/composer-test-scenarios": "^1", + "phpunit/phpunit": "^4.8|^5.5.4", + "satooshi/php-coveralls": "^1.0.2|dev-master", + "squizlabs/php_codesniffer": "^2.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Grasmash\\YamlExpander\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matthew Grasmick" + } + ], + "description": "Expands internal property references in a yaml file.", + "time": "2017-12-16T16:06:03+00:00" + }, + { + "name": "ircmaxell/password-compat", + "version": "v1.0.4", + "source": { + "type": "git", + "url": "https://github.com/ircmaxell/password_compat.git", + "reference": "5c5cde8822a69545767f7c7f3058cb15ff84614c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ircmaxell/password_compat/zipball/5c5cde8822a69545767f7c7f3058cb15ff84614c", + "reference": "5c5cde8822a69545767f7c7f3058cb15ff84614c", + "shasum": "" + }, + "require-dev": { + "phpunit/phpunit": "4.*" + }, + "type": "library", + "autoload": { + "files": [ + "lib/password.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Anthony Ferrara", + "email": "ircmaxell@php.net", + "homepage": "http://blog.ircmaxell.com" + } + ], + "description": "A compatibility library for the proposed simplified password hashing algorithm: https://wiki.php.net/rfc/password_hash", + "homepage": "https://github.com/ircmaxell/password_compat", + "keywords": [ + "hashing", + "password" + ], + "time": "2014-11-20T16:49:30+00:00" + }, + { + "name": "joomla-projects/jorobo", + "version": "0.7.0", + "source": { + "type": "git", + "url": "https://github.com/joomla-projects/jorobo.git", + "reference": "1386f7712e39f08a3c1722e514eebdd6d0580c62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/joomla-projects/jorobo/zipball/1386f7712e39f08a3c1722e514eebdd6d0580c62", + "reference": "1386f7712e39f08a3c1722e514eebdd6d0580c62", + "shasum": "" + }, + "require": { + "consolidation/robo": "~1", + "joomla/github": "~1.3", + "php": ">=5.4.0" + }, + "require-dev": { + "codeception/aspect-mock": "~0.5", + "codeception/base": "~2.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Joomla\\Jorobo\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0+" + ], + "authors": [ + { + "name": "Yves Hoppe", + "email": "yves@compojoom.com" + }, + { + "name": "Niels Braczek", + "email": "nbraczek@bsds.de" + }, + { + "name": "Niels Nübel", + "email": "niels@niels-nuebel.de" + } + ], + "description": "Tools and Tasks based on Robo.li for Joomla Extension Development and Releases", + "time": "2017-08-24T01:24:32+00:00" + }, + { + "name": "joomla/compat", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/joomla-framework/compat.git", + "reference": "f23565fe0184517778996226eb4b2333deb369c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/joomla-framework/compat/zipball/f23565fe0184517778996226eb4b2333deb369c4", + "reference": "f23565fe0184517778996226eb4b2333deb369c4", + "shasum": "" + }, + "require": { + "php": ">=5.3.10" + }, + "type": "joomla-package", + "autoload": { + "classmap": [ + "src/JsonSerializable.php", + "src/CallbackFilterIterator.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0+" + ], + "description": "Joomla Compat Package", + "homepage": "https://github.com/joomla-framework/compat", + "keywords": [ + "compat", + "framework", + "joomla" + ], + "time": "2015-02-24T00:21:06+00:00" + }, + { + "name": "joomla/github", + "version": "1.7.0", + "source": { + "type": "git", + "url": "https://github.com/joomla-framework/github-api.git", + "reference": "61580350250930083c42a24dc6a7873b28e606c1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/joomla-framework/github-api/zipball/61580350250930083c42a24dc6a7873b28e606c1", + "reference": "61580350250930083c42a24dc6a7873b28e606c1", + "shasum": "" + }, + "require": { + "joomla/http": "~1.3|~2.0", + "joomla/registry": "^1.4.5|~2.0", + "joomla/uri": "~1.0|~2.0", + "php": "^5.3.10|~7.0" + }, + "require-dev": { + "joomla/coding-standards": "~2.0@alpha", + "phpunit/phpunit": "^4.8.35|^5.4.3|~6.0" + }, + "type": "joomla-package", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Joomla\\Github\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "description": "Joomla Github Package", + "homepage": "https://github.com/joomla-framework/github-api", + "keywords": [ + "framework", + "github", + "joomla" + ], + "time": "2018-06-30T20:05:34+00:00" + }, + { + "name": "joomla/http", + "version": "1.3.2", + "source": { + "type": "git", + "url": "https://github.com/joomla-framework/http.git", + "reference": "84d1e6976d3a093c94394d2dde156987996ee3ce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/joomla-framework/http/zipball/84d1e6976d3a093c94394d2dde156987996ee3ce", + "reference": "84d1e6976d3a093c94394d2dde156987996ee3ce", + "shasum": "" + }, + "require": { + "composer/ca-bundle": "~1.0", + "joomla/uri": "~1.0|~2.0", + "php": "^5.3.10|~7.0" + }, + "require-dev": { + "joomla/coding-standards": "~2.0@alpha", + "joomla/test": "~1.0", + "phpunit/phpunit": "^4.8.35|^5.4.3|~6.0" + }, + "suggest": { + "ext-curl": "To use cURL for HTTP connections", + "joomla/registry": "Registry can be used as an alternative to using an array for the package options." + }, + "type": "joomla-package", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Joomla\\Http\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "description": "Joomla HTTP Package", + "homepage": "https://github.com/joomla-framework/http", + "keywords": [ + "framework", + "http", + "joomla" + ], + "time": "2018-04-23T11:54:19+00:00" + }, + { + "name": "joomla/registry", + "version": "1.6.2", + "source": { + "type": "git", + "url": "https://github.com/joomla-framework/registry.git", + "reference": "182eed3a56b2b7e14cef11fdbc63c253ddcfd924" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/joomla-framework/registry/zipball/182eed3a56b2b7e14cef11fdbc63c253ddcfd924", + "reference": "182eed3a56b2b7e14cef11fdbc63c253ddcfd924", + "shasum": "" + }, + "require": { + "joomla/compat": "~1.0", + "joomla/utilities": "^1.4.1|~2.0", + "php": "^5.3.10|~7.0", + "symfony/polyfill-php55": "~1.0" + }, + "require-dev": { + "joomla/coding-standards": "~2.0@alpha", + "joomla/test": "~1.0", + "phpunit/phpunit": "^4.8.35|^5.4.3|~6.0", + "symfony/yaml": "~2.0|~3.0|~4.0" + }, + "suggest": { + "symfony/yaml": "Install symfony/yaml if you require YAML support." + }, + "type": "joomla-package", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Joomla\\Registry\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "description": "Joomla Registry Package", + "homepage": "https://github.com/joomla-framework/registry", + "keywords": [ + "framework", + "joomla", + "registry" + ], + "time": "2018-06-06T16:48:30+00:00" + }, + { + "name": "joomla/string", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/joomla-framework/string.git", + "reference": "66363d317e6c020f30a70265c129281c77c43ca0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/joomla-framework/string/zipball/66363d317e6c020f30a70265c129281c77c43ca0", + "reference": "66363d317e6c020f30a70265c129281c77c43ca0", + "shasum": "" + }, + "require": { + "php": "^5.3.10|~7.0" + }, + "require-dev": { + "joomla/test": "~1.0", + "phpunit/phpunit": "~4.8|~5.0", + "squizlabs/php_codesniffer": "1.*" + }, + "suggest": { + "ext-mbstring": "For improved processing" + }, + "type": "joomla-package", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Joomla\\String\\": "src/" + }, + "files": [ + "src/phputf8/utf8.php", + "src/phputf8/ord.php", + "src/phputf8/str_ireplace.php", + "src/phputf8/str_pad.php", + "src/phputf8/str_split.php", + "src/phputf8/strcasecmp.php", + "src/phputf8/strcspn.php", + "src/phputf8/stristr.php", + "src/phputf8/strrev.php", + "src/phputf8/strspn.php", + "src/phputf8/trim.php", + "src/phputf8/ucfirst.php", + "src/phputf8/ucwords.php", + "src/phputf8/utils/ascii.php", + "src/phputf8/utils/validation.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0+" + ], + "description": "Joomla String Package", + "homepage": "https://github.com/joomla-framework/string", + "keywords": [ + "framework", + "joomla", + "string" + ], + "time": "2016-12-10T18:13:42+00:00" + }, + { + "name": "joomla/uri", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/joomla-framework/uri.git", + "reference": "848a31dc895a9c8c9d7ea67571d6a4dd634a9dc1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/joomla-framework/uri/zipball/848a31dc895a9c8c9d7ea67571d6a4dd634a9dc1", + "reference": "848a31dc895a9c8c9d7ea67571d6a4dd634a9dc1", + "shasum": "" + }, + "require": { + "php": "^5.3.10|~7.0" + }, + "require-dev": { + "joomla/coding-standards": "~2.0@alpha", + "joomla/test": "~1.0", + "phpunit/phpunit": "^4.8.35|^5.4.3|~6.0" + }, + "type": "joomla-package", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Joomla\\Uri\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "description": "Joomla Uri Package", + "homepage": "https://github.com/joomla-framework/uri", + "keywords": [ + "framework", + "joomla", + "uri" + ], + "time": "2018-07-01T00:12:15+00:00" + }, + { + "name": "joomla/utilities", + "version": "1.6.0", + "source": { + "type": "git", + "url": "https://github.com/joomla-framework/utilities.git", + "reference": "181fe644149ca0bd4a31f12212d3840147552b30" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/joomla-framework/utilities/zipball/181fe644149ca0bd4a31f12212d3840147552b30", + "reference": "181fe644149ca0bd4a31f12212d3840147552b30", + "shasum": "" + }, + "require": { + "joomla/string": "~1.3|~2.0", + "php": "^5.3.10|~7.0" + }, + "require-dev": { + "joomla/coding-standards": "~2.0@alpha", + "phpunit/phpunit": "^4.8.35|^5.4.3|~6.0" + }, + "type": "joomla-package", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Joomla\\Utilities\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "description": "Joomla Utilities Package", + "homepage": "https://github.com/joomla-framework/utilities", + "keywords": [ + "framework", + "joomla", + "utilities" + ], + "time": "2018-10-16T23:36:52+00:00" + }, + { + "name": "league/container", + "version": "2.4.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/container.git", + "reference": "43f35abd03a12977a60ffd7095efd6a7808488c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/container/zipball/43f35abd03a12977a60ffd7095efd6a7808488c0", + "reference": "43f35abd03a12977a60ffd7095efd6a7808488c0", + "shasum": "" + }, + "require": { + "container-interop/container-interop": "^1.2", + "php": "^5.4.0 || ^7.0" + }, + "provide": { + "container-interop/container-interop-implementation": "^1.2", + "psr/container-implementation": "^1.0" + }, + "replace": { + "orno/di": "~2.0" + }, + "require-dev": { + "phpunit/phpunit": "4.*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev", + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Container\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Phil Bennett", + "email": "philipobenito@gmail.com", + "homepage": "http://www.philipobenito.com", + "role": "Developer" + } + ], + "description": "A fast and intuitive dependency injection container.", + "homepage": "https://github.com/thephpleague/container", + "keywords": [ + "container", + "dependency", + "di", + "injection", + "league", + "provider", + "service" + ], + "time": "2017-05-10T09:20:27+00:00" + }, + { + "name": "psr/container", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "time": "2017-02-14T16:28:37+00:00" + }, + { + "name": "psr/log", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", + "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2018-11-20T15:27:04+00:00" + }, + { + "name": "symfony/console", + "version": "v4.2.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "4dff24e5d01e713818805c1862d2e3f901ee7dd0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/4dff24e5d01e713818805c1862d2e3f901ee7dd0", + "reference": "4dff24e5d01e713818805c1862d2e3f901ee7dd0", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/contracts": "^1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/dependency-injection": "<3.4", + "symfony/process": "<3.3" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~3.4|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/event-dispatcher": "~3.4|~4.0", + "symfony/lock": "~3.4|~4.0", + "symfony/process": "~3.4|~4.0" + }, + "suggest": { + "psr/log-implementation": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.2-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "time": "2018-11-27T07:40:44+00:00" + }, + { + "name": "symfony/contracts", + "version": "v1.0.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/contracts.git", + "reference": "1aa7ab2429c3d594dd70689604b5cf7421254cdf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/contracts/zipball/1aa7ab2429c3d594dd70689604b5cf7421254cdf", + "reference": "1aa7ab2429c3d594dd70689604b5cf7421254cdf", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "require-dev": { + "psr/cache": "^1.0", + "psr/container": "^1.0" + }, + "suggest": { + "psr/cache": "When using the Cache contracts", + "psr/container": "When using the Service contracts", + "symfony/cache-contracts-implementation": "", + "symfony/service-contracts-implementation": "", + "symfony/translation-contracts-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\": "" + }, + "exclude-from-classmap": [ + "**/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A set of abstractions extracted out of the Symfony components", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "time": "2018-12-05T08:06:11+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v4.2.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "921f49c3158a276d27c0d770a5a347a3b718b328" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/921f49c3158a276d27c0d770a5a347a3b718b328", + "reference": "921f49c3158a276d27c0d770a5a347a3b718b328", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/contracts": "^1.0" + }, + "conflict": { + "symfony/dependency-injection": "<3.4" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~3.4|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/expression-language": "~3.4|~4.0", + "symfony/stopwatch": "~3.4|~4.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.2-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com", + "time": "2018-12-01T08:52:38+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v4.2.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "2f4c8b999b3b7cadb2a69390b01af70886753710" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/2f4c8b999b3b7cadb2a69390b01af70886753710", + "reference": "2f4c8b999b3b7cadb2a69390b01af70886753710", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/polyfill-ctype": "~1.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.2-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Filesystem Component", + "homepage": "https://symfony.com", + "time": "2018-11-11T19:52:12+00:00" + }, + { + "name": "symfony/finder", + "version": "v4.2.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "e53d477d7b5c4982d0e1bfd2298dbee63d01441d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/e53d477d7b5c4982d0e1bfd2298dbee63d01441d", + "reference": "e53d477d7b5c4982d0e1bfd2298dbee63d01441d", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.2-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Finder Component", + "homepage": "https://symfony.com", + "time": "2018-11-11T19:52:12+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.10.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "e3d826245268269cd66f8326bd8bc066687b4a19" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19", + "reference": "e3d826245268269cd66f8326bd8bc066687b4a19", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + }, + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "time": "2018-08-06T14:22:27+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.10.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "c79c051f5b3a46be09205c73b80b346e4153e494" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/c79c051f5b3a46be09205c73b80b346e4153e494", + "reference": "c79c051f5b3a46be09205c73b80b346e4153e494", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "time": "2018-09-21T13:07:52+00:00" + }, + { + "name": "symfony/polyfill-php55", + "version": "v1.10.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php55.git", + "reference": "42a4c00a347625ac8853c3358c47eeadc7fd4e96" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php55/zipball/42a4c00a347625ac8853c3358c47eeadc7fd4e96", + "reference": "42a4c00a347625ac8853c3358c47eeadc7fd4e96", + "shasum": "" + }, + "require": { + "ircmaxell/password-compat": "~1.0", + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php55\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 5.5+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2018-10-31T12:13:01+00:00" + }, + { + "name": "symfony/process", + "version": "v4.2.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "2b341009ccec76837a7f46f59641b431e4d4c2b0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/2b341009ccec76837a7f46f59641b431e4d4c2b0", + "reference": "2b341009ccec76837a7f46f59641b431e4d4c2b0", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.2-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Process Component", + "homepage": "https://symfony.com", + "time": "2018-11-20T16:22:05+00:00" + }, + { + "name": "symfony/yaml", + "version": "v4.2.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "c41175c801e3edfda90f32e292619d10c27103d7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/c41175c801e3edfda90f32e292619d10c27103d7", + "reference": "c41175c801e3edfda90f32e292619d10c27103d7", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/console": "<3.4" + }, + "require-dev": { + "symfony/console": "~3.4|~4.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.2-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2018-11-11T19:52:12+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/dist/agosms-1.0.26/administrator/components/com_agosms/access.xml b/dist/agosms-1.0.26/administrator/components/com_agosms/access.xml new file mode 100644 index 00000000..c92e3322 --- /dev/null +++ b/dist/agosms-1.0.26/administrator/components/com_agosms/access.xml @@ -0,0 +1,35 @@ + + +
+ + + + + + + + + +
+
+ + + + + +
+
+ + + + + + +
+
+ + + + +
+
diff --git a/dist/agosms-1.0.26/administrator/components/com_agosms/agosms.php b/dist/agosms-1.0.26/administrator/components/com_agosms/agosms.php new file mode 100644 index 00000000..ab99a902 --- /dev/null +++ b/dist/agosms-1.0.26/administrator/components/com_agosms/agosms.php @@ -0,0 +1,21 @@ +authorise('core.manage', 'com_agosms')) +{ + throw new JAccessExceptionNotallowed(JText::_('JERROR_ALERTNOAUTHOR'), 403); +} + +$controller = JControllerLegacy::getInstance('Agosms'); +$controller->execute(JFactory::getApplication()->input->get('task')); +$controller->redirect(); diff --git a/dist/agosms-1.0.26/administrator/components/com_agosms/agosms.xml b/dist/agosms-1.0.26/administrator/components/com_agosms/agosms.xml new file mode 100644 index 00000000..78da6771 --- /dev/null +++ b/dist/agosms-1.0.26/administrator/components/com_agosms/agosms.xml @@ -0,0 +1,91 @@ + + + com_agosms + Joomla! Project + 2018-12-09 + (C) 2005 - 2018 Open Source Matters. All rights reserved. + GNU General Public License version 2 or later; see LICENSE.txt + admin@joomla.org + www.joomla.org + 1.0.26 + COM_AGOSMS_XML_DESCRIPTION + script.php + + + + sql/install.mysql.sql + sql/install.postgresql.sql + sql/install.sqlsrv.sql + + + + + sql/uninstall.mysql.sql + sql/uninstall.postgresql.sql + sql/uninstall.sqlsrv.sql + + + + + sql/updates/mysql + sql/updates/postgresql + sql/updates/sqlsrv + + + + + js +leaflet + + + + controllers +controller.php +models +agosms.php +helpers +metadata.xml +router.php +views + + + de-DE/de-DE.com_agosms.sys.ini +de-DE/de-DE.com_agosms.ini +en-GB/en-GB.com_agosms.sys.ini +en-GB/en-GB.com_agosms.ini + + + com_agosms + + + com_agosms_links + com_agosms_categories + + + controllers +controller.php +models +agosms.php +access.xml +tables +script.php +helpers +sql +config.xml +agosms.xml +views + + + de-DE/de-DE.com_agosms.sys.ini +de-DE/de-DE.com_agosms.ini +en-GB/en-GB.com_agosms.sys.ini +en-GB/en-GB.com_agosms.ini + + + + diff --git a/dist/agosms-1.0.26/administrator/components/com_agosms/config.xml b/dist/agosms-1.0.26/administrator/components/com_agosms/config.xml new file mode 100644 index 00000000..20dace2e --- /dev/null +++ b/dist/agosms-1.0.26/administrator/components/com_agosms/config.xml @@ -0,0 +1,477 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ +
+ +
+ +
+ +
+ +
diff --git a/dist/agosms-1.0.26/administrator/components/com_agosms/controller.php b/dist/agosms-1.0.26/administrator/components/com_agosms/controller.php new file mode 100644 index 00000000..0b818f93 --- /dev/null +++ b/dist/agosms-1.0.26/administrator/components/com_agosms/controller.php @@ -0,0 +1,52 @@ +input->get('view', 'agosms'); + $layout = $this->input->get('layout', 'default'); + $id = $this->input->getInt('id'); + + // Check for edit form. + if ($view == 'agosm' && $layout == 'edit' && !$this->checkEditId('com_agosms.edit.agosm', $id)) + { + // Somehow the person just went to the form - we don't allow that. + $this->setError(JText::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $id)); + $this->setMessage($this->getError(), 'error'); + $this->setRedirect(JRoute::_('index.php?option=com_agosms&view=agosms', false)); + + return false; + } + + return parent::display(); + } +} diff --git a/dist/agosms-1.0.26/administrator/components/com_agosms/controllers/agosm.php b/dist/agosms-1.0.26/administrator/components/com_agosms/controllers/agosm.php new file mode 100644 index 00000000..8c3c4f0d --- /dev/null +++ b/dist/agosms-1.0.26/administrator/components/com_agosms/controllers/agosm.php @@ -0,0 +1,130 @@ +input->getInt('filter_category_id'), 'int'); + $allow = null; + + if ($categoryId) + { + // If the category has been passed in the URL check it. + $allow = JFactory::getUser()->authorise('core.create', $this->option . '.category.' . $categoryId); + } + + if ($allow !== null) + { + return $allow; + } + + // In the absense of better information, revert to the component permissions. + return parent::allowAdd($data); + } + + /** + * Method to check if you can add a new record. + * + * @param array $data An array of input data. + * @param string $key The name of the key for the primary key. + * + * @return boolean + * + * @since 1.6 + */ + protected function allowEdit($data = array(), $key = 'id') + { + $recordId = (int) isset($data[$key]) ? $data[$key] : 0; + + // Since there is no asset tracking, fallback to the component permissions. + if (!$recordId) + { + return parent::allowEdit($data, $key); + } + + // Get the item. + $item = $this->getModel()->getItem($recordId); + + // Since there is no item, return false. + if (empty($item)) + { + return false; + } + + $user = JFactory::getUser(); + + // Check if can edit own core.edit.own. + $canEditOwn = $user->authorise('core.edit.own', $this->option . '.category.' . (int) $item->catid) && $item->created_by == $user->id; + + // Check the category core.edit permissions. + return $canEditOwn || $user->authorise('core.edit', $this->option . '.category.' . (int) $item->catid); + } + + /** + * Method to run batch operations. + * + * @param object $model The model. + * + * @return boolean True if successful, false otherwise and internal error is set. + * + * @since 1.7 + */ + public function batch($model = null) + { + JSession::checkToken() or jexit(JText::_('JINVALID_TOKEN')); + + // Set the model + $model = $this->getModel('Agosm', '', array()); + + // Preset the redirect + $this->setRedirect(JRoute::_('index.php?option=com_agosms&view=agosms' . $this->getRedirectToListAppend(), false)); + + return parent::batch($model); + } + + /** + * Function that allows child controller access to model data after the data has been saved. + * + * @param JModelLegacy $model The data model object. + * @param array $validData The validated data. + * + * @return void + * + * @since 1.6 + */ + protected function postSaveHook(JModelLegacy $model, $validData = array()) + { + $task = $this->getTask(); + + if ($task == 'save') + { + $this->setRedirect(JRoute::_('index.php?option=com_agosms&view=agosms', false)); + } + } +} diff --git a/dist/agosms-1.0.26/administrator/components/com_agosms/controllers/agosms.php b/dist/agosms-1.0.26/administrator/components/com_agosms/controllers/agosms.php new file mode 100644 index 00000000..9e3404c4 --- /dev/null +++ b/dist/agosms-1.0.26/administrator/components/com_agosms/controllers/agosms.php @@ -0,0 +1,35 @@ + true)) + { + return parent::getModel($name, $prefix, $config); + } +} diff --git a/dist/agosms-1.0.26/administrator/components/com_agosms/helpers/agosms.php b/dist/agosms-1.0.26/administrator/components/com_agosms/helpers/agosms.php new file mode 100644 index 00000000..5dddd08c --- /dev/null +++ b/dist/agosms-1.0.26/administrator/components/com_agosms/helpers/agosms.php @@ -0,0 +1,167 @@ +get('custom_fields_enable', '1')) + { + JHtmlSidebar::addEntry( + JText::_('JGLOBAL_FIELDS'), + 'index.php?option=com_fields&context=com_agosms.agosm', + $vName == 'fields.fields' + ); + + JHtmlSidebar::addEntry( + JText::_('JGLOBAL_FIELD_GROUPS'), + 'index.php?option=com_fields&view=groups&context=com_agosms.agosm', + $vName == 'fields.groups' + ); + } + } + + /** + * Adds Count Items for WebLinks Category Manager. + * + * @param stdClass[] &$items The agosms category objects. + * + * @return stdClass[] The agosms category objects. + * + * @since 3.6.0 + */ + public static function countItems(&$items) + { + $db = JFactory::getDbo(); + + foreach ($items as $item) + { + $item->count_trashed = 0; + $item->count_archived = 0; + $item->count_unpublished = 0; + $item->count_published = 0; + + $query = $db->getQuery(true) + ->select('state, COUNT(*) AS count') + ->from($db->qn('#__agosms')) + ->where($db->qn('catid') . ' = ' . (int) $item->id) + ->group('state'); + + $db->setQuery($query); + $agosms = $db->loadObjectList(); + + foreach ($agosms as $agosm) + { + if ($agosm->state == 1) + { + $item->count_published = $agosm->count; + } + elseif ($agosm->state == 0) + { + $item->count_unpublished = $agosm->count; + } + elseif ($agosm->state == 2) + { + $item->count_archived = $agosm->count; + } + elseif ($agosm->state == -2) + { + $item->count_trashed = $agosm->count; + } + } + } + + return $items; + } + + /** + * Adds Count Items for Tag Manager. + * + * @param stdClass[] &$items The agosm tag objects + * @param string $extension The name of the active view. + * + * @return stdClass[] + * + * @since 3.7.0 + */ + public static function countTagItems(&$items, $extension) + { + $db = JFactory::getDbo(); + + foreach ($items as $item) + { + $item->count_trashed = 0; + $item->count_archived = 0; + $item->count_unpublished = 0; + $item->count_published = 0; + + $query = $db->getQuery(true); + $query->select('published as state, count(*) AS count') + ->from($db->qn('#__contentitem_tag_map') . 'AS ct ') + ->where('ct.tag_id = ' . (int) $item->id) + ->where('ct.type_alias =' . $db->q($extension)) + ->join('LEFT', $db->qn('#__categories') . ' AS c ON ct.content_item_id=c.id') + ->group('state'); + + $db->setQuery($query); + $agosms = $db->loadObjectList(); + + foreach ($agosms as $agosm) + { + if ($agosm->state == 1) + { + $item->count_published = $agosm->count; + } + if ($agosm->state == 0) + { + $item->count_unpublished = $agosm->count; + } + if ($agosm->state == 2) + { + $item->count_archived = $agosm->count; + } + if ($agosm->state == -2) + { + $item->count_trashed = $agosm->count; + } + } + } + + return $items; + } +} diff --git a/dist/agosms-1.0.26/administrator/components/com_agosms/helpers/associations.php b/dist/agosms-1.0.26/administrator/components/com_agosms/helpers/associations.php new file mode 100644 index 00000000..02e69cd3 --- /dev/null +++ b/dist/agosms-1.0.26/administrator/components/com_agosms/helpers/associations.php @@ -0,0 +1,193 @@ +getType($typeName); + + $context = $this->extension . '.item'; + $catidField = 'catid'; + + if ($typeName === 'category') + { + $context = 'com_categories.item'; + $catidField = ''; + } + + // Get the associations. + $associations = JLanguageAssociations::getAssociations( + $this->extension, + $type['tables']['a'], + $context, + $id, + 'id', + 'alias', + $catidField + ); + + return $associations; + } + + /** + * Get item information + * + * @param string $typeName The item type + * @param int $id The id of item for which we need the associated items + * + * @return JTable|null + * + * @since __DEPLOY_VERSION__ + */ + public function getItem($typeName, $id) + { + if (empty($id)) + { + return null; + } + + $table = null; + + switch ($typeName) + { + case 'agosm': + $table = JTable::getInstance('Agosm', 'AgosmsTable'); + break; + + case 'category': + $table = JTable::getInstance('Category'); + break; + } + + if (empty($table)) + { + return null; + } + + $table->load($id); + + return $table; + } + + /** + * Get information about the type + * + * @param string $typeName The item type + * + * @return array Array of item types + * + * @since __DEPLOY_VERSION__ + */ + public function getType($typeName = '') + { + $fields = $this->getFieldsTemplate(); + $tables = array(); + $joins = array(); + $support = $this->getSupportTemplate(); + $title = ''; + + if (in_array($typeName, $this->itemTypes)) + { + switch ($typeName) + { + case 'agosm': + + $support['state'] = true; + $support['acl'] = true; + $support['checkout'] = true; + $support['category'] = true; + $support['save2copy'] = true; + + $tables = array( + 'a' => '#__agosms' + ); + + $title = 'agosm'; + break; + + case 'category': + $fields['created_user_id'] = 'a.created_user_id'; + $fields['ordering'] = 'a.lft'; + $fields['level'] = 'a.level'; + $fields['catid'] = ''; + $fields['state'] = 'a.published'; + + $support['state'] = true; + $support['acl'] = true; + $support['checkout'] = true; + $support['level'] = true; + + $tables = array( + 'a' => '#__categories' + ); + + $title = 'category'; + break; + } + } + + return array( + 'fields' => $fields, + 'support' => $support, + 'tables' => $tables, + 'joins' => $joins, + 'title' => $title + ); + } +} diff --git a/dist/agosms-1.0.26/administrator/components/com_agosms/helpers/html/weblink.php b/dist/agosms-1.0.26/administrator/components/com_agosms/helpers/html/weblink.php new file mode 100644 index 00000000..b4de23ea --- /dev/null +++ b/dist/agosms-1.0.26/administrator/components/com_agosms/helpers/html/weblink.php @@ -0,0 +1,95 @@ + $associated) + { + $associations[$tag] = (int) $associated->id; + } + + // Get the associated agosms items + $db = JFactory::getDbo(); + $query = $db->getQuery(true) + ->select('c.id, c.title as title') + ->select('l.sef as lang_sef, lang_code') + ->from('#__agosms as c') + ->select('cat.title as category_title') + ->join('LEFT', '#__categories as cat ON cat.id=c.catid') + ->where('c.id IN (' . implode(',', array_values($associations)) . ')') + ->join('LEFT', '#__languages as l ON c.language=l.lang_code') + ->select('l.image') + ->select('l.title as language_title'); + $db->setQuery($query); + + try + { + $items = $db->loadObjectList('id'); + } + catch (RuntimeException $e) + { + throw new Exception($e->getMessage(), 500, $e); + } + + if ($items) + { + foreach ($items as &$item) + { + $text = strtoupper($item->lang_sef); + $url = JRoute::_('index.php?option=com_agosms&task=agosm.edit&id=' . (int) $item->id); + + $tooltip = htmlspecialchars($item->title, ENT_QUOTES, 'UTF-8') . '
' . JText::sprintf('JCATEGORY_SPRINTF', $item->category_title); + $classes = 'hasPopover label label-association label-' . $item->lang_sef; + + $item->link = '' + . $text . ''; + } + } + + JHtml::_('bootstrap.popover'); + + $html = JLayoutHelper::render('joomla.content.associations', $items); + } + + return $html; + } +} diff --git a/dist/agosms-1.0.26/administrator/components/com_agosms/models/agosm.php b/dist/agosms-1.0.26/administrator/components/com_agosms/models/agosm.php new file mode 100644 index 00000000..b948f6eb --- /dev/null +++ b/dist/agosms-1.0.26/administrator/components/com_agosms/models/agosm.php @@ -0,0 +1,453 @@ +id)) + { + if ($record->state != -2) + { + return; + } + + if ($record->catid) + { + return JFactory::getUser()->authorise('core.delete', 'com_agosms.category.' . (int) $record->catid); + } + + return parent::canDelete($record); + } + } + + /** + * Method to test whether a record can be deleted. + * + * @param object $record A record object. + * + * @return boolean True if allowed to change the state of the record. Defaults to the permission for the component. + * + * @since 1.6 + */ + protected function canEditState($record) + { + if (!empty($record->catid)) + { + return JFactory::getUser()->authorise('core.edit.state', 'com_agosms.category.' . (int) $record->catid); + } + + return parent::canEditState($record); + } + + /** + * Method to get a table object, load it if necessary. + * + * @param string $type The table name. Optional. + * @param string $prefix The class prefix. Optional. + * @param array $config Configuration array for model. Optional. + * + * @return JTable A JTable object + * + * @since 1.6 + */ + public function getTable($type = 'Agosm', $prefix = 'AgosmsTable', $config = array()) + { + return JTable::getInstance($type, $prefix, $config); + } + + /** + * Abstract method for getting the form from the model. + * + * @param array $data Data for the form. + * @param boolean $loadData True if the form is to load its own data (default case), false if not. + * + * @return mixed A JForm object on success, false on failure + * + * @since 1.6 + */ + public function getForm($data = array(), $loadData = true) + { + // Get the form. + $form = $this->loadForm('com_agosms.agosm', 'agosm', array('control' => 'jform', 'load_data' => $loadData)); + + if (empty($form)) + { + return false; + } + + // Determine correct permissions to check. + if ($this->getState('agosm.id')) + { + // Existing record. Can only edit in selected categories. + $form->setFieldAttribute('catid', 'action', 'core.edit'); + } + else + { + // New record. Can only create in selected categories. + $form->setFieldAttribute('catid', 'action', 'core.create'); + } + + // Modify the form based on access controls. + if (!$this->canEditState((object) $data)) + { + // Disable fields for display. + $form->setFieldAttribute('ordering', 'disabled', 'true'); + $form->setFieldAttribute('state', 'disabled', 'true'); + $form->setFieldAttribute('publish_up', 'disabled', 'true'); + $form->setFieldAttribute('publish_down', 'disabled', 'true'); + + // Disable fields while saving. + // The controller has already verified this is a record you can edit. + $form->setFieldAttribute('ordering', 'filter', 'unset'); + $form->setFieldAttribute('state', 'filter', 'unset'); + $form->setFieldAttribute('publish_up', 'filter', 'unset'); + $form->setFieldAttribute('publish_down', 'filter', 'unset'); + } + + return $form; + } + + /** + * Method to get the data that should be injected in the form. + * + * @return array The default data is an empty array. + * + * @since 1.6 + */ + protected function loadFormData() + { + // Check the session for previously entered form data. + $data = JFactory::getApplication()->getUserState('com_agosms.edit.agosm.data', array()); + + if (empty($data)) + { + $data = $this->getItem(); + + // Prime some default values. + if ($this->getState('agosm.id') == 0) + { + $app = JFactory::getApplication(); + $data->set('catid', $app->input->get('catid', $app->getUserState('com_agosms.agosms.filter.category_id'), 'int')); + } + } + + $this->preprocessData('com_agosms.agosm', $data); + + return $data; + } + + /** + * Method to get a single record. + * + * @param integer $pk The id of the primary key. + * + * @return mixed Object on success, false on failure. + * + * @since 1.6 + */ + public function getItem($pk = null) + { + if ($item = parent::getItem($pk)) + { + // Convert the metadata field to an array. + $registry = new Registry; + $registry->loadString($item->metadata); + $item->metadata = $registry->toArray(); + + // Convert the images field to an array. + $registry = new Registry; + $registry->loadString($item->images); + $item->images = $registry->toArray(); + + // Load associated agosms items + $assoc = JLanguageAssociations::isEnabled(); + + if ($assoc) + { + $item->associations = array(); + + if ($item->id != null) + { + $associations = JLanguageAssociations::getAssociations('com_agosms', '#__agosms', 'com_agosms.item', $item->id); + + foreach ($associations as $tag => $association) + { + $item->associations[$tag] = $association->id; + } + } + } + + if (!empty($item->id)) + { + $item->tags = new JHelperTags; + $item->tags->getTagIds($item->id, 'com_agosms.agosm'); + $item->metadata['tags'] = $item->tags; + } + } + + return $item; + } + + /** + * Prepare and sanitise the table data prior to saving. + * + * @param JTable $table A reference to a JTable object. + * + * @return void + * + * @since 1.6 + */ + protected function prepareTable($table) + { + $date = JFactory::getDate(); + $user = JFactory::getUser(); + + $table->title = htmlspecialchars_decode($table->title, ENT_QUOTES); + $table->alias = JApplicationHelper::stringURLSafe($table->alias); + + if (empty($table->alias)) + { + $table->alias = JApplicationHelper::stringURLSafe($table->title); + } + + if (empty($table->id)) + { + // Set the values + + // Set ordering to the last item if not set + if (empty($table->ordering)) + { + $db = $this->getDbo(); + $query = $db->getQuery(true) + ->select('MAX(ordering)') + ->from($db->quoteName('#__agosms')); + + $db->setQuery($query); + $max = $db->loadResult(); + + $table->ordering = $max + 1; + } + else + { + // Set the values + $table->modified = $date->toSql(); + $table->modified_by = $user->id; + } + } + + // Increment the agosm version number. + $table->version++; + } + + /** + * A protected method to get a set of ordering conditions. + * + * @param JTable $table A JTable object. + * + * @return array An array of conditions to add to ordering queries. + * + * @since 1.6 + */ + protected function getReorderConditions($table) + { + $condition = array(); + $condition[] = 'catid = ' . (int) $table->catid; + + return $condition; + } + + /** + * Method to save the form data. + * + * @param array $data The form data. + * + * @return boolean True on success. + * + * @since 3.1 + */ + public function save($data) + { + $app = JFactory::getApplication(); + + JLoader::register('CategoriesHelper', JPATH_ADMINISTRATOR . '/components/com_categories/helpers/categories.php'); + + // Cast catid to integer for comparison + $catid = (int) $data['catid']; + + // Check if New Category exists + if ($catid > 0) + { + $catid = CategoriesHelper::validateCategoryId($data['catid'], 'com_agosms'); + } + + // Save New Category + if ($catid == 0 && $this->canCreateCategory()) + { + $table = array(); + $table['title'] = $data['catid']; + $table['parent_id'] = 1; + $table['extension'] = 'com_agosms'; + $table['language'] = $data['language']; + $table['published'] = 1; + + // Create new category and get catid back + $data['catid'] = CategoriesHelper::createCategory($table); + } + + // Alter the title for save as copy + if ($app->input->get('task') == 'save2copy') + { + list($name, $alias) = $this->generateNewTitle($data['catid'], $data['alias'], $data['title']); + $data['title'] = $name; + $data['alias'] = $alias; + $data['state'] = 0; + } + + return parent::save($data); + } + + /** + * Method to change the title & alias. + * + * @param integer $category_id The id of the parent. + * @param string $alias The alias. + * @param string $name The title. + * + * @return array Contains the modified title and alias. + * + * @since 3.1 + */ + protected function generateNewTitle($category_id, $alias, $name) + { + // Alter the title & alias + $table = $this->getTable(); + + while ($table->load(array('alias' => $alias, 'catid' => $category_id))) + { + if ($name == $table->title) + { + $name = JString::increment($name); + } + + $alias = JString::increment($alias, 'dash'); + } + + return array($name, $alias); + } + + /** + * Allows preprocessing of the JForm object. + * + * @param JForm $form The form object + * @param array $data The data to be merged into the form object + * @param string $group The plugin group to be executed + * + * @return void + * + * @since 3.6.0 + */ + protected function preprocessForm(JForm $form, $data, $group = 'content') + { + if ($this->canCreateCategory()) + { + $form->setFieldAttribute('catid', 'allowAdd', 'true'); + } + + // Association agosms items + if (JLanguageAssociations::isEnabled()) + { + $languages = JLanguageHelper::getContentLanguages(false, true, null, 'ordering', 'asc'); + + if (count($languages) > 1) + { + $addform = new SimpleXMLElement('
'); + $fields = $addform->addChild('fields'); + $fields->addAttribute('name', 'associations'); + $fieldset = $fields->addChild('fieldset'); + $fieldset->addAttribute('name', 'item_associations'); + + foreach ($languages as $language) + { + $field = $fieldset->addChild('field'); + $field->addAttribute('name', $language->lang_code); + $field->addAttribute('type', 'modal_agosm'); + $field->addAttribute('language', $language->lang_code); + $field->addAttribute('label', $language->title); + $field->addAttribute('translate_label', 'false'); + $field->addAttribute('select', 'true'); + $field->addAttribute('new', 'true'); + $field->addAttribute('edit', 'true'); + $field->addAttribute('clear', 'true'); + } + + $form->load($addform, false); + } + } + + parent::preprocessForm($form, $data, $group); + } + + /** + * Is the user allowed to create an on the fly category? + * + * @return bool + * + * @since 3.6.0 + */ + private function canCreateCategory() + { + return JFactory::getUser()->authorise('core.create', 'com_agosms'); + } +} diff --git a/dist/agosms-1.0.26/administrator/components/com_agosms/models/agosms.php b/dist/agosms-1.0.26/administrator/components/com_agosms/models/agosms.php new file mode 100644 index 00000000..d050730a --- /dev/null +++ b/dist/agosms-1.0.26/administrator/components/com_agosms/models/agosms.php @@ -0,0 +1,286 @@ +input->get('forcedLanguage', '', 'cmd'); + + // Adjust the context to support modal layouts. + if ($layout = $app->input->get('layout')) + { + $this->context .= '.' . $layout; + } + + // Adjust the context to support forced languages. + if ($forcedLanguage) + { + $this->context .= '.' . $forcedLanguage; + } + + // Load the filter state. + $this->setState('filter.search', $this->getUserStateFromRequest($this->context . '.filter.search', 'filter_search', '', 'string')); + $this->setState('filter.access', $this->getUserStateFromRequest($this->context . '.filter.access', 'filter_access', '', 'cmd')); + $this->setState('filter.published', $this->getUserStateFromRequest($this->context . '.filter.published', 'filter_published', '', 'string')); + $this->setState('filter.category_id', $this->getUserStateFromRequest($this->context . '.filter.category_id', 'filter_category_id', '', 'cmd')); + $this->setState('filter.language', $this->getUserStateFromRequest($this->context . '.filter.language', 'filter_language', '', 'string')); + $this->setState('filter.tag', $this->getUserStateFromRequest($this->context . '.filter.tag', 'filter_tag', '', 'string')); + $this->setState('filter.level', $this->getUserStateFromRequest($this->context . '.filter.level', 'filter_level', '', 'cmd')); + + // Load the parameters. + $params = JComponentHelper::getParams('com_agosms'); + $this->setState('params', $params); + + // Force a language. + if (!empty($forcedLanguage)) + { + $this->setState('filter.language', $forcedLanguage); + } + + // List state information. + parent::populateState($ordering, $direction); + } + + /** + * Method to get a store id based on model configuration state. + * + * This is necessary because the model is used by the component and + * different modules that might need different sets of data or different + * ordering requirements. + * + * @param string $id A prefix for the store id. + * + * @return string A store id. + * + * @since 1.6 + */ + protected function getStoreId($id = '') + { + // Compile the store id. + $id .= ':' . $this->getState('filter.search'); + $id .= ':' . $this->getState('filter.access'); + $id .= ':' . $this->getState('filter.published'); + $id .= ':' . $this->getState('filter.category_id'); + $id .= ':' . $this->getState('filter.language'); + $id .= ':' . $this->getState('filter.tag'); + $id .= ':' . $this->getState('filter.level'); + + return parent::getStoreId($id); + } + + /** + * Build an SQL query to load the list data. + * + * @return JDatabaseQuery + * + * @since 1.6 + */ + protected function getListQuery() + { + // Create a new query object. + $db = $this->getDbo(); + $query = $db->getQuery(true); + $user = JFactory::getUser(); + + // Select the required fields from the table. + $query->select( + $this->getState( + 'list.select', + 'a.id, a.title, a.alias, a.checked_out, a.checked_out_time, a.catid, a.created, a.created_by, ' . + 'a.hits, a.state, a.access, a.ordering, a.language, a.publish_up, a.publish_down' + ) + ); + $query->from($db->quoteName('#__agosms', 'a')); + + // Join over the language + $query->select($db->quoteName('l.title', 'language_title')) + ->select($db->quoteName('l.image', 'language_image')) + ->join('LEFT', $db->quoteName('#__languages', 'l') . ' ON ' . $db->qn('l.lang_code') . ' = ' . $db->qn('a.language')); + + // Join over the users for the checked out user. + $query->select($db->quoteName('uc.name', 'editor')) + ->join('LEFT', $db->quoteName('#__users', 'uc') . ' ON ' . $db->qn('uc.id') . ' = ' . $db->qn('a.checked_out')); + + // Join over the asset groups. + $query->select($db->quoteName('ag.title', 'access_level')) + ->join('LEFT', $db->quoteName('#__viewlevels', 'ag') . ' ON ' . $db->qn('ag.id') . ' = ' . $db->qn('a.access')); + + // Join over the categories. + $query->select('c.title AS category_title') + ->join('LEFT', $db->quoteName('#__categories', 'c') . ' ON ' . $db->qn('c.id') . ' = ' . $db->qn('a.catid')); + + // Join over the associations. + $assoc = JLanguageAssociations::isEnabled(); + + if ($assoc) + { + $query->select('COUNT(asso2.id)>1 AS association') + ->join('LEFT', $db->quoteName('#__associations', 'asso') . ' ON asso.id = a.id AND asso.context = ' . $db->quote('com_agosms.item')) + ->join('LEFT', $db->quoteName('#__associations', 'asso2') . ' ON asso2.key = asso.key') + ->group('a.id, l.title, l.image, uc.name, ag.title, c.title'); + } + + // Filter by access level. + if ($access = $this->getState('filter.access')) + { + $query->where($db->quoteName('a.access') . ' = ' . (int) $access); + } + + // Implement View Level Access + if (!$user->authorise('core.admin')) + { + $groups = implode(',', $user->getAuthorisedViewLevels()); + $query->where($db->quoteName('a.access') . ' IN (' . $groups . ')'); + } + + // Filter by published state + $published = $this->getState('filter.published'); + + if (is_numeric($published)) + { + $query->where($db->quoteName('a.state') . ' = ' . (int) $published); + } + elseif ($published === '') + { + $query->where('(' . $db->quoteName('a.state') . ' IN (0, 1))'); + } + + // Filter by category. + $categoryId = $this->getState('filter.category_id'); + + if (is_numeric($categoryId)) + { + $query->where($db->quoteName('a.catid') . ' = ' . (int) $categoryId); + } + + // Filter on the level. + if ($level = $this->getState('filter.level')) + { + $query->where($db->quoteName('c.level') . ' <= ' . (int) $level); + } + + // Filter by search in title + $search = $this->getState('filter.search'); + + if (!empty($search)) + { + if (stripos($search, 'id:') === 0) + { + $query->where($db->quoteName('a.id') . ' = ' . (int) substr($search, 3)); + } + else + { + $search = $db->quote('%' . str_replace(' ', '%', $db->escape(trim($search), true) . '%')); + $query->where('(' . $db->quoteName('a.title') . ' LIKE ' . $search . ' OR ' . $db->quoteName('a.alias') . ' LIKE ' . $search . ')'); + } + } + + // Filter on the language. + if ($language = $this->getState('filter.language')) + { + $query->where($db->quoteName('a.language') . ' = ' . $db->quote($language)); + } + + $tagId = $this->getState('filter.tag'); + + // Filter by a single tag. + if (is_numeric($tagId)) + { + $query->where($db->quoteName('tagmap.tag_id') . ' = ' . (int) $tagId) + ->join( + 'LEFT', $db->quoteName('#__contentitem_tag_map', 'tagmap') + . ' ON ' . $db->quoteName('tagmap.content_item_id') . ' = ' . $db->quoteName('a.id') + . ' AND ' . $db->quoteName('tagmap.type_alias') . ' = ' . $db->quote('com_agosms.agosm') + ); + } + + // Add the list ordering clause. + $orderCol = $this->state->get('list.ordering', 'a.title'); + $orderDirn = $this->state->get('list.direction', 'ASC'); + + if ($orderCol == 'a.ordering' || $orderCol == 'category_title') + { + $orderCol = 'c.title ' . $orderDirn . ', a.ordering'; + } + + $query->order($db->escape($orderCol . ' ' . $orderDirn)); + + return $query; + } +} diff --git a/dist/agosms-1.0.26/administrator/components/com_agosms/models/fields/modal/agosm.php b/dist/agosms-1.0.26/administrator/components/com_agosms/models/fields/modal/agosm.php new file mode 100644 index 00000000..d7f494ae --- /dev/null +++ b/dist/agosms-1.0.26/administrator/components/com_agosms/models/fields/modal/agosm.php @@ -0,0 +1,258 @@ +element['new'] == 'true'); + $allowEdit = ((string) $this->element['edit'] == 'true'); + $allowClear = ((string) $this->element['clear'] != 'false'); + $allowSelect = ((string) $this->element['select'] != 'false'); + + // Load language + JFactory::getLanguage()->load('com_agosms', JPATH_ADMINISTRATOR); + + // The active agosm id field. + $value = (int) $this->value > 0 ? (int) $this->value : ''; + + // Create the modal id. + $modalId = 'Agosm_' . $this->id; + + // Add the modal field script to the document head. + JHtml::_('jquery.framework'); + JHtml::_('script', 'system/modal-fields.js', array('version' => 'auto', 'relative' => true)); + + // Script to proxy the select modal function to the modal-fields.js file. + if ($allowSelect) + { + static $scriptSelect = null; + + if (is_null($scriptSelect)) + { + $scriptSelect = array(); + } + + if (!isset($scriptSelect[$this->id])) + { + JFactory::getDocument()->addScriptDeclaration(" + function jSelectAgosm_" . $this->id . "(id, title, catid, object, url, language) { + window.processModalSelect('Agosm', '" . $this->id . "', id, title, catid, object, url, language); + } + "); + $scriptSelect[$this->id] = true; + } + } + + // Setup variables for display. + $linkAgosms = 'index.php?option=com_agosms&view=agosms&layout=modal&tmpl=component&' . JSession::getFormToken() . '=1'; + $linkAgosm = 'index.php?option=com_agosms&view=agosm&layout=modal&tmpl=component&' . JSession::getFormToken() . '=1'; + $modalTitle = JText::_('COM_AGOSMS_CHANGE_AGOSM'); + + if (isset($this->element['language'])) + { + $linkAgosms .= '&forcedLanguage=' . $this->element['language']; + $linkAgosm .= '&forcedLanguage=' . $this->element['language']; + $modalTitle .= ' — ' . $this->element['label']; + } + + $urlSelect = $linkAgosms . '&function=jSelectAgosm_' . $this->id; + $urlEdit = $linkAgosm . '&task=agosm.edit&id=\' + document.getElementById("' . $this->id . '_id").value + \''; + $urlNew = $linkAgosm . '&task=agosm.add'; + + if ($value) + { + $db = JFactory::getDbo(); + $query = $db->getQuery(true) + ->select($db->quoteName('title')) + ->from($db->quoteName('#__agosms')) + ->where($db->quoteName('id') . ' = ' . (int) $value); + $db->setQuery($query); + try + { + $title = $db->loadResult(); + } + catch (RuntimeException $e) + { + JError::raiseWarning(500, $e->getMessage()); + } + } + $title = empty($title) ? JText::_('COM_AGOSMS_SELECT_A_AGOSM') : htmlspecialchars($title, ENT_QUOTES, 'UTF-8'); + + // The current agosm display field. + $html = ''; + $html .= ''; + + // Select agosm button + if ($allowSelect) + { + $html .= '' + . ' ' . JText::_('JSELECT') + . ''; + } + // New agosm button + if ($allowNew) + { + $html .= '' + . ' ' . JText::_('JACTION_CREATE') + . ''; + } + // Edit agosm button + if ($allowEdit) + { + $html .= '' + . ' ' . JText::_('JACTION_EDIT') + . ''; + } + // Clear agosm button + if ($allowClear) + { + $html .= '' + . '' . JText::_('JCLEAR') + . ''; + } + $html .= ''; + + // Select agosm modal + if ($allowSelect) + { + $html .= JHtml::_( + 'bootstrap.renderModal', + 'ModalSelect' . $modalId, + array( + 'title' => $modalTitle, + 'url' => $urlSelect, + 'height' => '400px', + 'width' => '800px', + 'bodyHeight' => '70', + 'modalWidth' => '80', + 'footer' => '', + ) + ); + } + + // New agosm modal + if ($allowNew) + { + $html .= JHtml::_( + 'bootstrap.renderModal', + 'ModalNew' . $modalId, + array( + 'title' => JText::_('COM_AGOSMS_NEW_AGOSM'), + 'backdrop' => 'static', + 'keyboard' => false, + 'closeButton' => false, + 'url' => $urlNew, + 'height' => '400px', + 'width' => '800px', + 'bodyHeight' => '70', + 'modalWidth' => '80', + 'footer' => '' + . '' + . '', + ) + ); + } + + // Edit agosm modal + if ($allowEdit) + { + $html .= JHtml::_( + 'bootstrap.renderModal', + 'ModalEdit' . $modalId, + array( + 'title' => JText::_('COM_AGOSMS_EDIT_AGOSM'), + 'backdrop' => 'static', + 'keyboard' => false, + 'closeButton' => false, + 'url' => $urlEdit, + 'height' => '400px', + 'width' => '800px', + 'bodyHeight' => '70', + 'modalWidth' => '80', + 'footer' => '' + . '' + . '', + ) + ); + } + // Note: class='required' for client side validation. + $class = $this->required ? ' class="required modal-value"' : ''; + $html .= ''; + return $html; + } + + /** + * Method to get the field label markup. + * + * @return string The field label markup. + * + * @since __DEPLOY_VERSION__ + */ + protected function getLabel() + { + return str_replace($this->id, $this->id . '_id', parent::getLabel()); + } +} diff --git a/dist/agosms-1.0.26/administrator/components/com_agosms/models/forms/agosm.xml b/dist/agosms-1.0.26/administrator/components/com_agosms/models/forms/agosm.xml new file mode 100644 index 00000000..40b68ff5 --- /dev/null +++ b/dist/agosms-1.0.26/administrator/components/com_agosms/models/forms/agosm.xml @@ -0,0 +1,592 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + // + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + + + + + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ + + + + + + + + +
+
+
diff --git a/dist/agosms-1.0.26/administrator/components/com_agosms/models/forms/filter_agosms.xml b/dist/agosms-1.0.26/administrator/components/com_agosms/models/forms/filter_agosms.xml new file mode 100644 index 00000000..a6b61d85 --- /dev/null +++ b/dist/agosms-1.0.26/administrator/components/com_agosms/models/forms/filter_agosms.xml @@ -0,0 +1,108 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/dist/agosms-1.0.26/administrator/components/com_agosms/script.php b/dist/agosms-1.0.26/administrator/components/com_agosms/script.php new file mode 100644 index 00000000..32b52289 --- /dev/null +++ b/dist/agosms-1.0.26/administrator/components/com_agosms/script.php @@ -0,0 +1,276 @@ +load(array('extension' => 'com_agosms', 'title' => 'Uncategorised'))) + { + $category->extension = 'com_agosms'; + $category->title = 'Uncategorised'; + $category->description = ''; + $category->published = 1; + $category->access = 1; + $category->params = '{"category_layout":"","image":""}'; + $category->metadata = '{"author":"","robots":""}'; + $category->metadesc = ''; + $category->metakey = ''; + $category->language = '*'; + $category->checked_out_time = JFactory::getDbo()->getNullDate(); + $category->version = 1; + $category->hits = 0; + $category->modified_user_id = 0; + $category->checked_out = 0; + + // Set the location in the tree + $category->setLocation(1, 'last-child'); + + // Check to make sure our data is valid + if (!$category->check()) + { + JFactory::getApplication()->enqueueMessage(JText::sprintf('COM_AGOSMS_ERROR_INSTALL_CATEGORY', $category->getError())); + + return; + } + + // Now store the category + if (!$category->store(true)) + { + JFactory::getApplication()->enqueueMessage(JText::sprintf('COM_AGOSMS_ERROR_INSTALL_CATEGORY', $category->getError())); + + return; + } + + // Build the path for our category + $category->rebuildPath($category->id); + } + } + + /** + * Method to run after the install routine. + * + * @param string $type The action being performed + * @param JInstallerAdapterComponent $parent The class calling this method + * + * @return void + * + * @since 3.4.1 + */ + public function postflight($type, $parent) + { + // Only execute database changes on MySQL databases + $dbName = JFactory::getDbo()->name; + + if (strpos($dbName, 'mysql') !== false) + { + // Add Missing Table Colums if needed + $this->addColumnsIfNeeded(); + + // Drop the Table Colums if needed + $this->dropColumnsIfNeeded(); + } + + // Insert missing UCM Records if needed + $this->insertMissingUcmRecords(); + } + + /** + * Method to insert missing records for the UCM tables + * + * @return void + * + * @since 3.4.1 + */ + private function insertMissingUcmRecords() + { + // Insert the rows in the #__content_types table if they don't exist already + $db = JFactory::getDbo(); + + // Get the type ID for a Agosm + $query = $db->getQuery(true); + $query->select($db->quoteName('type_id')) + ->from($db->quoteName('#__content_types')) + ->where($db->quoteName('type_alias') . ' = ' . $db->quote('com_agosms.agosm')); + $db->setQuery($query); + + $agosmTypeId = $db->loadResult(); + + // Get the type ID for a Agosm Category + $query->clear('where'); + $query->where($db->quoteName('type_alias') . ' = ' . $db->quote('com_agosms.category')); + $db->setQuery($query); + + $categoryTypeId = $db->loadResult(); + + // Set the table columns to insert table to + $columnsArray = array( + $db->quoteName('type_title'), + $db->quoteName('type_alias'), + $db->quoteName('table'), + $db->quoteName('rules'), + $db->quoteName('field_mappings'), + $db->quoteName('router'), + $db->quoteName('content_history_options'), + ); + + // If we have no type id for com_agosms.agosm insert it + if (!$agosmTypeId) + { + // Insert the data. + $query->clear(); + $query->insert($db->quoteName('#__content_types')); + $query->columns($columnsArray); + $query->values( + $db->quote('Agosm') . ', ' + . $db->quote('com_agosms.agosm') . ', ' + . $db->quote( + '{"special":{"dbtable":"#__agosms","key":"id","type":"Agosm","prefix":"AgosmsTable","config":"array()"}, + "common":{"dbtable":"#__ucm_content","key":"ucm_id","type":"Corecontent","prefix":"JTable","config":"array()"}}') . ', ' + . $db->quote('') . ', ' + . $db->quote( + '{"common":{"core_content_item_id":"id","core_title":"title","core_state":"state","core_alias":"alias", + "core_created_time":"created","core_modified_time":"modified","core_body":"description", "core_hits":"hits", + "core_publish_up":"publish_up","core_publish_down":"publish_down","core_access":"access", "core_params":"params", + "core_featured":"featured", "core_metadata":"metadata", "core_language":"language", "core_images":"images", "core_urls":"url", + "core_version":"version", "core_ordering":"ordering", "core_metakey":"metakey", "core_metadesc":"metadesc", + "core_catid":"catid", "core_xreference":"xreference", "asset_id":"null"}, "special":{}}') . ', ' + . $db->quote('AgosmsHelperRoute::getAgosmRoute') . ', ' + . $db->quote( + '{"formFile":"administrator\\/components\\/com_agosms\\/models\\/forms\\/agosm.xml", + "hideFields":["asset_id","checked_out","checked_out_time","version","featured","images"], "ignoreChanges":["modified_by", + "modified", "checked_out", "checked_out_time", "version", "hits"], "convertToInt":["publish_up", "publish_down", "featured", + "ordering"], "displayLookup":[{"sourceColumn":"catid","targetTable":"#__categories","targetColumn":"id","displayColumn":"title"}, + {"sourceColumn":"created_by","targetTable":"#__users","targetColumn":"id","displayColumn":"name"}, + {"sourceColumn":"access","targetTable":"#__viewlevels","targetColumn":"id","displayColumn":"title"}, + {"sourceColumn":"modified_by","targetTable":"#__users","targetColumn":"id","displayColumn":"name"} ]}') + ); + + $db->setQuery($query); + $db->execute(); + } + + // If we have no type id for com_agosms.category insert it + if (!$categoryTypeId) + { + // Insert the data. + $query->clear(); + $query->insert($db->quoteName('#__content_types')); + $query->columns($columnsArray); + $query->values( + $db->quote('Agosms Category') . ', ' + . $db->quote('com_agosms.category') . ', ' + . $db->quote(' + {"special":{"dbtable":"#__categories","key":"id","type":"Category","prefix":"JTable","config":"array()"}, + "common":{"dbtable":"#__ucm_content","key":"ucm_id","type":"Corecontent","prefix":"JTable","config":"array()"}}') . ', ' + . $db->quote('') . ', ' + . $db->quote(' + {"common":{"core_content_item_id":"id","core_title":"title","core_state":"published","core_alias":"alias", + "core_created_time":"created_time","core_modified_time":"modified_time","core_body":"description", + "core_hits":"hits","core_publish_up":"null","core_publish_down":"null","core_access":"access", + "core_params":"params", "core_featured":"null", "core_metadata":"metadata", "core_language":"language", + "core_images":"null", "core_urls":"null", "core_version":"version", "core_ordering":"null", "core_metakey":"metakey", + "core_metadesc":"metadesc", "core_catid":"parent_id", "core_xreference":"null", "asset_id":"asset_id"}, + "special":{"parent_id":"parent_id","lft":"lft","rgt":"rgt","level":"level","path":"path","extension":"extension","note":"note"}}') . ', ' + . $db->quote('AgosmsHelperRoute::getCategoryRoute') . ', ' + . $db->quote(' + {"formFile":"administrator\\/components\\/com_categories\\/models\\/forms\\/category.xml", + "hideFields":["asset_id","checked_out","checked_out_time","version","lft","rgt","level","path","extension"], + "ignoreChanges":["modified_user_id", "modified_time", "checked_out", "checked_out_time", "version", + "hits", "path"],"convertToInt":["publish_up", "publish_down"], + "displayLookup":[{"sourceColumn":"created_user_id","targetTable":"#__users","targetColumn":"id", + "displayColumn":"name"},{"sourceColumn":"access","targetTable":"#__viewlevels","targetColumn":"id", + "displayColumn":"title"},{"sourceColumn":"modified_user_id","targetTable":"#__users","targetColumn":"id", + "displayColumn":"name"},{"sourceColumn":"parent_id","targetTable":"#__categories","targetColumn":"id", + "displayColumn":"title"}]}') + ); + + $db->setQuery($query); + $db->execute(); + } + } + + /** + * Method to drop colums from #__agosms if they still there. + * + * @return void + * + * @since 3.4.1 + */ + private function dropColumnsIfNeeded() + { + $oldColumns = array( + 'sid', + 'date', + 'archived', + 'approved', + ); + + $db = JFactory::getDbo(); + $table = $db->getTableColumns('#__agosms'); + + $columns = array_intersect($oldColumns, array_keys($table)); + + foreach ($columns as $column) + { + $sql = 'ALTER TABLE ' . $db->quoteName('#__agosms') . ' DROP COLUMN ' . $db->quoteName($column); + $db->setQuery($sql); + $db->execute(); + } + } + + /** + * Method to add colums from #__agosms if they are missing. + * + * @return void + * + * @since 3.4.1 + */ + private function addColumnsIfNeeded() + { + $db = JFactory::getDbo(); + $table = $db->getTableColumns('#__agosms'); + + if (!array_key_exists('version', $table)) + { + $sql = 'ALTER TABLE ' . $db->quoteName('#__agosms') . ' ADD COLUMN ' . $db->quoteName('version') . " int(10) unsigned NOT NULL DEFAULT '1'"; + $db->setQuery($sql); + $db->execute(); + } + + if (!array_key_exists('images', $table)) + { + $sql = 'ALTER TABLE ' . $db->quoteName('#__agosms') . ' ADD COLUMN ' . $db->quoteName('images') . ' text NOT NULL'; + $db->setQuery($sql); + $db->execute(); + } + } +} diff --git a/dist/agosms-1.0.26/administrator/components/com_agosms/sql/install.mysql.sql b/dist/agosms-1.0.26/administrator/components/com_agosms/sql/install.mysql.sql new file mode 100644 index 00000000..bffe8235 --- /dev/null +++ b/dist/agosms-1.0.26/administrator/components/com_agosms/sql/install.mysql.sql @@ -0,0 +1,76 @@ +-- +-- Insert data into table `#__content_types` for UCM functions +-- + +INSERT INTO `#__content_types` (`type_title`, `type_alias`, `table`, `rules`, `field_mappings`, `router`, `content_history_options`) VALUES +('Agosm', 'com_agosms.agosm', '{"special":{"dbtable":"#__agosms","key":"id","type":"Agosm","prefix":"AgosmsTable","config":"array()"},"common":{"dbtable":"#__ucm_content","key":"ucm_id","type":"Corecontent","prefix":"JTable","config":"array()"}}', '', '{"common":{"core_content_item_id":"id","core_title":"title","core_state":"state","core_alias":"alias","core_created_time":"created","core_modified_time":"modified","core_body":"description", "core_hits":"hits","core_publish_up":"publish_up","core_publish_down":"publish_down","core_access":"access", "core_params":"params", "core_featured":"featured", "core_metadata":"metadata", "core_language":"language", "core_images":"images", "core_urls":"url", "core_version":"version", "core_ordering":"ordering", "core_metakey":"metakey", "core_metadesc":"metadesc", "core_catid":"catid", "core_xreference":"xreference", "asset_id":"null"}, "special":{}}', 'AgosmsHelperRoute::getAgosmRoute', '{"formFile":"administrator\\/components\\/com_agosms\\/models\\/forms\\/agosm.xml", "hideFields":["asset_id","checked_out","checked_out_time","version","featured","images"], "ignoreChanges":["modified_by", "modified", "checked_out", "checked_out_time", "version", "hits"], "convertToInt":["publish_up", "publish_down", "featured", "ordering"], "displayLookup":[{"sourceColumn":"catid","targetTable":"#__categories","targetColumn":"id","displayColumn":"title"},{"sourceColumn":"created_by","targetTable":"#__users","targetColumn":"id","displayColumn":"name"},{"sourceColumn":"access","targetTable":"#__viewlevels","targetColumn":"id","displayColumn":"title"},{"sourceColumn":"modified_by","targetTable":"#__users","targetColumn":"id","displayColumn":"name"} ]}'), +('Agosms Category', 'com_agosms.category', '{"special":{"dbtable":"#__categories","key":"id","type":"Category","prefix":"JTable","config":"array()"},"common":{"dbtable":"#__ucm_content","key":"ucm_id","type":"Corecontent","prefix":"JTable","config":"array()"}}', '', '{"common":{"core_content_item_id":"id","core_title":"title","core_state":"published","core_alias":"alias","core_created_time":"created_time","core_modified_time":"modified_time","core_body":"description", "core_hits":"hits","core_publish_up":"null","core_publish_down":"null","core_access":"access", "core_params":"params", "core_featured":"null", "core_metadata":"metadata", "core_language":"language", "core_images":"null", "core_urls":"null", "core_version":"version", "core_ordering":"null", "core_metakey":"metakey", "core_metadesc":"metadesc", "core_catid":"parent_id", "core_xreference":"null", "asset_id":"asset_id"}, "special":{"parent_id":"parent_id","lft":"lft","rgt":"rgt","level":"level","path":"path","extension":"extension","note":"note"}}', 'AgosmsHelperRoute::getCategoryRoute', '{"formFile":"administrator\\/components\\/com_categories\\/models\\/forms\\/category.xml", "hideFields":["asset_id","checked_out","checked_out_time","version","lft","rgt","level","path","extension"], "ignoreChanges":["modified_user_id", "modified_time", "checked_out", "checked_out_time", "version", "hits", "path"],"convertToInt":["publish_up", "publish_down"], "displayLookup":[{"sourceColumn":"created_user_id","targetTable":"#__users","targetColumn":"id","displayColumn":"name"},{"sourceColumn":"access","targetTable":"#__viewlevels","targetColumn":"id","displayColumn":"title"},{"sourceColumn":"modified_user_id","targetTable":"#__users","targetColumn":"id","displayColumn":"name"},{"sourceColumn":"parent_id","targetTable":"#__categories","targetColumn":"id","displayColumn":"title"}]}'); + +-- +-- Table structure for table `#__agosms` +-- + +CREATE TABLE IF NOT EXISTS `#__agosms` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `catid` int(11) NOT NULL DEFAULT 0, + `title` varchar(250) NOT NULL DEFAULT '', + `alias` varchar(400) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT '', + `url` varchar(250) NOT NULL DEFAULT '', + `coordinates` varchar(250) NOT NULL DEFAULT '', + `showdefaultpin` tinyint(1) NOT NULL DEFAULT 0, + `customPinPath` varchar(250) NOT NULL DEFAULT '', + `customPinSize` varchar(250) NOT NULL DEFAULT '', + `customPinShadowPath` varchar(250) NOT NULL DEFAULT '', + `customPinShadowSize` varchar(250) NOT NULL DEFAULT '', + `customPinOffset` varchar(250) NOT NULL DEFAULT '', + `customPinPopupOffset` varchar(250) NOT NULL DEFAULT '', + `showpopup` tinyint(1) NOT NULL DEFAULT 0, + `customiconstart_icon` varchar(250) NOT NULL DEFAULT '', + `customiconstart_markercolor` varchar(250) NOT NULL DEFAULT '', + `customiconstart_iconcolor` varchar(250) NOT NULL DEFAULT '', + `customiconstart_extraclasses` varchar(250) NOT NULL DEFAULT '', + `customiconstart_spin` varchar(250) NOT NULL DEFAULT '', + + `awesomeicon_icon` varchar(250) NOT NULL DEFAULT '', + `awesomeicon_markercolor` varchar(250) NOT NULL DEFAULT '', + `awesomeicon_iconcolor` varchar(250) NOT NULL DEFAULT '', + `awesomeicon_extraclasses` varchar(250) NOT NULL DEFAULT '', + `awesomeicon_spin` varchar(250) NOT NULL DEFAULT '', + + + + + `popuptext` text NOT NULL, + `description` text NOT NULL, + `hits` int(11) NOT NULL DEFAULT 0, + `state` tinyint(1) NOT NULL DEFAULT 0, + `checked_out` int(11) NOT NULL DEFAULT 0, + `checked_out_time` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', + `ordering` int(11) NOT NULL DEFAULT 0, + `access` int(11) NOT NULL DEFAULT 1, + `params` text NOT NULL, + `language` char(7) NOT NULL DEFAULT '', + `created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', + `created_by` int(10) unsigned NOT NULL DEFAULT 0, + `created_by_alias` varchar(255) NOT NULL DEFAULT '', + `modified` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', + `modified_by` int(10) unsigned NOT NULL DEFAULT 0, + `metakey` text NOT NULL, + `metadesc` text NOT NULL, + `metadata` text NOT NULL, + `featured` tinyint(3) unsigned NOT NULL DEFAULT 0 COMMENT 'Set if link is featured.', + `xreference` varchar(50) NOT NULL COMMENT 'A reference to enable linkages to external data sets.', + `publish_up` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', + `publish_down` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', + `version` int(10) unsigned NOT NULL DEFAULT 1, + `images` text NOT NULL, + PRIMARY KEY (`id`), + KEY `idx_access` (`access`), + KEY `idx_checkout` (`checked_out`), + KEY `idx_state` (`state`), + KEY `idx_catid` (`catid`), + KEY `idx_createdby` (`created_by`), + KEY `idx_featured_catid` (`featured`,`catid`), + KEY `idx_language` (`language`), + KEY `idx_xreference` (`xreference`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE=utf8mb4_unicode_ci; diff --git a/dist/agosms-1.0.26/administrator/components/com_agosms/sql/uninstall.mysql.sql b/dist/agosms-1.0.26/administrator/components/com_agosms/sql/uninstall.mysql.sql new file mode 100644 index 00000000..f3becde0 --- /dev/null +++ b/dist/agosms-1.0.26/administrator/components/com_agosms/sql/uninstall.mysql.sql @@ -0,0 +1,3 @@ +DELETE FROM `#__content_types` WHERE `type_alias` IN ('com_agosms.agosm', 'com_agosms.category'); + +DROP TABLE IF EXISTS `#__agosms`; diff --git a/dist/agosms-1.0.26/administrator/components/com_agosms/sql/updates/mysql/0.sql b/dist/agosms-1.0.26/administrator/components/com_agosms/sql/updates/mysql/0.sql new file mode 100644 index 00000000..e69de29b diff --git a/dist/agosms-1.0.26/administrator/components/com_agosms/tables/agosm.php b/dist/agosms-1.0.26/administrator/components/com_agosms/tables/agosm.php new file mode 100644 index 00000000..8d223103 --- /dev/null +++ b/dist/agosms-1.0.26/administrator/components/com_agosms/tables/agosm.php @@ -0,0 +1,200 @@ +setColumnAlias('published', 'state'); + + JTableObserverTags::createObserver($this, array('typeAlias' => 'com_agosms.agosm')); + JTableObserverContenthistory::createObserver($this, array('typeAlias' => 'com_agosms.agosm')); + } + + /** + * Overload the store method for the Agosms table. + * + * @param boolean $updateNulls Toggle whether null values should be updated. + * + * @return boolean True on success, false on failure. + * + * @since 1.6 + */ + public function store($updateNulls = false) + { + $date = JFactory::getDate(); + $user = JFactory::getUser(); + + $this->modified = $date->toSql(); + + if ($this->id) + { + // Existing item + $this->modified_by = $user->id; + } + else + { + // New agosm. A agosm created and created_by field can be set by the user, + // so we don't touch either of these if they are set. + if (!(int) $this->created) + { + $this->created = $date->toSql(); + } + + if (empty($this->created_by)) + { + $this->created_by = $user->id; + } + } + + // Set publish_up to null date if not set + if (!$this->publish_up) + { + $this->publish_up = $this->getDbo()->getNullDate(); + } + + // Set publish_down to null date if not set + if (!$this->publish_down) + { + $this->publish_down = $this->getDbo()->getNullDate(); + } + + // Verify that the alias is unique + $table = JTable::getInstance('Agosm', 'AgosmsTable'); + + if ($table->load(array('language' => $this->language, 'alias' => $this->alias, 'catid' => $this->catid)) && ($table->id != $this->id || $this->id == 0)) + { + $this->setError(JText::_('COM_AGOSMS_ERROR_UNIQUE_ALIAS')); + + return false; + } + + // Convert IDN urls to punycode + $this->url = JStringPunycode::urlToPunycode($this->url); + + return parent::store($updateNulls); + } + + /** + * Overloaded check method to ensure data integrity. + * + * @return boolean True on success. + * + * @since 1.5 + */ + public function check() + { + if (JFilterInput::checkAttribute(array('href', $this->url))) + { + $this->setError(JText::_('COM_AGOSMS_ERR_TABLES_PROVIDE_URL')); + + return false; + } + + // Check for valid name + if (trim($this->title) == '') + { + $this->setError(JText::_('COM_AGOSMS_ERR_TABLES_TITLE')); + return false; + } + + // Check for existing name + $db = $this->getDbo(); + + $query = $db->getQuery(true) + ->select($db->quoteName('id')) + ->from($db->quoteName('#__agosms')) + ->where($db->quoteName('title') . ' = ' . $db->quote($this->title)) + ->where($db->quoteName('language') . ' = ' . $db->quote($this->language)) + ->where($db->quoteName('catid') . ' = ' . (int) $this->catid); + $db->setQuery($query); + + $xid = (int) $db->loadResult(); + + if ($xid && $xid != (int) $this->id) + { + $this->setError(JText::_('COM_AGOSMS_ERR_TABLES_NAME')); + + return false; + } + + if (empty($this->alias)) + { + $this->alias = $this->title; + } + + $this->alias = JApplicationHelper::stringURLSafe($this->alias, $this->language); + + if (trim(str_replace('-', '', $this->alias)) == '') + { + $this->alias = JFactory::getDate()->format("Y-m-d-H-i-s"); + } + + // Check the publish down date is not earlier than publish up. + if ($this->publish_down > $db->getNullDate() && $this->publish_down < $this->publish_up) + { + $this->setError(JText::_('JGLOBAL_START_PUBLISH_AFTER_FINISH')); + + return false; + } + + /* + * Clean up keywords -- eliminate extra spaces between phrases + * and cr (\r) and lf (\n) characters from string + */ + if (!empty($this->metakey)) + { + // Array of characters to remove + $bad_characters = array("\n", "\r", "\"", "<", ">"); + $after_clean = JString::str_ireplace($bad_characters, "", $this->metakey); + $keys = explode(',', $after_clean); + $clean_keys = array(); + + foreach ($keys as $key) + { + // Ignore blank keywords + if (trim($key)) + { + $clean_keys[] = trim($key); + } + } + + // Put array back together delimited by ", " + $this->metakey = implode(", ", $clean_keys); + } + + return true; + } +} diff --git a/dist/agosms-1.0.26/administrator/components/com_agosms/views/agosm/tmpl/edit.php b/dist/agosms-1.0.26/administrator/components/com_agosms/views/agosm/tmpl/edit.php new file mode 100644 index 00000000..7ce53bd3 --- /dev/null +++ b/dist/agosms-1.0.26/administrator/components/com_agosms/views/agosm/tmpl/edit.php @@ -0,0 +1,102 @@ + 0 )); + +$app = JFactory::getApplication(); +$input = $app->input; + +$assoc = JLanguageAssociations::isEnabled(); + +// Fieldsets to not automatically render by /layouts/joomla/edit/params.php +$this->ignore_fieldsets = array('details', 'images', 'item_associations', 'jmetadata'); + +JFactory::getDocument()->addScriptDeclaration(" + Joomla.submitbutton = function(task) + { + if (task == 'agosm.cancel' || document.formvalidator.isValid(document.getElementById('agosm-form'))) { + " . $this->form->getField('description')->save() . " + Joomla.submitform(task, document.getElementById('agosm-form')); + } + }; +"); + +// In case of modal +$isModal = $input->get('layout') == 'modal' ? true : false; +$layout = $isModal ? 'modal' : 'edit'; +$tmpl = $isModal || $input->get('tmpl', '', 'cmd') === 'component' ? '&tmpl=component' : ''; +?> + +
+ + + +
+ 'details')); ?> + + item->id) ? JText::_('COM_AGOSMS_NEW_AGOSM', true) : JText::_('COM_AGOSMS_EDIT_AGOSM', true)); ?> +
+
+
+ form->getControlGroup('description'); ?> +
+
+
+ +
+
+ + + +
+
+ form->getControlGroup('images'); ?> + form->getGroup('images') as $field) : ?> + getControlGroup(); ?> + +
+
+ + + + +
+
+ +
+
+ +
+
+ + + + + + + loadTemplate('associations'); ?> + + + + + + + +
+ + + + +
diff --git a/dist/agosms-1.0.26/administrator/components/com_agosms/views/agosm/tmpl/edit_associations.php b/dist/agosms-1.0.26/administrator/components/com_agosms/views/agosm/tmpl/edit_associations.php new file mode 100644 index 00000000..e25dbcb6 --- /dev/null +++ b/dist/agosms-1.0.26/administrator/components/com_agosms/views/agosm/tmpl/edit_associations.php @@ -0,0 +1,13 @@ +form->getFieldsets('params'); ?> + $fieldSet) : ?> +
+ description) && trim($fieldSet->description)) : ?> + ' . $this->escape(JText::_($fieldSet->description)) . '

'; ?> + + form->getFieldset($name) as $field) : ?> +
+
label; ?>
+
input; ?>
+
+ +
+ diff --git a/dist/agosms-1.0.26/administrator/components/com_agosms/views/agosm/tmpl/modal.php b/dist/agosms-1.0.26/administrator/components/com_agosms/views/agosm/tmpl/modal.php new file mode 100644 index 00000000..3379c374 --- /dev/null +++ b/dist/agosms-1.0.26/administrator/components/com_agosms/views/agosm/tmpl/modal.php @@ -0,0 +1,32 @@ + 'bottom')); + +// @deprecated 4.0 the function parameter, the inline js and the buttons are not needed since 3.7.0. +$function = JFactory::getApplication()->input->getCmd('function', 'jEditAgosm_' . (int) $this->item->id); + +// Function to update input title when changed +JFactory::getDocument()->addScriptDeclaration(' + function jEditAgosmModal() { + if (window.parent && document.formvalidator.isValid(document.getElementById("agosm-form"))) { + return window.parent.' . $this->escape($function) . '(document.getElementById("jform_title").value); + } + } +'); +?> + + + + +
+ setLayout('edit'); ?> + loadTemplate(); ?> +
diff --git a/dist/agosms-1.0.26/administrator/components/com_agosms/views/agosm/tmpl/modal_associations.php b/dist/agosms-1.0.26/administrator/components/com_agosms/views/agosm/tmpl/modal_associations.php new file mode 100644 index 00000000..e25dbcb6 --- /dev/null +++ b/dist/agosms-1.0.26/administrator/components/com_agosms/views/agosm/tmpl/modal_associations.php @@ -0,0 +1,13 @@ +form->getFieldsets('params'); ?> + $fieldSet) : ?> +
+ description) && trim($fieldSet->description)) : ?> + ' . $this->escape(JText::_($fieldSet->description)) . '

'; ?> + + form->getFieldset($name) as $field) : ?> +
+
label; ?>
+
input; ?>
+
+ +
+ diff --git a/dist/agosms-1.0.26/administrator/components/com_agosms/views/agosm/view.html.php b/dist/agosms-1.0.26/administrator/components/com_agosms/views/agosm/view.html.php new file mode 100644 index 00000000..093add0f --- /dev/null +++ b/dist/agosms-1.0.26/administrator/components/com_agosms/views/agosm/view.html.php @@ -0,0 +1,118 @@ +state = $this->get('State'); + $this->item = $this->get('Item'); + $this->form = $this->get('Form'); + + // Check for errors. + if (count($errors = $this->get('Errors'))) + { + JError::raiseError(500, implode("\n", $errors)); + + return false; + } + + // If we are forcing a language in modal (used for associations). + if ($this->getLayout() === 'modal' && $forcedLanguage = JFactory::getApplication()->input->get('forcedLanguage', '', 'cmd')) + { + // Set the language field to the forcedLanguage and disable changing it. + $this->form->setValue('language', null, $forcedLanguage); + $this->form->setFieldAttribute('language', 'readonly', 'true'); + + // Only allow to select categories with All language or with the forced language. + $this->form->setFieldAttribute('catid', 'language', '*,' . $forcedLanguage); + + // Only allow to select tags with All language or with the forced language. + $this->form->setFieldAttribute('tags', 'language', '*,' . $forcedLanguage); + } + + $this->addToolbar(); + + parent::display($tpl); + } + + /** + * Add the page title and toolbar. + * + * @return void + * + * @since 1.6 + */ + protected function addToolbar() + { + JFactory::getApplication()->input->set('hidemainmenu', true); + + $user = JFactory::getUser(); + $isNew = ($this->item->id == 0); + $checkedOut = !($this->item->checked_out == 0 || $this->item->checked_out == $user->get('id')); + + // Since we don't track these assets at the item level, use the category id. + $canDo = JHelperContent::getActions('com_agosms', 'category', $this->item->catid); + + JToolbarHelper::title($isNew ? JText::_('COM_AGOSMS_MANAGER_AGOSM_NEW') : JText::_('COM_AGOSMS_MANAGER_AGOSM_EDIT'), 'link agosms'); + + // If not checked out, can save the item. + if (!$checkedOut && ($canDo->get('core.edit')||(count($user->getAuthorisedCategories('com_agosms', 'core.create'))))) + { + JToolbarHelper::apply('agosm.apply'); + JToolbarHelper::save('agosm.save'); + } + if (!$checkedOut && (count($user->getAuthorisedCategories('com_agosms', 'core.create')))) + { + JToolbarHelper::save2new('agosm.save2new'); + } + // If an existing item, can save to a copy. + if (!$isNew && (count($user->getAuthorisedCategories('com_agosms', 'core.create')) > 0)) + { + JToolbarHelper::save2copy('agosm.save2copy'); + } + if (empty($this->item->id)) + { + JToolbarHelper::cancel('agosm.cancel'); + } + else + { + if ($this->state->params->get('save_history', 0) && $user->authorise('core.edit')) + { + JToolbarHelper::versions('com_agosms.agosm', $this->item->id); + } + + JToolbarHelper::cancel('agosm.cancel', 'JTOOLBAR_CLOSE'); + } + + JToolbarHelper::divider(); + JToolbarHelper::help('JHELP_COMPONENTS_AGOSMS_LINKS_EDIT'); + } +} diff --git a/dist/agosms-1.0.26/administrator/components/com_agosms/views/agosms/tmpl/default.php b/dist/agosms-1.0.26/administrator/components/com_agosms/views/agosms/tmpl/default.php new file mode 100644 index 00000000..7877c479 --- /dev/null +++ b/dist/agosms-1.0.26/administrator/components/com_agosms/views/agosms/tmpl/default.php @@ -0,0 +1,188 @@ +get('id'); +$listOrder = $this->escape($this->state->get('list.ordering')); +$listDirn = $this->escape($this->state->get('list.direction')); +$canOrder = $user->authorise('core.edit.state', 'com_agosms.category'); +$saveOrder = $listOrder == 'a.ordering'; +$assoc = JLanguageAssociations::isEnabled(); + +if ($saveOrder) +{ + $saveOrderingUrl = 'index.php?option=com_agosms&task=agosms.saveOrderAjax&tmpl=component'; + JHtml::_('sortablelist.sortable', 'agosmList', 'adminForm', strtolower($listDirn), $saveOrderingUrl); +} +?> +
+ sidebar)) : ?> +
+ sidebar; ?> +
+
+ +
+ + $this)); ?> +
+ items)) : ?> +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + items as $i => $item) : ?> + + cat_link = JRoute::_('index.php?option=com_categories&extension=com_agosms&task=edit&type=other&cid[]=' . $item->catid); ?> + authorise('core.create', 'com_agosms.category.' . $item->catid); ?> + authorise('core.edit', 'com_agosms.category.' . $item->catid); ?> + authorise('core.manage', 'com_checkin') || $item->checked_out == $user->id || $item->checked_out == 0; ?> + authorise('core.edit.own', 'com_agosms.category.' . $item->catid) && $item->created_by == $user->id; ?> + authorise('core.edit.state', 'com_agosms.category.' . $item->catid) && $canCheckin; ?> + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ pagination->getListFooter(); ?> +
+ + + + + + + + + + + + + + id); ?> + +
+ state, $i, 'agosms.', $canChange, 'cb', $item->publish_up, $item->publish_down); ?> + + + state === 2 ? 'un' : '') . 'archive', 'cb' . $i, 'agosms'); ?> + state === -2 ? 'un' : '') . 'trash', 'cb' . $i, 'agosms'); ?> + escape($item->title)); ?> + +
+
+ checked_out) : ?> + editor, $item->checked_out_time, 'agosms.', $canCheckin); ?> + + + + escape($item->title); ?> + + escape($item->title); ?> + + + escape($item->alias)); ?> + +
+ escape($item->category_title); ?> +
+
+ escape($item->access_level); ?> + + hits; ?> + + association) : ?> + id); ?> + + + + + id; ?> +
+ + authorise('core.create', 'com_content') + && $user->authorise('core.edit', 'com_content') + && $user->authorise('core.edit.state', 'com_content')) : ?> + JText::_('COM_AGOSMS_BATCH_OPTIONS'), + 'footer' => $this->loadTemplate('batch_footer'), + ), + $this->loadTemplate('batch_body') + ); ?> + + + + + + +
+ diff --git a/dist/agosms-1.0.26/administrator/components/com_agosms/views/agosms/tmpl/default_batch_body.php b/dist/agosms-1.0.26/administrator/components/com_agosms/views/agosms/tmpl/default_batch_body.php new file mode 100644 index 00000000..d3cdbafa --- /dev/null +++ b/dist/agosms-1.0.26/administrator/components/com_agosms/views/agosms/tmpl/default_batch_body.php @@ -0,0 +1,41 @@ +state->get('filter.published'); +?> + +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ = 0) : ?> +
+
+ +
+
+ +
+
+ +
+
+
+
diff --git a/dist/agosms-1.0.26/administrator/components/com_agosms/views/agosms/tmpl/default_batch_footer.php b/dist/agosms-1.0.26/administrator/components/com_agosms/views/agosms/tmpl/default_batch_footer.php new file mode 100644 index 00000000..7526b94d --- /dev/null +++ b/dist/agosms-1.0.26/administrator/components/com_agosms/views/agosms/tmpl/default_batch_footer.php @@ -0,0 +1,18 @@ + + + + + \ No newline at end of file diff --git a/dist/agosms-1.0.26/administrator/components/com_agosms/views/agosms/tmpl/modal.php b/dist/agosms-1.0.26/administrator/components/com_agosms/views/agosms/tmpl/modal.php new file mode 100644 index 00000000..8f5de7b1 --- /dev/null +++ b/dist/agosms-1.0.26/administrator/components/com_agosms/views/agosms/tmpl/modal.php @@ -0,0 +1,148 @@ +isClient('site')) +{ + JSession::checkToken('get') or die(JText::_('JINVALID_TOKEN')); +} +JLoader::register('AgosmsHelperRoute', JPATH_ROOT . '/components/com_agosms/helpers/route.php'); + +// Include the component HTML helpers. + +JHtml::addIncludePath(JPATH_COMPONENT . '/helpers/html'); +JHtml::_('behavior.core'); +JHtml::_('behavior.polyfill', array('event'), 'lt IE 9'); +JHtml::_('script', 'com_agosms/admin-agosms-modal.js', array('version' => 'auto', 'relative' => true)); +JHtml::_('bootstrap.tooltip', '.hasTooltip', array('placement' => 'bottom')); +JHtml::_('formbehavior.chosen', 'select'); + +// Special case for the search field tooltip. +$searchFilterDesc = $this->filterForm->getFieldAttribute('search', 'description', null, 'filter'); +JHtml::_('bootstrap.tooltip', '#filter_search', array('title' => JText::_($searchFilterDesc), 'placement' => 'bottom')); + +$function = $app->input->getCmd('function', 'jSelectAgosm'); +$editor = $app->input->getCmd('editor', ''); +$listOrder = $this->escape($this->state->get('list.ordering')); +$listDirn = $this->escape($this->state->get('list.direction')); +$onclick = $this->escape($function); + +if (!empty($editor)) +{ + // This view is used also in com_menus. Load the xtd script only if the editor is set! + JFactory::getDocument()->addScriptOptions('xtd-agosms', array('editor' => $editor)); + $onclick = "jSelectAgosm"; +} + +$iconStates = array( + -2 => 'icon-trash', + 0 => 'icon-unpublish', + 1 => 'icon-publish', + 2 => 'icon-archive', +); + +?> +
+ +
+ $this)); ?> +
+ items)) : ?> +
+ +
+ + + + + + + + + + + + + + + + + + + items as $i => $item) : ?> + + language && JLanguageMultilang::isEnabled()) : ?> + language); ?> + + language, 0, 2); ?> + + language, 0, 3); ?> + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ pagination->getListFooter(); ?> +
+ + + escape($onclick) . '"' + . ' data-id="' . $item->id . '"' + . ' data-title="' . $this->escape(addslashes($item->title)) . '"' + . ' data-cat-id="' . $this->escape($item->catid) . '"' + . ' data-uri="' . $this->escape(AgosmsHelperRoute::getAgosmRoute($item->id, $item->catid, $item->language)) . '"' + . ' data-language="' . $this->escape($lang) . '"'; + ?> + > + escape($item->title); ?> + +
+ escape($item->category_title); ?> +
+
+ escape($item->access_level); ?> + + + + created, JText::_('DATE_FORMAT_LC4')); ?> + + id; ?> +
+ + + + + + + +
+
diff --git a/dist/agosms-1.0.26/administrator/components/com_agosms/views/agosms/view.html.php b/dist/agosms-1.0.26/administrator/components/com_agosms/views/agosms/view.html.php new file mode 100644 index 00000000..a9b13312 --- /dev/null +++ b/dist/agosms-1.0.26/administrator/components/com_agosms/views/agosms/view.html.php @@ -0,0 +1,170 @@ +state = $this->get('State'); + $this->items = $this->get('Items'); + $this->pagination = $this->get('Pagination'); + $this->filterForm = $this->get('FilterForm'); + $this->activeFilters = $this->get('ActiveFilters'); + + // Modal layout doesn't need the submenu. + if ($this->getLayout() !== 'modal') + { + AgosmsHelper::addSubmenu('agosms'); + } + + // Check for errors. + if (count($errors = $this->get('Errors'))) + { + JError::raiseError(500, implode("\n", $errors)); + return false; + } + + // We don't need toolbar in the modal layout. + if ($this->getLayout() !== 'modal') + { + $this->addToolbar(); + $this->sidebar = JHtmlSidebar::render(); + } + else + { + // In article associations modal we need to remove language filter if forcing a language. + // We also need to change the category filter to show show categories with All or the forced language. + if ($forcedLanguage = JFactory::getApplication()->input->get('forcedLanguage', '', 'CMD')) + { + // If the language is forced we can't allow to select the language, so transform the language selector filter into an hidden field. + $languageXml = new SimpleXMLElement(''); + $this->filterForm->setField($languageXml, 'filter', true); + + // Also, unset the active language filter so the search tools is not open by default with this filter. + unset($this->activeFilters['language']); + + // One last changes needed is to change the category filter to just show categories with All language or with the forced language. + $this->filterForm->setFieldAttribute('category_id', 'language', '*,' . $forcedLanguage, 'filter'); + } + } + + parent::display($tpl); + } + + /** + * Add the page title and toolbar. + * + * @return void + * + * @since 1.6 + */ + protected function addToolbar() + { + require_once JPATH_COMPONENT . '/helpers/agosms.php'; + + $state = $this->get('State'); + $canDo = JHelperContent::getActions('com_agosms', 'category', $state->get('filter.category_id')); + $user = JFactory::getUser(); + + // Get the toolbar object instance + $bar = JToolBar::getInstance('toolbar'); + + JToolbarHelper::title(JText::_('COM_AGOSMS_MANAGER_AGOSMS'), 'link agosms'); + + if (count($user->getAuthorisedCategories('com_agosms', 'core.create')) > 0) + { + JToolbarHelper::addNew('agosm.add'); + } + + if ($canDo->get('core.edit') || $canDo->get('core.edit.own')) + { + JToolbarHelper::editList('agosm.edit'); + } + + if ($canDo->get('core.edit.state')) + { + JToolbarHelper::publish('agosms.publish', 'JTOOLBAR_PUBLISH', true); + JToolbarHelper::unpublish('agosms.unpublish', 'JTOOLBAR_UNPUBLISH', true); + + JToolbarHelper::archiveList('agosms.archive'); + JToolbarHelper::checkin('agosms.checkin'); + } + + if ($state->get('filter.published') == -2 && $canDo->get('core.delete')) + { + JToolbarHelper::deleteList('JGLOBAL_CONFIRM_DELETE', 'agosms.delete', 'JTOOLBAR_EMPTY_TRASH'); + } + elseif ($canDo->get('core.edit.state')) + { + JToolbarHelper::trash('agosms.trash'); + } + + // Add a batch button + if ($user->authorise('core.create', 'com_agosms') && $user->authorise('core.edit', 'com_agosms') + && $user->authorise('core.edit.state', 'com_agosms')) + { + JHtml::_('bootstrap.modal', 'collapseModal'); + $title = JText::_('JTOOLBAR_BATCH'); + + // Instantiate a new JLayoutFile instance and render the batch button + $layout = new JLayoutFile('joomla.toolbar.batch'); + + $dhtml = $layout->render(array('title' => $title)); + $bar->appendButton('Custom', $dhtml, 'batch'); + } + + if ($user->authorise('core.admin', 'com_agosms') || $user->authorise('core.options', 'com_agosms')) + { + JToolbarHelper::preferences('com_agosms'); + } + + JToolbarHelper::help('JHELP_COMPONENTS_AGOSMS_LINKS'); + } + + /** + * Returns an array of fields the table can be sorted by + * + * @return array Array containing the field name to sort by as the key and display text as value + * + * @since 3.0 + */ + protected function getSortFields() + { + return array( + 'a.ordering' => JText::_('JGRID_HEADING_ORDERING'), + 'a.state' => JText::_('JSTATUS'), + 'a.title' => JText::_('JGLOBAL_TITLE'), + 'a.access' => JText::_('JGRID_HEADING_ACCESS'), + 'a.hits' => JText::_('JGLOBAL_HITS'), + 'a.language' => JText::_('JGRID_HEADING_LANGUAGE'), + 'a.id' => JText::_('JGRID_HEADING_ID') + ); + } +} diff --git a/dist/agosms-1.0.26/administrator/components/com_agosms/views/button/tmpl/default.php b/dist/agosms-1.0.26/administrator/components/com_agosms/views/button/tmpl/default.php new file mode 100644 index 00000000..45622684 --- /dev/null +++ b/dist/agosms-1.0.26/administrator/components/com_agosms/views/button/tmpl/default.php @@ -0,0 +1,53 @@ +addStyleSheet(JURI::root(true) . '/media/mod_agosm/leaflet/leaflet.css'); +$document->addScript(JURI::root(true) . '/media/mod_agosm/leaflet/leaflet.js'); +$document->addScript(JURI::root(true) . '/media/com_agosms/js/admin-agosms-button-default.js'); +$uniqid = uniqid(); +$input = JFactory::getApplication()->input; +JText::script('COM_AGOSMS_BUTTON_DEFAULT_POPUP_PROMPT'); +?> +
+
+ +
+ + + diff --git a/dist/agosms-1.0.26/administrator/components/com_agosms/views/button/view.html.php b/dist/agosms-1.0.26/administrator/components/com_agosms/views/button/view.html.php new file mode 100644 index 00000000..b1cd859d --- /dev/null +++ b/dist/agosms-1.0.26/administrator/components/com_agosms/views/button/view.html.php @@ -0,0 +1,38 @@ +session = JFactory::getSession(); + $this->config = $config; + + parent::display($tpl); + } +} diff --git a/dist/agosms-1.0.26/administrator/language/de-DE/de-DE.com_agosms.ini b/dist/agosms-1.0.26/administrator/language/de-DE/de-DE.com_agosms.ini new file mode 100644 index 00000000..4a37dd47 --- /dev/null +++ b/dist/agosms-1.0.26/administrator/language/de-DE/de-DE.com_agosms.ini @@ -0,0 +1,295 @@ +COM_AGOSM="Agosm" +COM_AGOSM_ACCESS_UPDATE_DESC="Geheimes Wort zum Aktualisieren." +COM_AGOSM_ACCESS_UPDATE_LABEL="Geheimes Wort zum Aktualisieren" +COM_AGOSM_CONFIGURATION="Agosm Konfiguration" +COM_AGOSM_HERE_A_COMPONENT_IS_CREATED="Diese Komponente wird nun nicht mehr benötigt. Sie kann, nachdem das geheime Wort in die Komponenten agosms (S am Ende) übertragen wurde, gelöscht werden." +COM_AGOSM_INSERT="Koordinaten auswählen" +COM_AGOSMS_BUTTON_DEFAULT_POPUP_PROMPT="

Koordinaten auswählen

Bitte zoomen Sie in die Karte, verschieben Sie die Karte und klicken Sie die gewünschte Position in der Karte um die Koordinaten auszuwählen." +COM_AGOSMS="Agosmen" +COM_AGOSMS_ACCESS_HEADING="Zugriff" +COM_AGOSMS_BATCH_OPTIONS="Stapelverarbeitung der ausgewählten Links" +COM_AGOSMS_BATCH_TIP="Wenn eine Kategorie zum Verschieben / Kopieren ausgewählt ist, werden alle ausgewählten Aktionen auf die kopierten oder verschobenen Verknüpfungen angewendet. Andernfalls werden alle Aktionen auf die ausgewählten Verknüpfungen angewendet." +COM_AGOSMS_CATEGORIES_DESC="Diese Einstellungen gelten für Agosms-Kategorien, sofern sie nicht für ein bestimmtes Menüelement geändert werden." +COM_AGOSMS_CATEGORY_DESC="Diese Einstellungen gelten für Agosms-Kategorieoptionen, sofern sie nicht für ein bestimmtes Menüelement geändert werden." +COM_AGOSMS_CHANGE_AGOSM="Agosm auswählen oder ändern" +COM_AGOSMS_COMPONENT_DESC="Diese Einstellungen gelten nur für Agosms, wenn sie für ein bestimmtes Menüelement oder Agosm geändert werden." +COM_AGOSMS_COMPONENT_LABEL="Agosm" +COM_AGOSMS_CONFIG_INTEGRATION_SETTINGS_DESC="Diese Einstellungen legen fest, wie die Agosms-Komponente in andere Erweiterungen integriert wird." +COM_AGOSMS_CONFIGURATION="Agosms Manager Optionen" +COM_AGOSMS_EDIT_AGOSM="Agosm bearbeiten" +COM_AGOSMS_ERR_TABLES_NAME="Es gibt bereits einen Agosm mit diesem Namen in dieser Kategorie. Bitte versuche es erneut." +COM_AGOSMS_ERR_TABLES_PROVIDE_URL="Bitte geben Sie eine gültige URL an" +COM_AGOSMS_ERR_TABLES_TITLE="Dein Agosm muss einen Titel enthalten." +COM_AGOSMS_ERROR_UNIQUE_ALIAS="Ein anderer Agosm aus dieser Kategorie hat denselben Aliasnamen (erinnere dich daran, dass es sich um einen verschrotteten Gegenstand handelt)." +COM_AGOSMS_FIELD_ALIAS_DESC="Der Alias ​​dient nur der internen Verwendung. Lassen Sie dieses Feld leer und Joomla wird einen Standardwert aus dem Titel eingeben. Er muss für jedes Agosm in derselben Kategorie eindeutig sein." +COM_AGOSMS_FIELD_CATEGORY_DESC="Wähle eine Kategorie für diesen Agosm." +COM_AGOSMS_FIELD_CATEGORYCHOOSE_DESC="Bitte wählen Sie eine Agosms Kategorie zur Anzeige aus." +COM_AGOSMS_FIELD_CAPTCHA_DESC="Wählen Sie das Captcha-Plugin, das im agosm-Formular verwendet wird. Möglicherweise müssen Sie die erforderlichen Informationen für Ihr Captcha-Plugin im Plugin-Manager eingeben.
Wenn 'Standard verwenden' ausgewählt ist, stellen Sie ein Captcha-Plugin sicher ist in Globale Konfiguration ausgewählt. " +COM_AGOSMS_FIELD_CAPTCHA_LABEL="Captcha auf Agosm zulassen" +COM_AGOSMS_FIELD_CONFIG_CAT_SHOWNUMBERS_DESC="Zeigt oder versteckt die Anzahl der Agosms in jeder Kategorie." +COM_AGOSMS_FIELD_CONFIG_CAT_SHOWNUMBERS_LABEL="# Agosms" +COM_AGOSMS_FIELD_CONFIG_COUNTCLICKS_DESC="Wenn ja, wird die Anzahl der Klicks auf den Link aufgezeichnet." +COM_AGOSMS_FIELD_CONFIG_COUNTCLICKS_LABEL="Klicks zählen" +COM_AGOSMS_FIELD_CONFIG_DESCRIPTION_DESC="Zeigen oder verbergen Sie die Beschreibung unten." +COM_AGOSMS_FIELD_CONFIG_HITS_DESC="Treffer ein- oder ausblenden." +COM_AGOSMS_FIELD_CONFIG_ICON_DESC="Wenn das Symbol oben ausgewählt ist, wählen Sie ein Symbol aus, das mit den Agosms angezeigt werden soll. Wenn keines ausgewählt ist, wird das Standardsymbol verwendet." +COM_AGOSMS_FIELD_CONFIG_ICON_LABEL="Wähle Icon" +COM_AGOSMS_FIELD_CONFIG_LINKDESCRIPTION_DESC="Zeigt oder versteckt die Beschreibung der Links." +COM_AGOSMS_FIELD_CONFIG_LINKDESCRIPTION_LABEL="Beschreibung der Links" +COM_AGOSMS_FIELD_CONFIG_OTHERCATS_DESC="Andere Kategorien ein- oder ausblenden." +COM_AGOSMS_FIELD_CONFIG_OTHERCATS_LABEL="Weitere Kategorien" +COM_AGOSMS_FIELD_CONFIG_SHOWREPORT_DESC="Blendet die Option" Ungültige Link melden "ein oder aus." +COM_AGOSMS_FIELD_CONFIG_SHOWREPORT_LABEL="Berichte" +COM_AGOSMS_FIELD_COUNTCLICKS_DESC="Wenn ja, wird die Anzahl der Klicks auf den Link aufgezeichnet." +COM_AGOSMS_FIELD_COUNTCLICKS_LABEL="Klicks zählen" +COM_AGOSMS_FIELD_DESCRIPTION_DESC="Geben Sie eine Beschreibung für das Agosm ein." +COM_AGOSMS_FIELD_DISPLAY_NUM_DESC="Standardanzahl von Agosms, die auf einer Seite aufgelistet werden sollen." +COM_AGOSMS_FIELD_DISPLAY_NUM_LABEL="Anzahl der zu listenen Agosmen" +COM_AGOSMS_FIELD_FIRST_DESC="Das Bild, das angezeigt werden soll." +COM_AGOSMS_FIELD_FIRST_LABEL="Erstes Bild" + +COM_AGOSMS_FIELD_HEIGHT_DESC="Höhe des Popup- oder Modal-Zielfensters. Standard ist 600x500, wenn ein Feld leer ist." +COM_AGOSMS_FIELD_HEIGHT_LABEL="Höhe" +COM_AGOSMS_FIELD_ICON_DESC="Zeigt einen Text, ein Symbol oder nichts mit den Agosms an. Standard ist 'Icon'." +COM_AGOSMS_FIELD_ICON_LABEL="Nur Text / Symbol / Agosm" +COM_AGOSMS_FIELD_ICON_OPTION_ICON="Icon" +COM_AGOSMS_FIELD_ICON_OPTION_TEXT="Text" +COM_AGOSMS_FIELD_ICON_OPTION_AGOSM="Nur Agosm" +COM_AGOSMS_FIELD_IMAGE_ALT_DESC="Alternativer Text, der für Besucher ohne Zugriff auf Bilder verwendet wird. Ersetzt durch den Beschriftungstext, falls vorhanden." +COM_AGOSMS_FIELD_IMAGE_ALT_LABEL="Alt Text" +COM_AGOSMS_FIELD_IMAGE_CAPTION_DESC="Dem Bild angehängte Beschriftung." +COM_AGOSMS_FIELD_IMAGE_CAPTION_LABEL="Beschriftung" + +COM_AGOSMS_FIELD_LANGUAGE_DESC="Weisen Sie diesem agosm eine Sprache zu." +COM_AGOSMS_FIELD_MODIFIED_DESC="Datum und Uhrzeit der letzten Änderung des Links." +COM_AGOSMS_FIELD_SECOND_DESC="Das zweite Bild, das angezeigt werden soll." +COM_AGOSMS_FIELD_SECOND_LABEL="Zweites Bild" +COM_AGOSMS_FIELD_SELECT_CATEGORY_DESC="Wählen Sie eine Agosmen-Kategorie zur Anzeige aus." +COM_AGOSMS_FIELD_SELECT_CATEGORY_LABEL="Wählen Sie eine Kategorie" +COM_AGOSMS_FIELD_SHOW_CAT_TAGS_DESC="Zeigt die Tags für eine Kategorie an." +COM_AGOSMS_FIELD_SHOW_CAT_TAGS_LABEL="Tags anzeigen" +COM_AGOSMS_FIELD_SHOW_TAGS_DESC="Zeige die Tags für ein Agosm." +COM_AGOSMS_FIELD_SHOW_TAGS_LABEL="Tags anzeigen" +COM_AGOSMS_FIELD_STATE_DESC="Veröffentlichungsstatus festlegen" +COM_AGOSMS_FIELD_TARGET_DESC="Zielbrowserfenster, wenn der Link ausgewählt ist." +COM_AGOSMS_FIELD_TARGET_LABEL="Ziel" +COM_AGOSMS_FIELD_TITLE_DESC="Agosm muss einen Titel haben." +COM_AGOSMS_FIELD_URL_DESC="Sie müssen eine URL eingeben. IDN (International) Links werden beim Speichern in punycode konvertiert." +COM_AGOSMS_FIELD_URL_LABEL="URL" +COM_AGOSMS_FIELD_VALUE_REPORTED="Berichtet" +COM_AGOSMS_FIELD_VERSION_DESC="Anzahl der Wiederholungen dieses Agosms." +COM_AGOSMS_FIELD_VERSION_LABEL="Revision" +COM_AGOSMS_FIELD_WIDTH_DESC="Breite des Popup- oder Modal-Zielfensters. Standardeinstellung ist 600x500, wenn ein Feld leer ist." +COM_AGOSMS_FIELD_WIDTH_LABEL="Breite" +COM_AGOSMS_FIELDSET_IMAGES="Bilder" +COM_AGOSMS_FIELDSET_OPTIONS="Optionen" +COM_AGOSMS_FILTER_CATEGORY="Kategorie filtern" +COM_AGOSMS_FILTER_SEARCH_DESC="Suche in Agosm Titel und Alias. Präfix mit ID: um nach einer Agosm ID zu suchen." +COM_AGOSMS_FILTER_SEARCH_LABEL="Agosms suchen" +COM_AGOSMS_FILTER_STATE="Filterstatus" +COM_AGOSMS_FLOAT_FIRST_DESC="Steuert die Platzierung des ersten Bildes." +COM_AGOSMS_FLOAT_FIRST_LABEL="Erstes Bild-Float" +COM_AGOSMS_FLOAT_SECOND_DESC="Steuert die Platzierung des zweiten Bildes." +COM_AGOSMS_FLOAT_SECOND_LABEL="Zweites Bild Float" +COM_AGOSMS_HEADING_ASSOCIATION="Assoziation" +COM_AGOSMS_HITS_DESC="Anzahl der Treffer für dieses Agosm." +COM_AGOSMS_LEFT="Links" +COM_AGOSMS_LIST_LAYOUT_DESC="Diese Einstellungen gelten für Layoutoptionen für Agosms-Listen, sofern sie nicht für einen bestimmten Menüeintrag geändert werden." +COM_AGOSMS_MANAGER_AGOSM="Agosms" +COM_AGOSMS_MANAGER_AGOSMS="Agosmen" +COM_AGOSMS_MANAGER_AGOSM_EDIT="Agosm: Bearbeiten" +COM_AGOSMS_MANAGER_AGOSM_NEW="Agosm: Neu" +COM_AGOSMS_N_ITEMS_ARCHIVED="% d agosms erfolgreich archiviert." +COM_AGOSMS_N_ITEMS_ARCHIVED_1="% d agosm erfolgreich archiviert." +COM_AGOSMS_N_ITEMS_CHECKED_IN_0="Kein Agosm erfolgreich eingecheckt." +COM_AGOSMS_N_ITEMS_CHECKED_IN_1="% d agosm hat erfolgreich eingecheckt." +COM_AGOSMS_N_ITEMS_CHECKED_IN_MORE="% d agosms erfolgreich eingecheckt." +COM_AGOSMS_N_ITEMS_DELETED="% d agosms wurde erfolgreich gelöscht." +COM_AGOSMS_N_ITEMS_DELETED_1="% d agosm wurde erfolgreich gelöscht." +COM_AGOSMS_N_ITEMS_PUBLISHED="% d agosms erfolgreich veröffentlicht." +COM_AGOSMS_N_ITEMS_PUBLISHED_1="% d agosm erfolgreich veröffentlicht." +COM_AGOSMS_N_ITEMS_TRASHED="% d agosms erfolgreich gelöscht." +COM_AGOSMS_N_ITEMS_TRASHED_1="% d agosm erfolgreich gelöscht." +COM_AGOSMS_N_ITEMS_UNPUBLISHED="% d agosms erfolgreich unveröffentlicht." +COM_AGOSMS_N_ITEMS_UNPUBLISHED_1="% d agosm erfolgreich unveröffentlicht." +COM_AGOSMS_NEW_AGOSM="Neuer Agosm" +COM_AGOSMS_NONE="Keine" +COM_AGOSMS_OPTION_FILTER_ACCESS="- Filterzugriff -" +COM_AGOSMS_OPTION_FILTER_CATEGORY="- Filterkategorie -" +COM_AGOSMS_OPTION_FILTER_PUBLISHED="- Filterstatus -" +COM_AGOSMS_OPTIONS="Optionen" +COM_AGOSMS_ORDER_HEADING="Bestellung" +COM_AGOSMS_RIGHT="Richtig" +COM_AGOSMS_SAVE_SUCCESS="Agosm erfolgreich gespeichert" +COM_AGOSMS_SEARCH_IN_TITLE="Suche in Titel" +COM_AGOSMS_SELECT_A_AGOSM="Wähle Agosm" +COM_AGOSMS_SHOW_EMPTY_CATEGORIES_DESC="Wenn Zeigen, werden leere Kategorien angezeigt. Eine Kategorie ist nur leer - wenn sie keine Agosms oder Unterkategorien hat." +COM_AGOSMS_SUBMENU_CATEGORIES="Kategorien" +COM_AGOSMS_SUBMENU_AGOSMS="Agosms" +COM_AGOSMS_AGOSMS="Agosmen" +COM_AGOSMS_XML_DESCRIPTION="Komponente für die Verwaltung von Agosms" +JGLOBAL_NO_ITEM_SELECTED="Keine Agosms ausgewählt" +JGLOBAL_NEWITEMSLAST_DESC="Neue Agosms haben standardmäßig die letzte Position. Die Reihenfolge kann geändert werden, nachdem dieser Agosm gespeichert wurde." +JLIB_APPLICATION_ERROR_BATCH_CANNOT_CREATE="Sie dürfen keine neuen Agosms in dieser Kategorie erstellen." +JLIB_APPLICATION_ERROR_BATCH_CANNOT_EDIT="Sie dürfen eines oder mehrere dieser Agosms nicht bearbeiten." +JLIB_RULES_SETTING_NOTES="Änderungen gelten nur für diese Komponente.
Inherited - eine globale Konfigurationseinstellung oder eine höhere Einstellung wird angewendet.
Verweigert gewinnt immer - was auch immer auf der globalen oder höheren Ebene festgelegt wird und gilt für alle untergeordneten Elemente.
Zugelassen wird aktiviert die Aktion für diese Komponente, sofern sie nicht durch eine globale Konfigurationseinstellung außer Kraft gesetzt wird. " +COM_AGOSMS_FIELDSET_PINOPTIONS="Modul-Agosmen - Marker-Optionen" + +; VOM MODULE KOPIERT +MOD_AGOSM="AGOSM Modul" +MOD_AGOSM_XML_DESCRIPTION="Zeigt OpenStreetMap mit Markern und Controllern an." +;Routing +MOD_AGOSM_ROUTING_CONTROL_FIELDSETLABEL="Routing" +MOD_AGOSM_CONFIG_CONTROL_LANGUAGEROUTING_LABEL="Sprache" +MOD_AGOSM_CONFIG_CONTROL_LANGUAGEROUTING_DESC="Sprache im Routing Control" +MOD_AGOSM_CONFIG_CONTROL_ROUTEWHILEDRAGGING_LABEL="Ziehen" +MOD_AGOSM_CONFIG_CONTROL_ROUTEWHILEDRAGGING_DESC="Wenn diese Option zutrifft, werden die Routen kontinuierlich berechnet, während der Benutzer Wegpunkte zieht und sofort eine Rückmeldung gibt." +MOD_AGOSM_CONFIG_CONTROL_SHOWROUTING_LABEL="Routing anzeigen" +MOD_AGOSM_CONFIG_CONTROL_SHOWROUTING_DESC="Soll die Routing-Steuerung in die Karte integriert werden?" +MOD_AGOSM_ROUTING_FIELDSETDESCRIPTION="Finde den Weg von A nach B auf der Leaflet-Map." +MOD_AGOSM_CONFIG_CONTROL_ENDROUTING_DESC="Wo soll das Routing beendet werden?" +MOD_AGOSM_CONFIG_CONTROL_ENDROUTING_LABEL="Ende" +MOD_AGOSM_CONFIG_CONTROL_STARTROUTING_DESC="Wo möchten Sie starten?" +MOD_AGOSM_CONFIG_CONTROL_STARTROUTING_LABEL="Start" +MOD_AGOSM_CONFIG_CONTROL_UNITROUTING_DESC="Zu verwendende Einheiten; 'metrisch' oder 'imperial'" +MOD_AGOSM_CONFIG_CONTROL_UNITROUTING_LABEL="Einheiten" +MOD_AGOSM_CONFIG_CONTROL_PROFILEOUTING_LABEL="Routing Profil" +MOD_AGOSM_CONFIG_CONTROL_PROFILEROUTING_DESC="Die Mapbox Directions API zeigt Ihnen, wie Sie dorthin gelangen, wo Sie hingehen. Mit der Directions-API können Sie: optimales Fahren, Gehen und Radfahren berechnen (https://www.mapbox.com/api-documentation/ #Richtungen)." +; esriGeocoder +MOD_AGOSM_DEFAULT_ESRI_GEOCODER_PLACEHOLDER="Nach Orten oder Adressen suchen" +MOD_AGOSM_DEFAULT_ESRI_GEOCODER_TITLE="Ortssuche" +MOD_AGOSM_CONFIG_CONTROL_ESRIGEOGODER_ALLOWMULTIPELRESULTS_DESC="Wenn der Wert auf 'true' gesetzt ist und der Benutzer das Formular ohne Vorschlag abschickt, geocodiert das Steuerelement den aktuellen Text in der Eingabe." +MOD_AGOSM_CONFIG_CONTROL_ESRIGEOGODER_ALLOWMULTIPELRESULTS_LABEL="Mehrere Ergebnisse zulassen" +MOD_AGOSM_CONFIG_CONTROL_ESRIGEOCDODER_COLLAPSEAFTERRESULT_DESC="Bestimmt, ob der Geocodierer nach dem Finden eines Ergebnisses kollabieren soll oder nicht." +MOD_AGOSM_CONFIG_CONTROL_ESRIGEOGODER_COLLAPSEAFTERRESULT_LABEL="Collapse after Result" +MOD_AGOSM_CONFIG_CONTROL_ESRIGEOGODER_EXPANDED_DESC="Bestimmt, ob der Geocoder in einem expandierten Zustand startet oder nicht." +MOD_AGOSM_CONFIG_CONTROL_ESRIGEOGODER_EXPANDED_LABEL="Expanded" +MOD_AGOSM_CONFIG_CONTROL_ESRIGEOGODER_POSITION_DESC="Die Position auf der Karte" +MOD_AGOSM_CONFIG_CONTROL_ESRIGEOGODER_POSITION_LABEL="Position" +MOD_AGOSM_CONFIG_CONTROL_ESRIGEOCDER_USEMAPBOUNDS_DESC="Bestimmt, ob und wann der Geocoder die Grenzen der Karte zum Filtern von Suchergebnissen verwenden soll. Wenn true, liefert der Geocoder immer Ergebnisse in den aktuellen Kartengrenzen. Wenn false, sucht er immer die Welt." +MOD_AGOSM_CONFIG_CONTROL_ESRIGEOGODER_USEMAPBOUNDS_LABEL="MapBounds verwenden" +MOD_AGOSM_CONFIG_CONTROL_ESRIGEOGODER_ZOOMTORESULTS_DESC="Bestimmt, ob die Karte nach Abschluss der Geocodierung auf das Ergebnis zoomt oder nicht." +MOD_AGOSM_CONFIG_CONTROL_ESRIGEOGODER_ZOOMTORESULTS_LABEL="Zoom auf Ergebnisse" +MOD_AGOSM_CONFIG_CONTROL_SHOWGEOCODERESRI_DESC="Soll die ESRI Geocoder-Steuerung in die Karte integriert werden?" +MOD_AGOSM_CONFIG_CONTROL_SHOWGEOCODERESRI_LABEL="Zeige ESRI Geocoder" +MOD_AGOSM_GEOCODER_ESRI_CONTROL_FIELDSETLABEL="ESRI Geocoder" +MOD_AGOSM_GEOCODER_ESRI_FIELDSETDESCRIPTION="ESRI Geocoder" +MOD_AGOSM_CONFIG_CONTROL_ESRIGEOGODER_OPENGETADDRESS_DESC="Öffnet die Adresse, die als Wert der GET-Variable mit der Namenadresse angegeben wurde." +MOD_AGOSM_CONFIG_CONTROL_ESRIGEOGODER_OPENGETADDRESS_LABEL="Adresse öffnen" +MOD_AGOSM_CONFIG_CONTROL_ESRIGEOGODER_REVERSE_DESC="Umgekehrte Geocodierung ist der Prozess der Rückkodierung eines Punktstandorts (Breite, Länge) zu einer lesbaren Adresse oder Ortsname. Klicken Sie auf die Karte und ein Marker wird mit der Adresse des angeklickten Punktes erstellt. Achtung: Diese Option ist nicht einfach, wenn Sie Marker mit Pop-Ups verwenden: Der Benutzer muss darauf achten, wo er klickt, wenn er ein Pop-Up öffnen möchte, wenn er etwas verpasst, wird ein neuer Marker erzeugt, wenn diese Option aktiviert ist . " +MOD_AGOSM_CONFIG_CONTROL_ESRIGEOGODER_REVERSE_LABEL="Geocodierung umkehren" +MOD_AGOSM_CONFIG_CONTROL_USEESRI_DESC="Nutze den Geocoding Service des Environmental Systems Research Institute (ESRI)" +MOD_AGOSM_CONFIG_CONTROL_USEESRI_LABEL="ESRI Geocoding Service verwenden" +;OsmGeocoder +MOD_AGOSM_CONTROL_HEADLINE="Position und Verhalten des Controls" +MOD_AGOSM_CONFIG_CONTROL_SHOWGEOCODER_LABEL="OSM Geocoder anzeigen" +MOD_AGOSM_CONFIG_CONTROL_SHOWGEOCODER_DESC="Soll die OSM Geocoder-Steuerung in die Karte integriert werden?" +MOD_AGOSM_GEOCODER_CONTROL_FIELDSETLABEL="OSM Geocoder-Steuerung" +MOD_AGOSM_GEOCODER_FIELDSETDESCRIPTION="Leaflet Control OSM Geocoder. Ein einfacher Geocoder, der den OpenstreetMap-Gecoder Nominatim verwendet, um Orte zu lokalisieren." +MOD_AGOSM_CONFIG_CONTROL_COLLAPSED_LABEL="Kollabiert" +MOD_AGOSM_CONFIG_CONTROL_COLLAPSED_DESC="Ob es kollabiert ist oder nicht." +MOD_AGOSM_CONFIG_CONTROL_EXPAND_LABEL="Erweitern" +MOD_AGOSM_CONFIG_CONTROL_EXPAND_DESC="So erweitern Sie ein reduziertes Steuerelement: berühren, klicken oder bewegen" +MOD_AGOSM_CONFIG_CONTROL_POSITION_LABEL="Position" +MOD_AGOSM_CONFIG_CONTROL_POSITION_DESC="Die Position der Karte" +MOD_AGOSM_CONFIG_CONTROL_POSITION_BOTTOMLEFT="unten links" +MOD_AGOSM_CONFIG_CONTROL_POSITION_BOTTOMRIGHT="unten rechts" +MOD_AGOSM_CONFIG_CONTROL_POSITION_TOPLEFT="oben links" +MOD_AGOSM_CONFIG_CONTROL_POSITION_TOPRIGHT="oben rechts" +MOD_AGOSM_DEFAULT_TEXT_PLACEHOLDER="Postleitzahl, Stadt oder vollständige Adresse" +MOD_AGOSM_DEFAULT_TEXT_ERRORMESSAGE="Nichts gefunden." +MOD_AGOSM_DEFAULT_TEXT="Suchen" +; Spezieller Marker +MOD_AGOSM_CONFIG_CONTROL_SHOWPIN_LABEL="Pin anzeigen" +MOD_AGOSM_CONFIG_CONTROL_SHOWPIN_DESC="Möchten Sie einen speziellen Pin anzeigen?" +MOD_AGOSM_H_PIN="Spezielle Pin-Konfiguration" +MOD_AGOSM_PIN="Pin auswählen" +MOD_AGOSM_PIN_DESC="Wählen Sie die letzte Option, wenn Sie Ihre eigene verwenden möchten." +MOD_AGOSM_OWNPIN="eigene (benutzerdefinierter Stil)" +MOD_AGOSM_NOPIN="keine PIN" +MOD_AGOSM_PINPATH="Eigener Pin (benutzerdefinierter Stil)" +MOD_AGOSM_PINPATH_DESC="Pfad zu Ihrem Pin." +MOD_AGOSM_PINSIZE="Pingröße" +MOD_AGOSM_PINSIZE_DESC="Größe des Symbols. Beispiel: '38, 95 '" +MOD_AGOSM_PINSHADOWPATH="Schatten" +MOD_AGOSM_PINSHADOWPATH_DESC="Pfad zum Bild des Schattens Ihres Pins." +MOD_AGOSM_PINSHADOWSIZE="Schattengröße" +MOD_AGOSM_PINSHADOWSIZE_DESC="Größe des Symbols. Beispiel: '50, 64 '" +MOD_AGOSM_PINOFFSET="Pin-Punkt-Offset" +MOD_AGOSM_PINOFFSET_DESC="Punkt des Symbols, der der Position des Markers entspricht. Beispiel: '22, 94 '" +MOD_AGOSM_PINPOPUPOFFSET="Popup-Offset" +MOD_AGOSM_PINPOPUPOFFSET_DESC="Punkt, von dem sich das Popup relativ zum IconAnchor öffnen soll. Beispiel: '-3, -76'" +MOD_AGOSM_POSITION="Startposition | Dies kann in anderen Tabs außer Kraft gesetzt werden. Zum Beispiel im Tab Geocoder Control mit der Option Bounds oder aus dem Routing Control." +MOD_AGOSM_LON="Längengrad" +MOD_AGOSM_LON_DESC="Pin-Position LON" +MOD_AGOSM_LAT="Breitengrad" +MOD_AGOSM_LAT_DESC="Pin-Position LAT" +MOD_AGOSM_HPOPUP="Popup" +MOD_AGOSM_POPUP="Popup anzeigen" +MOD_AGOSM_KLICK="Zeige bei Klick" +MOD_AGOSM_ALWAYS="sofort anzeigen" +MOD_AGOSM_NEVER="kein Popup" +MOD_AGOSM_POPUP_DESC="Zeige ein Popup-Fenster, wenn du auf den Pin klickst. Du kannst nur jeweils ein Popup öffnen." +MOD_AGOSM_POPUPTEXT="Text im Popup" +MOD_AGOSM_POPUPTEXT_DESC="HTML ist erlaubt" +MOD_AGOSM_CONFIG_CONTROL_SUBFORM_DESC="Sie können so viele Marker hinzufügen wie Sie möchten." +MOD_AGOSM_CONFIG_CONTROL_SUBFORM_LABEL="Marker hinzufügen" +; Kartenkonfiguration +MOD_AGOSM_H_MAP="Kartenkonfiguration" +MOD_AGOSM_HEIGHT="Kartenhöhe" +MOD_AGOSM_HEIGHT_DESC="in px" +MOD_AGOSM_ZOOM="Zoomlevel" +MOD_AGOSM_ZOOM_DESC="1: weit, 20: sehr nah" +MOD_AGOSM_BASELAYER="Kartenstil" +MOD_AGOSM_BASELAYER_DESC="Mapnik ist der Standard-Stil von OpenStreetMap." +MOD_AGOSM_CUSTOMBASELAYER="Benutzerdefinierter Kartenstil" +MOD_AGOSM_CUSTOMBASELAYER_DESC="Nur wenn 'Custom' als Kartenstil gewählt wurde. Wer liefert den Kartenstil (Kacheln)?" +MOD_AGOSM_CUSTOMBASELAYERURL="Tile Server URL" +MOD_AGOSM_CUSTOMBASELAYERURL_DESC="Nur wenn 'benutzerdefiniert' als Map Style gewählt wurde. Beispiel: http://tile.openstreetmap.org/{z}/{x}/{y}.png" +MOD_AGOSM_NOWORLDWARP="Kein Worldwarp" +MOD_AGOSM_NOWORLDWARP_DESC="Normalerweise wird die Welt weiter nach links und nach rechts gehen. Diese Option schränkt den Blick auf 'eine Welt' ein." +MOD_AGOSM_ATTRIMAGERY="Kartenstil anzeigen" +MOD_AGOSM_ATTRIMAGERY_DESC="Zeigt den verwendeten Kartenstil an. Mit mapbox wird die Deaktivierung ignoriert." +MOD_AGOSM_ATTRMODULE="Author note anzeigen" +MOD_AGOSM_ATTRMODULE_DESC="Zeige einen Link zu meiner Website." +MOD_AGOSM_SCALE="Skalieren" +MOD_AGOSM_SCALE_DESC="Zeige die Skalierung in der unteren linken Ecke." +MOD_AGOSM_METRIC="Metrisch (m / km)" +MOD_AGOSM_IMPERIAL="Imperial (ft / mi)" +MOD_AGOSM_MODULE_BY="Modul von" +MOD_AGOSM_MAPBOXKEY_DESC="Um eines der Tools, APIs oder SDKs von Mapbox zu verwenden, benötigen Sie ein Mapbox-Zugriffstoken. Siehe https://www.mapbox.com/help/define-access-token/. Sie können denselben Zugriff verwenden -Token zum Routen und zum Anzeigen der Karte." +MOD_AGOSM_MAPBOXKEY_LABEL="Zugriffstoken" +MOD_AGOSM_DETECTRETINA="Netzhautanzeige erkennen" +MOD_AGOSM_DETECTRETINA_DESC="Wenn true und der Benutzer auf einem Retina-Display ist, werden vier Kacheln mit der Hälfte der angegebenen Größe und eine größere Zoomstufe anstelle von einem angefordert, um die hohe Auflösung zu nutzen." +MOD_AGOSM_LATLON="Koordinaten" +MOD_AGOSM_LATLON_DESC="Koordinaten im geographischen Koordinatensystem: Breite und Länge - Latitude and Longitude" +MOD_AGOSM_LONLAT="Koordinaten" +MOD_AGOSM_LONLAT_DESC="Koordinaten im geographischen Koordinatensystem: Länge und Breite - Longitude and Latitude" +MOD_AGOSM_CONTROL_GOOGLETYPE_DESC="In der Google Maps JavaScript API sind vier Arten von Karten verfügbar." +MOD_AGOSM_CONTROL_GOOGLETYPE_LABEL="Google Maps Map Type" +MOD_AGOSM_CONTROL_MAPBOXMAPTYPE_DESC="Welche Kartenkacheln sollten auf der Karte angezeigt werden? Kartenkacheln sind quadratische Bitmap-Grafiken, die in einer Gitteranordnung angezeigt werden: http://wiki.openstreetmap.org/wiki/Tiles" +MOD_AGOSM_CONTROL_MAPBOXMAPTYPE_LABEL="Mapbox Tile Server" +MOD_AGOSM_CONTROL_STAMENMAPTYPE_DESC="Welche Kartenkacheln sollten auf der Karte angezeigt werden? Kartenkacheln sind quadratische Bitmap-Grafiken, die in einer Gitteranordnung angezeigt werden: http://wiki.openstreetmap.org/wiki/Tiles. Für Staubgefäße finden Sie weitere Informationen hier: http: //maps.stamen.com/ " +MOD_AGOSM_CONTROL_STAMENMAPTYPE_LABEL="Staubblatt-Tile-Server" +MOD_AGOSM_CONTROL_THUNDERFORESTMAPTYPE_DESC="Thunderforest-Kartentyp: Siehe https://thunderforest.com/maps/" +MOD_AGOSM_CONTROL_THUNDERFORESTMAPTYPE_LABEL="Thunderforest Map Type" +MOD_AGOSM_GOOGLEKEY_DESC="Um die Google Maps JavaScript-API zu verwenden, müssen Sie Ihr App-Projekt in der Google API-Konsole registrieren und einen Google API-Schlüssel erhalten, den Sie Ihrer App hinzufügen können." +MOD_AGOSM_GOOGLEKEY_LABEL="Google Maps-API-Schlüssel" +MOD_AGOSM_THUNDERKEY_DESC="Thunderforest-API-Schlüssel: Siehe https://thunderforest.com/docs/apikeys/" +MOD_AGOSM_THUNDERKEY_LABEL="Thunderforest-API-Schlüssel" +Benutzerdefinierte Markierungen fontawesom +MOD_AGOSM_AWESOMEPLUGIN="Pin mit Fontawesome Icon" +MOD_AGOSM_FONTAWESOME_EXTRACLASSES_DESC="Zusätzliche Klasse. Zusätzliche Klassen für das Symbol. Sie können eine benutzerdefinierte Klasse (myclass) oder eine font awesome classe (fa-rotate90) oder beide (fa-rotate90 myclass) verwenden. Weitere Informationen finden Sie unter http://fontawesome.io / examples /. HINWEIS: Sie können nicht alle font awesome classe verwenden. Einige werden von diesem Plugin überschrieben. Besonders die Klassen bezüglich der Größe des Icons. " +MOD_AGOSM_FONTAWESOME_EXTRACLASSES_LABEL="Extra Klasse" +MOD_AGOSM_FONTAWESOME_ICONCOLOR_DESC="Farbe des Symbols." +MOD_AGOSM_FONTAWESOME_ICONCOLOR_LABEL="Icon color" +MOD_AGOSM_FONTAWESOME_ICON_DESC="Das Symbolbild. Weitere Informationen finden Sie unter: http://fontawesome.io/icons/" +MOD_AGOSM_FONTAWESOME_ICON_LABEL="Icon Bild" +MOD_AGOSM_FONTAWESOME_MARKERCOLOR_DESC="Farbe des Markers." +MOD_AGOSM_FONTAWESOME_MARKERCOLOR_LABEL="Markierungsfarbe" +MOD_AGOSM_FONTAWESOME_SPIN_DESC="Lass das Symbol drehen" +MOD_AGOSM_FONTAWESOME_SPIN_LABEL="Drehen" +; Zeige Component-Pins +MOD_AGOSM_H_PINCOM="Pins von Komponente anzeigen" +MOD_AGOSM_CONFIG_CONTROL_SHOWCOMPONENTPIN_DESC="Möchten Sie einen speziellen Pin aus der Komponente com_agosms anzeigen?" +MOD_AGOSM_CONFIG_CONTROL_SHOWCOMPONENTPIN_LABEL="Pin von der Komponente com_agosms anzeigen" diff --git a/dist/agosms-1.0.26/administrator/language/de-DE/de-DE.com_agosms.sys.ini b/dist/agosms-1.0.26/administrator/language/de-DE/de-DE.com_agosms.sys.ini new file mode 100644 index 00000000..02a1a45a --- /dev/null +++ b/dist/agosms-1.0.26/administrator/language/de-DE/de-DE.com_agosms.sys.ini @@ -0,0 +1,4 @@ +COM_AGOSM="Agosm" +COM_AGOSM_ACCESS_UPDATE_DESC="Secret Word for updating. You also need to activate the Installer Plugin. If you lost the secret word you can write an email to info@astrid-guenther.de." +COM_AGOSM_ACCESS_UPDATE_LABEL="Secret Word for updating" +COM_AGOSM_CONFIGURATION="Agosm Konfiguration" \ No newline at end of file diff --git a/dist/agosms-1.0.26/administrator/language/en-GB/en-GB.com_agosms.ini b/dist/agosms-1.0.26/administrator/language/en-GB/en-GB.com_agosms.ini new file mode 100644 index 00000000..f0fef9ed --- /dev/null +++ b/dist/agosms-1.0.26/administrator/language/en-GB/en-GB.com_agosms.ini @@ -0,0 +1,295 @@ +COM_AGOSM="Agosm" +COM_AGOSM_ACCESS_UPDATE_DESC="Secret Word for updating. You also need to activate the Installer Plugin. If you lost the secret word you can write an email to info@astrid-guenther.de." +COM_AGOSM_ACCESS_UPDATE_LABEL="Secret Word for updating" +COM_AGOSM_CONFIGURATION="Agosm Configuration" +COM_AGOSM_HERE_A_COMPONENT_IS_CREATED="This component is no longer needed. It can be deleted after the secret word has been transferred to the components agosms (S at the end)." +COM_AGOSM_INSERT="Select coordinates" +COM_AGOSMS_BUTTON_DEFAULT_POPUP_PROMPT="

Select coordinates

Please pan and zoom and click in on the desired position on the map to select the coordinates." +COM_AGOSMS="Agosms" +COM_AGOSMS_ACCESS_HEADING="Access" +COM_AGOSMS_BATCH_OPTIONS="Batch process the selected links" +COM_AGOSMS_BATCH_TIP="If a category is selected for move/copy, any actions selected will be applied to the copied or moved links. Otherwise, all actions are applied to the selected links." +COM_AGOSMS_CATEGORIES_DESC="These settings apply for Agosms Categories Options unless they are changed for a specific menu item." +COM_AGOSMS_CATEGORY_DESC="These settings apply for Agosms Category Options unless they are changed for a specific menu item." +COM_AGOSMS_CHANGE_AGOSM="Select or Change Agosm" +COM_AGOSMS_COMPONENT_DESC="These settings apply for Agosms unless they are changed for a specific menu item or agosm." +COM_AGOSMS_COMPONENT_LABEL="Agosm" +COM_AGOSMS_CONFIG_INTEGRATION_SETTINGS_DESC="These settings determine how the Agosms Component will integrate with other extensions." +COM_AGOSMS_CONFIGURATION="Agosms Manager Options" +COM_AGOSMS_EDIT_AGOSM="Edit Agosm" +COM_AGOSMS_ERR_TABLES_NAME="There is already a Agosm with that name in this category. Please try again." +COM_AGOSMS_ERR_TABLES_PROVIDE_URL="Please provide a valid URL" +COM_AGOSMS_ERR_TABLES_TITLE="Your agosm must contain a title." +COM_AGOSMS_ERROR_UNIQUE_ALIAS="Another agosm from this category has the same alias (remember it may be a trashed item)." +COM_AGOSMS_FIELD_ALIAS_DESC="The alias is for internal use only. Leave this blank and Joomla will fill in a default value from the title. It has to be unique for each agosm in the same category." +COM_AGOSMS_FIELD_CATEGORY_DESC="Choose a category for this Agosm." +COM_AGOSMS_FIELD_CATEGORYCHOOSE_DESC="Please choose a Agosms category to display." +COM_AGOSMS_FIELD_CAPTCHA_DESC="Select the captcha plugin that will be used in the agosm submit form. You may need to enter required information for your captcha plugin in the Plugin Manager.
If 'Use Default' is selected, make sure a captcha plugin is selected in Global Configuration." +COM_AGOSMS_FIELD_CAPTCHA_LABEL="Allow Captcha on Agosm" +COM_AGOSMS_FIELD_CONFIG_CAT_SHOWNUMBERS_DESC="Show or hide the number of Agosms in each Category." +COM_AGOSMS_FIELD_CONFIG_CAT_SHOWNUMBERS_LABEL="# Agosms" +COM_AGOSMS_FIELD_CONFIG_COUNTCLICKS_DESC="If set to yes, the number of times the link has been clicked will be recorded." +COM_AGOSMS_FIELD_CONFIG_COUNTCLICKS_LABEL="Count Clicks" +COM_AGOSMS_FIELD_CONFIG_DESCRIPTION_DESC="Show or hide the description below." +COM_AGOSMS_FIELD_CONFIG_HITS_DESC="Show or hide hits." +COM_AGOSMS_FIELD_CONFIG_ICON_DESC="If Icon is chosen above, select an icon to display with the Agosms. If none is selected, the default icon will be used." +COM_AGOSMS_FIELD_CONFIG_ICON_LABEL="Select Icon" +COM_AGOSMS_FIELD_CONFIG_LINKDESCRIPTION_DESC="Show or hide the links description." +COM_AGOSMS_FIELD_CONFIG_LINKDESCRIPTION_LABEL="Links Description" +COM_AGOSMS_FIELD_CONFIG_OTHERCATS_DESC="Show or hide other categories." +COM_AGOSMS_FIELD_CONFIG_OTHERCATS_LABEL="Other Categories" +COM_AGOSMS_FIELD_CONFIG_SHOWREPORT_DESC="Show or hide the Report Bad Link option." +COM_AGOSMS_FIELD_CONFIG_SHOWREPORT_LABEL="Reports" +COM_AGOSMS_FIELD_COUNTCLICKS_DESC="If set to yes, the number of times the link has been clicked will be recorded." +COM_AGOSMS_FIELD_COUNTCLICKS_LABEL="Count Clicks" +COM_AGOSMS_FIELD_DESCRIPTION_DESC="Enter a description for the agosm." +COM_AGOSMS_FIELD_DISPLAY_NUM_DESC="Default number of Agosms to list on a page." +COM_AGOSMS_FIELD_DISPLAY_NUM_LABEL="# of Agosms to List" +COM_AGOSMS_FIELD_FIRST_DESC="The image to be displayed." +COM_AGOSMS_FIELD_FIRST_LABEL="First Image" + +COM_AGOSMS_FIELD_HEIGHT_DESC="Height of the target popup or modal window. Defaults to 600x500 if one field is left empty." +COM_AGOSMS_FIELD_HEIGHT_LABEL="Height" +COM_AGOSMS_FIELD_ICON_DESC="Displays a text, an icon or nothing with the Agosms. Default is 'Icon'." +COM_AGOSMS_FIELD_ICON_LABEL="Text/Icon/Agosm Only" +COM_AGOSMS_FIELD_ICON_OPTION_ICON="Icon" +COM_AGOSMS_FIELD_ICON_OPTION_TEXT="Text" +COM_AGOSMS_FIELD_ICON_OPTION_AGOSM="Agosm Only" +COM_AGOSMS_FIELD_IMAGE_ALT_DESC="Alternative text used for visitors without access to images. Replaced with caption text if it is present." +COM_AGOSMS_FIELD_IMAGE_ALT_LABEL="Alt Text" +COM_AGOSMS_FIELD_IMAGE_CAPTION_DESC="Caption attached to the image." +COM_AGOSMS_FIELD_IMAGE_CAPTION_LABEL="Caption" + +COM_AGOSMS_FIELD_LANGUAGE_DESC="Assign a language to this agosm." +COM_AGOSMS_FIELD_MODIFIED_DESC="The date and time the link was last modified." +COM_AGOSMS_FIELD_SECOND_DESC="The second image to be displayed." +COM_AGOSMS_FIELD_SECOND_LABEL="Second Image" +COM_AGOSMS_FIELD_SELECT_CATEGORY_DESC="Select a agosms category to display." +COM_AGOSMS_FIELD_SELECT_CATEGORY_LABEL="Select a Category" +COM_AGOSMS_FIELD_SHOW_CAT_TAGS_DESC="Show the tags for a category." +COM_AGOSMS_FIELD_SHOW_CAT_TAGS_LABEL="Show Tags" +COM_AGOSMS_FIELD_SHOW_TAGS_DESC="Show the tags for a agosm." +COM_AGOSMS_FIELD_SHOW_TAGS_LABEL="Show Tags" +COM_AGOSMS_FIELD_STATE_DESC="Set publication status." +COM_AGOSMS_FIELD_TARGET_DESC="Target browser window when the link is selected." +COM_AGOSMS_FIELD_TARGET_LABEL="Target" +COM_AGOSMS_FIELD_TITLE_DESC="Agosm must have a title." +COM_AGOSMS_FIELD_URL_DESC="You must enter a URL. IDN (International) Links are converted to punycode when they are saved." +COM_AGOSMS_FIELD_URL_LABEL="URL" +COM_AGOSMS_FIELD_VALUE_REPORTED="Reported" +COM_AGOSMS_FIELD_VERSION_DESC="A count of the number of times this agosm has been revised." +COM_AGOSMS_FIELD_VERSION_LABEL="Revision" +COM_AGOSMS_FIELD_WIDTH_DESC="Width of the target popup or modal window. Defaults to 600x500 if one field is left empty." +COM_AGOSMS_FIELD_WIDTH_LABEL="Width" +COM_AGOSMS_FIELDSET_IMAGES="Images" +COM_AGOSMS_FIELDSET_OPTIONS="Options" +COM_AGOSMS_FILTER_CATEGORY="Filter Category" +COM_AGOSMS_FILTER_SEARCH_DESC="Search in agosm title and alias. Prefix with ID: to search for a agosm ID." +COM_AGOSMS_FILTER_SEARCH_LABEL="Search Agosms" +COM_AGOSMS_FILTER_STATE="Filter State" +COM_AGOSMS_FLOAT_FIRST_DESC="Controls placement of the first image." +COM_AGOSMS_FLOAT_FIRST_LABEL="First Image Float" +COM_AGOSMS_FLOAT_SECOND_DESC="Controls placement of the second image." +COM_AGOSMS_FLOAT_SECOND_LABEL="Second Image Float" +COM_AGOSMS_HEADING_ASSOCIATION="Association" +COM_AGOSMS_HITS_DESC="Number of hits for this agosm." +COM_AGOSMS_LEFT="Left" +COM_AGOSMS_LIST_LAYOUT_DESC="These settings apply for Agosms List Layout Options unless they are changed for a specific menu item." +COM_AGOSMS_MANAGER_AGOSM="Agosms" +COM_AGOSMS_MANAGER_AGOSMS="Agosms" +COM_AGOSMS_MANAGER_AGOSM_EDIT="Agosm: Edit" +COM_AGOSMS_MANAGER_AGOSM_NEW="Agosm: New" +COM_AGOSMS_N_ITEMS_ARCHIVED="%d agosms successfully archived." +COM_AGOSMS_N_ITEMS_ARCHIVED_1="%d agosm successfully archived." +COM_AGOSMS_N_ITEMS_CHECKED_IN_0="No agosm successfully checked in." +COM_AGOSMS_N_ITEMS_CHECKED_IN_1="%d agosm successfully checked in." +COM_AGOSMS_N_ITEMS_CHECKED_IN_MORE="%d agosms successfully checked in." +COM_AGOSMS_N_ITEMS_DELETED="%d agosms successfully deleted." +COM_AGOSMS_N_ITEMS_DELETED_1="%d agosm successfully deleted." +COM_AGOSMS_N_ITEMS_PUBLISHED="%d agosms successfully published." +COM_AGOSMS_N_ITEMS_PUBLISHED_1="%d agosm successfully published." +COM_AGOSMS_N_ITEMS_TRASHED="%d agosms successfully trashed." +COM_AGOSMS_N_ITEMS_TRASHED_1="%d agosm successfully trashed." +COM_AGOSMS_N_ITEMS_UNPUBLISHED="%d agosms successfully unpublished." +COM_AGOSMS_N_ITEMS_UNPUBLISHED_1="%d agosm successfully unpublished." +COM_AGOSMS_NEW_AGOSM="New Agosm" +COM_AGOSMS_NONE="None" +COM_AGOSMS_OPTION_FILTER_ACCESS="- Filter Access -" +COM_AGOSMS_OPTION_FILTER_CATEGORY="- Filter Category -" +COM_AGOSMS_OPTION_FILTER_PUBLISHED="- Filter State -" +COM_AGOSMS_OPTIONS="Options" +COM_AGOSMS_ORDER_HEADING="Order" +COM_AGOSMS_RIGHT="Right" +COM_AGOSMS_SAVE_SUCCESS="Agosm successfully saved" +COM_AGOSMS_SEARCH_IN_TITLE="Search in title" +COM_AGOSMS_SELECT_A_AGOSM="Select Agosm" +COM_AGOSMS_SHOW_EMPTY_CATEGORIES_DESC="If Show, empty categories will display. A category is only empty - if it has no Agosms or subcategories." +COM_AGOSMS_SUBMENU_CATEGORIES="Categories" +COM_AGOSMS_SUBMENU_AGOSMS="Agosms" +COM_AGOSMS_AGOSMS="Agosms" +COM_AGOSMS_XML_DESCRIPTION="Component for agosms management" +JGLOBAL_NO_ITEM_SELECTED="No agosms selected" +JGLOBAL_NEWITEMSLAST_DESC="New Agosms default to the last position. Ordering can be changed after this Agosm is saved." +JLIB_APPLICATION_ERROR_BATCH_CANNOT_CREATE="You are not allowed to create new agosms in this category." +JLIB_APPLICATION_ERROR_BATCH_CANNOT_EDIT="You are not allowed to edit one or more of these agosms." +JLIB_RULES_SETTING_NOTES="Changes apply to this component only.
Inherited - a Global Configuration setting or higher level setting is applied.
Denied always wins - whatever is set at the Global or higher level and applies to all child elements.
Allowed will enable the action for this component unless it is overruled by a Global Configuration setting." +COM_AGOSMS_FIELDSET_PINOPTIONS="Module Agosms - Marker Options" + +;Copy from Module because load is not possible because the lang file is searched in admin folder todo issue +MOD_AGOSM="AGOSM Module" +MOD_AGOSM_XML_DESCRIPTION="Shows OpenStreetMap with markers and controlls." +;Routing +MOD_AGOSM_ROUTING_CONTROL_FIELDSETLABEL="Routing" +MOD_AGOSM_CONFIG_CONTROL_LANGUAGEROUTING_LABEL="Language" +MOD_AGOSM_CONFIG_CONTROL_LANGUAGEROUTING_DESC="Language in the Routing Control" +MOD_AGOSM_CONFIG_CONTROL_ROUTEWHILEDRAGGING_LABEL="Dragging" +MOD_AGOSM_CONFIG_CONTROL_ROUTEWHILEDRAGGING_DESC="If true, routes will continually be calculated while the user drags waypoints, giving immediate feedback." +MOD_AGOSM_CONFIG_CONTROL_SHOWROUTING_LABEL="Show Routing" +MOD_AGOSM_CONFIG_CONTROL_SHOWROUTING_DESC="Should the routing controll be integrated into the map?" +MOD_AGOSM_ROUTING_FIELDSETDESCRIPTION="Find the way from A to B on the Leaflet map." +MOD_AGOSM_CONFIG_CONTROL_ENDROUTING_DESC="Where do you want to end routing?" +MOD_AGOSM_CONFIG_CONTROL_ENDROUTING_LABEL="End" +MOD_AGOSM_CONFIG_CONTROL_STARTROUTING_DESC="Where do you want to start?" +MOD_AGOSM_CONFIG_CONTROL_STARTROUTING_LABEL="Start" +MOD_AGOSM_CONFIG_CONTROL_UNITROUTING_DESC="Units to use; 'metric' or 'imperial'" +MOD_AGOSM_CONFIG_CONTROL_UNITROUTING_LABEL="Units" +MOD_AGOSM_CONFIG_CONTROL_PROFILEOUTING_LABEL="Routing Profil" +MOD_AGOSM_CONFIG_CONTROL_PROFILEROUTING_DESC="The Mapbox Directions API will show you how to get where you're going. With the Directions API, you can: calculate optimal driving, walking, and cycling (https://www.mapbox.com/api-documentation/#directions)." +;esriGeocoder +MOD_AGOSM_DEFAULT_ESRI_GEOCODER_PLACEHOLDER="Search for places or addresses" +MOD_AGOSM_DEFAULT_ESRI_GEOCODER_TITLE="Location Search" +MOD_AGOSM_CONFIG_CONTROL_ESRIGEOGODER_ALLOWMULTIPELRESULTS_DESC="If set to true and the user submits the form without a suggestion, the control will geocode the current text in the input." +MOD_AGOSM_CONFIG_CONTROL_ESRIGEOGODER_ALLOWMULTIPELRESULTS_LABEL="Allow multiple Results" +MOD_AGOSM_CONFIG_CONTROL_ESRIGEOGODER_COLLAPSEAFTERRESULT_DESC="Determines whether or not the geocoder should collapse after a result is found." +MOD_AGOSM_CONFIG_CONTROL_ESRIGEOGODER_COLLAPSEAFTERRESULT_LABEL="Collapse after Result" +MOD_AGOSM_CONFIG_CONTROL_ESRIGEOGODER_EXPANDED_DESC="Determines whether or not the geocoder starts in an expanded state." +MOD_AGOSM_CONFIG_CONTROL_ESRIGEOGODER_EXPANDED_LABEL="Expanded" +MOD_AGOSM_CONFIG_CONTROL_ESRIGEOGODER_POSITION_DESC="The position on the map" +MOD_AGOSM_CONFIG_CONTROL_ESRIGEOGODER_POSITION_LABEL="Position" +MOD_AGOSM_CONFIG_CONTROL_ESRIGEOGODER_USEMAPBOUNDS_DESC="Determines if and when the geocoder should use the bounds of the map to filter search results. If true the geocoder will always return results in the current map bounds. If false it will always search the world." +MOD_AGOSM_CONFIG_CONTROL_ESRIGEOGODER_USEMAPBOUNDS_LABEL="Use MapBounds" +MOD_AGOSM_CONFIG_CONTROL_ESRIGEOGODER_ZOOMTORESULTS_DESC="Determines whether or not the map will zoom to the result after geocoding is complete." +MOD_AGOSM_CONFIG_CONTROL_ESRIGEOGODER_ZOOMTORESULTS_LABEL="Zoom to Results" +MOD_AGOSM_CONFIG_CONTROL_SHOWGEOCODERESRI_DESC="Should the ESRI geocoder controll be integrated into the map?" +MOD_AGOSM_CONFIG_CONTROL_SHOWGEOCODERESRI_LABEL="Show ESRI Geocoder" +MOD_AGOSM_GEOCODER_ESRI_CONTROL_FIELDSETLABEL="ESRI Geocoder" +MOD_AGOSM_GEOCODER_ESRI_FIELDSETDESCRIPTION="ESRI Geocoder" +MOD_AGOSM_CONFIG_CONTROL_ESRIGEOGODER_OPENGETADDRESS_DESC="Opens the address given as a value of the GET-variable with the name address. The map is pan to the address it is not overridden. This can be overridden in other tabs. For example in the tab Geocoder Control with the option Bounds or from the Routing Control." +MOD_AGOSM_CONFIG_CONTROL_ESRIGEOGODER_OPENGETADDRESS_LABEL="Open address" +MOD_AGOSM_CONFIG_CONTROL_ESRIGEOGODER_REVERSE_DESC="Reverse geocoding is the process of back (reverse) coding of a point location (latitude, longitude) to a readable address or place name. Click on the map and a marker is created with the address of the clicked point. Attention: This option is not easy if you use markers with pop ups. The user have to be pay attention to where he clicks, if he wants to open a pop up. If he misses slightly, a new marker will be created if this option is activated." +MOD_AGOSM_CONFIG_CONTROL_ESRIGEOGODER_REVERSE_LABEL="Reverse geocoding" +MOD_AGOSM_CONFIG_CONTROL_USEESRI_DESC="Use Geocoding Service of the Environmental Systems Research Institute (ESRI)" +MOD_AGOSM_CONFIG_CONTROL_USEESRI_LABEL="Use ESRI Geocoding Service" +;osmGeocoder +MOD_AGOSM_CONTROL_HEADLINE="Position and Behaviour of the Control" +MOD_AGOSM_CONFIG_CONTROL_SHOWGEOCODER_LABEL="Show OSM Geocoder" +MOD_AGOSM_CONFIG_CONTROL_SHOWGEOCODER_DESC="Should the OSM geocoder controll be integrated into the map?" +MOD_AGOSM_GEOCODER_CONTROL_FIELDSETLABEL="OSM Geocoder Control" +MOD_AGOSM_GEOCODER_FIELDSETDESCRIPTION="Leaflet Control OSM Geocoder. A simple geocoder that uses the OpenstreetMap gecoder Nominatim to locate places." +MOD_AGOSM_CONFIG_CONTROL_COLLAPSED_LABEL="Collapsed" +MOD_AGOSM_CONFIG_CONTROL_COLLAPSED_DESC="Whether its collapsed or not." +MOD_AGOSM_CONFIG_CONTROL_EXPAND_LABEL="Expand" +MOD_AGOSM_CONFIG_CONTROL_EXPAND_DESC="How to expand a collapsed control: touch, click or hover." +MOD_AGOSM_CONFIG_CONTROL_POSITION_LABEL="Positon" +MOD_AGOSM_CONFIG_CONTROL_POSITION_DESC="The position of the map" +MOD_AGOSM_CONFIG_CONTROL_POSITION_BOTTOMLEFT="bottom left" +MOD_AGOSM_CONFIG_CONTROL_POSITION_BOTTOMRIGHT="bottom right" +MOD_AGOSM_CONFIG_CONTROL_POSITION_TOPLEFT="top left" +MOD_AGOSM_CONFIG_CONTROL_POSITION_TOPRIGHT="top right" +MOD_AGOSM_DEFAULT_TEXT_PLACEHOLDER="Postcode, city or full address" +MOD_AGOSM_DEFAULT_TEXT_ERRORMESSAGE="Nothing found." +MOD_AGOSM_DEFAULT_TEXT="Search" +; Special marker +MOD_AGOSM_CONFIG_CONTROL_SHOWPIN_LABEL="Show Pin" +MOD_AGOSM_CONFIG_CONTROL_SHOWPIN_DESC="Do you like to display a special pin?" +MOD_AGOSM_H_PIN="Special Pin configuration" +MOD_AGOSM_PIN="Select pin" +MOD_AGOSM_PIN_DESC="Select the last option if wonna use your own." +MOD_AGOSM_OWNPIN="own (custom style)" +MOD_AGOSM_NOPIN="no pin" +MOD_AGOSM_PINPATH="Own pin (custom style)" +MOD_AGOSM_PINPATH_DESC="Path to your pin." +MOD_AGOSM_PINSIZE="Pin size" +MOD_AGOSM_PINSIZE_DESC="Size of the icon. Example: '38, 95'" +MOD_AGOSM_PINSHADOWPATH="Shadow" +MOD_AGOSM_PINSHADOWPATH_DESC="Path to the image of the shadow of your pin." +MOD_AGOSM_PINSHADOWSIZE="Shadow size" +MOD_AGOSM_PINSHADOWSIZE_DESC="Size of the icon. Example: '50, 64'" +MOD_AGOSM_PINOFFSET="Pin point offset" +MOD_AGOSM_PINOFFSET_DESC="Point of the icon which will correspond to marker's location. Example: '22, 94'" +MOD_AGOSM_PINPOPUPOFFSET="Popup offset" +MOD_AGOSM_PINPOPUPOFFSET_DESC="Point from which the popup should open relative to the iconAnchor. Example: '-3, -76'" +MOD_AGOSM_POSITION="Start position | This can be overridden in other tabs. For example in the tab Geocoder Control with the option Bounds or from the Routing Control." +MOD_AGOSM_LON="Longitude" +MOD_AGOSM_LON_DESC="Pin position LON" +MOD_AGOSM_LAT="Latitude" +MOD_AGOSM_LAT_DESC="Pin position LAT" +MOD_AGOSM_HPOPUP="Popup" +MOD_AGOSM_POPUP="Show popup" +MOD_AGOSM_KLICK="show on click" +MOD_AGOSM_ALWAYS="show immediately" +MOD_AGOSM_NEVER="no popup" +MOD_AGOSM_POPUP_DESC="Show a popup window if you klick to the pin. You can only open one popup at a time." +MOD_AGOSM_POPUPTEXT="Text in Popup" +MOD_AGOSM_POPUPTEXT_DESC="HTML is alowed" +MOD_AGOSM_CONFIG_CONTROL_SUBFORM_DESC="You can add as many markers as you like." +MOD_AGOSM_CONFIG_CONTROL_SUBFORM_LABEL="Add Marker" +;Map Config +MOD_AGOSM_H_MAP="Map configuration" +MOD_AGOSM_HEIGHT="Map height" +MOD_AGOSM_HEIGHT_DESC="in px" +MOD_AGOSM_ZOOM="Zoomlevel" +MOD_AGOSM_ZOOM_DESC="1: far awaly, 20: very close" +MOD_AGOSM_BASELAYER="Map style" +MOD_AGOSM_BASELAYER_DESC="Mapnik is the default style of OpenStreetMap." +MOD_AGOSM_CUSTOMBASELAYER="Custom map style" +MOD_AGOSM_CUSTOMBASELAYER_DESC="Only if choosed 'custom' as map style. Who delivers the map style (tiles)?" +MOD_AGOSM_CUSTOMBASELAYERURL="Tile server URL" +MOD_AGOSM_CUSTOMBASELAYERURL_DESC="Only if choosed 'custom' as map style. Example: http://tile.openstreetmap.org/{z}/{x}/{y}.png" +MOD_AGOSM_NOWORLDWARP="No Worldwarp" +MOD_AGOSM_NOWORLDWARP_DESC="Normally the world will continue to the left and the right. This option restricts the view to 'one world'." +MOD_AGOSM_ATTRIMAGERY="Show map style" +MOD_AGOSM_ATTRIMAGERY_DESC="Shows the used map style. With mapbox the deactivation is ignored." +MOD_AGOSM_ATTRMODULE="Show Author note" +MOD_AGOSM_ATTRMODULE_DESC="Show a link to my website." +MOD_AGOSM_SCALE="Scale" +MOD_AGOSM_SCALE_DESC="Show scale in the bottom left corner." +MOD_AGOSM_METRIC="Metric (m/km)" +MOD_AGOSM_IMPERIAL="Imperial (ft/mi)" +MOD_AGOSM_MODULE_BY="Module by" +MOD_AGOSM_MAPBOXKEY_DESC="To use any of Mapbox’s tools, APIs, or SDKs, you’ll need a Mapbox access token. See https://www.mapbox.com/help/define-access-token/. You can use the same access-token for routing and for displaying map." +MOD_AGOSM_MAPBOXKEY_LABEL="Access token" +MOD_AGOSM_DETECTRETINA="Detect retina display" +MOD_AGOSM_DETECTRETINA_DESC="If true and user is on a retina display, it will request four tiles of half the specified size and a bigger zoom level in place of one to utilize the high resolution." +MOD_AGOSM_LATLON="Coordinates" +MOD_AGOSM_LATLON_DESC="Coordinates at geographic coordinate system: Latitude and Longitude" +MOD_AGOSM_LONLAT="Coordinates" +MOD_AGOSM_LONLAT_DESC="Coordinates at geographic coordinate system: Longitude and Latitude" +MOD_AGOSM_CONTROL_GOOGLETYPE_DESC="There are four types of maps available within the Google Maps JavaScript API." +MOD_AGOSM_CONTROL_GOOGLETYPE_LABEL="Google Maps Map Type" +MOD_AGOSM_CONTROL_MAPBOXMAPTYPE_DESC="What map tiles should be displayed on the map? Map tiles are square bitmap graphics displayed in a grid arrangement: http://wiki.openstreetmap.org/wiki/Tiles" +MOD_AGOSM_CONTROL_MAPBOXMAPTYPE_LABEL="Mapbox Tile Server" +MOD_AGOSM_CONTROL_STAMENMAPTYPE_DESC="What map tiles should be displayed on the map? Map tiles are square bitmap graphics displayed in a grid arrangement: http://wiki.openstreetmap.org/wiki/Tiles. For stamen you can find more informations here: http://maps.stamen.com/" +MOD_AGOSM_CONTROL_STAMENMAPTYPE_LABEL="Stamen Tile Server" +MOD_AGOSM_CONTROL_THUNDERFORESTMAPTYPE_DESC="Thunderforest Map Type: See https://thunderforest.com/maps/" +MOD_AGOSM_CONTROL_THUNDERFORESTMAPTYPE_LABEL="Thunderforest Map Type" +MOD_AGOSM_GOOGLEKEY_DESC="To use the Google Maps JavaScript API, you must register your app project on the Google API Console and get a Google API key which you can add to your app." +MOD_AGOSM_GOOGLEKEY_LABEL="Google Maps API Key" +MOD_AGOSM_THUNDERKEY_DESC="Thunderforest API Key: See https://thunderforest.com/docs/apikeys/" +MOD_AGOSM_THUNDERKEY_LABEL="Thunderforest API Key" +;Custom markers fontawesom +MOD_AGOSM_AWESOMEPLUGIN="pin with fontawesome icon" +MOD_AGOSM_FONTAWESOME_EXTRACLASSES_DESC="Extra class. Additional classes for the icon. You can use a custom class (myclass) or font awesome classe (fa-rotate90) or both (fa-rotate90 myclass). For more informations see http://fontawesome.io/examples/. NOTE: You can not use all font awesome classe. Some are overwritten by this plugin. Especially the classes concerning the size of the icon." +MOD_AGOSM_FONTAWESOME_EXTRACLASSES_LABEL="Extra class" +MOD_AGOSM_FONTAWESOME_ICONCOLOR_DESC="Color of the icon." +MOD_AGOSM_FONTAWESOME_ICONCOLOR_LABEL="Icon color" +MOD_AGOSM_FONTAWESOME_ICON_DESC="The icon image. For more information please see: http://fontawesome.io/icons/" +MOD_AGOSM_FONTAWESOME_ICON_LABEL="Icon image" +MOD_AGOSM_FONTAWESOME_MARKERCOLOR_DESC="Color of the marker." +MOD_AGOSM_FONTAWESOME_MARKERCOLOR_LABEL="Marker color" +MOD_AGOSM_FONTAWESOME_SPIN_DESC="Make the icon spin" +MOD_AGOSM_FONTAWESOME_SPIN_LABEL="Spin" +; show component pins +MOD_AGOSM_H_PINCOM="Show pins from component" +MOD_AGOSM_CONFIG_CONTROL_SHOWCOMPONENTPIN_DESC="Do you like to display a special pin from the component com_agosms?" +MOD_AGOSM_CONFIG_CONTROL_SHOWCOMPONENTPIN_LABEL="Show Pin from component com_agosms" diff --git a/dist/agosms-1.0.26/administrator/language/en-GB/en-GB.com_agosms.sys.ini b/dist/agosms-1.0.26/administrator/language/en-GB/en-GB.com_agosms.sys.ini new file mode 100644 index 00000000..63fbae98 --- /dev/null +++ b/dist/agosms-1.0.26/administrator/language/en-GB/en-GB.com_agosms.sys.ini @@ -0,0 +1,35 @@ +; Joomla! Project +; Copyright (C) 2005 - 2017 Open Source Matters. All rights reserved. +; License GNU General Public License version 2 or later; see LICENSE.txt, see LICENSE.php +; Note : All ini files need to be saved as UTF-8 +COM_AGOSM="Agosm" +COM_AGOSM_ACCESS_UPDATE_DESC="Secret Word for updating. You also need to activate the Installer Plugin. If you lost the secret word you can write an email to info@astrid-guenther.de." +COM_AGOSM_ACCESS_UPDATE_LABEL="Secret Word for updating" +COM_AGOSM_CONFIGURATION="Agosm Configuration" +COM_AGOSM_HERE_A_COMPONENT_IS_CREATED="The component is not finished yet. We need it only for creating the views for the module buttons and for updating the module." +COM_AGOSM_INSERT="Insert" + +COM_AGOSMS="Agosms" +COM_AGOSMS_CATEGORIES="Categories" +COM_AGOSMS_CATEGORIES_VIEW_DEFAULT_DESC="Show all the agosm categories within a category." +COM_AGOSMS_CATEGORIES_VIEW_DEFAULT_OPTION="Default" +COM_AGOSMS_CATEGORIES_VIEW_DEFAULT_TITLE="List All Agosm Categories" +COM_AGOSMS_CATEGORY_ADD_TITLE="Category Manager: Add A New Agosms Category" +COM_AGOSMS_CATEGORY_EDIT_TITLE="Category Manager: Edit A Agosms Category" +COM_AGOSMS_CATEGORY_VIEW_DEFAULT_DESC="Displays a list of Agosms for a category." +COM_AGOSMS_CATEGORY_VIEW_DEFAULT_OPTION="Default" +COM_AGOSMS_CATEGORY_VIEW_DEFAULT_TITLE="List Agosms in a Category" +COM_AGOSMS_CONTENT_TYPE_AGOSM="Agosm" +COM_AGOSMS_CONTENT_TYPE_CATEGORY="Agosms Category" +COM_AGOSMS_FIELD_SELECT_AGOSM_DESC="Select the desired Agosm from the list." +COM_AGOSMS_FIELD_SELECT_AGOSM_LABEL="Select Agosm" +COM_AGOSMS_FORM_VIEW_DEFAULT_DESC="Display a form to submit a agosm in the Frontend." +COM_AGOSMS_FORM_VIEW_DEFAULT_OPTION="Default" +COM_AGOSMS_FORM_VIEW_DEFAULT_TITLE="Submit a Agosm" +COM_AGOSMS_LINKS="Links" +COM_AGOSMS_TAGS_AGOSM="Agosm" +COM_AGOSMS_TAGS_CATEGORY="Agosm Category" +COM_AGOSMS_AGOSM_VIEW_DEFAULT_DESC="Display a single Agosm" +COM_AGOSMS_AGOSM_VIEW_DEFAULT_TITLE="Single Agosm" +COM_AGOSMS_XML_DESCRIPTION="Component for agosms management." + diff --git a/dist/agosms-1.0.26/administrator/manifests/packages/agosms/script.php b/dist/agosms-1.0.26/administrator/manifests/packages/agosms/script.php new file mode 100644 index 00000000..60b916db --- /dev/null +++ b/dist/agosms-1.0.26/administrator/manifests/packages/agosms/script.php @@ -0,0 +1,32 @@ +minimumJoomla = '3.7.0'; + $this->minimumPhp = JOOMLA_MINIMUM_PHP; + } +} diff --git a/dist/agosms-1.0.26/administrator/manifests/packages/pkg_agosms.xml b/dist/agosms-1.0.26/administrator/manifests/packages/pkg_agosms.xml new file mode 100644 index 00000000..b9837967 --- /dev/null +++ b/dist/agosms-1.0.26/administrator/manifests/packages/pkg_agosms.xml @@ -0,0 +1,31 @@ + + + pkg_agosms + agosm + 2018-12-09 + Astrid Günther + (C) 2018 Astrid Günther. All rights reserved. + info@astrid-guenther.de + www.astrid-guenther.de + Astrid Günther + info@astrid-guenther.de + www.astrid-guenther.de + 1.0.26 + GNU General Public License version 2 or later; see LICENSE.txt + PKG_AGOSM_XML_DESCRIPTION + script.php + + + com_agosms.zip + mod_agosm.zip + plg_content_agosm.zip + plg_installer_agosminstaller.zip + + + en-GB/en-GB.pkg_agosms.sys.ini + de-DE/de-DE.pkg_agosms.sys.ini + + + https://raw.githubusercontent.com/astridx/pkg_agosms/master/agosms-update.xml + + diff --git a/dist/agosms-1.0.26/administrator/manifests/packages/pkg_agosms/language/de-DE/de-DE.pkg_agosms.sys.ini b/dist/agosms-1.0.26/administrator/manifests/packages/pkg_agosms/language/de-DE/de-DE.pkg_agosms.sys.ini new file mode 100644 index 00000000..017e66df --- /dev/null +++ b/dist/agosms-1.0.26/administrator/manifests/packages/pkg_agosms/language/de-DE/de-DE.pkg_agosms.sys.ini @@ -0,0 +1,2 @@ +PKG_AGOSM="OSM Erweiterungspaket" +PKG_AGOSM_XML_DESCRIPTION="Dieses Erweiterungspaket enthält alle Erweiterungen zu Agosm." diff --git a/dist/agosms-1.0.26/administrator/manifests/packages/pkg_agosms/language/en-GB/en-GB.pkg_agosms.sys.ini b/dist/agosms-1.0.26/administrator/manifests/packages/pkg_agosms/language/en-GB/en-GB.pkg_agosms.sys.ini new file mode 100644 index 00000000..564c7f3b --- /dev/null +++ b/dist/agosms-1.0.26/administrator/manifests/packages/pkg_agosms/language/en-GB/en-GB.pkg_agosms.sys.ini @@ -0,0 +1,2 @@ +PKG_AGOSM="AGOSM Extension Package" +PKG_AGOSM_XML_DESCRIPTION="The AGOSM package includes the Agosm extensions." diff --git a/dist/agosms-1.0.26/agosms.xml b/dist/agosms-1.0.26/agosms.xml new file mode 100644 index 00000000..78da6771 --- /dev/null +++ b/dist/agosms-1.0.26/agosms.xml @@ -0,0 +1,91 @@ + + + com_agosms + Joomla! Project + 2018-12-09 + (C) 2005 - 2018 Open Source Matters. All rights reserved. + GNU General Public License version 2 or later; see LICENSE.txt + admin@joomla.org + www.joomla.org + 1.0.26 + COM_AGOSMS_XML_DESCRIPTION + script.php + + + + sql/install.mysql.sql + sql/install.postgresql.sql + sql/install.sqlsrv.sql + + + + + sql/uninstall.mysql.sql + sql/uninstall.postgresql.sql + sql/uninstall.sqlsrv.sql + + + + + sql/updates/mysql + sql/updates/postgresql + sql/updates/sqlsrv + + + + + js +leaflet + + + + controllers +controller.php +models +agosms.php +helpers +metadata.xml +router.php +views + + + de-DE/de-DE.com_agosms.sys.ini +de-DE/de-DE.com_agosms.ini +en-GB/en-GB.com_agosms.sys.ini +en-GB/en-GB.com_agosms.ini + + + com_agosms + + + com_agosms_links + com_agosms_categories + + + controllers +controller.php +models +agosms.php +access.xml +tables +script.php +helpers +sql +config.xml +agosms.xml +views + + + de-DE/de-DE.com_agosms.sys.ini +de-DE/de-DE.com_agosms.ini +en-GB/en-GB.com_agosms.sys.ini +en-GB/en-GB.com_agosms.ini + + + + diff --git a/dist/agosms-1.0.26/components/com_agosms/agosms.php b/dist/agosms-1.0.26/components/com_agosms/agosms.php new file mode 100644 index 00000000..b0dbd52b --- /dev/null +++ b/dist/agosms-1.0.26/components/com_agosms/agosms.php @@ -0,0 +1,17 @@ +execute(JFactory::getApplication()->input->get('task')); +$controller->redirect(); diff --git a/dist/agosms-1.0.26/components/com_agosms/controller.php b/dist/agosms-1.0.26/components/com_agosms/controller.php new file mode 100644 index 00000000..bb24c27f --- /dev/null +++ b/dist/agosms-1.0.26/components/com_agosms/controller.php @@ -0,0 +1,68 @@ +input->getInt('w_id'); + $vName = $this->input->get('view', 'categories'); + $this->input->set('view', $vName); + + if (JFactory::getUser()->id ||($this->input->getMethod() == 'POST' && $vName == 'categories')) + { + $cacheable = false; + } + + $safeurlparams = array( + 'id' => 'INT', + 'limit' => 'UINT', + 'limitstart' => 'UINT', + 'filter_order' => 'CMD', + 'filter_order_Dir' => 'CMD', + 'lang' => 'CMD' + ); + + // Check for edit form. + if ($vName == 'form' && !$this->checkEditId('com_agosms.edit.agosm', $id)) + { + // Somehow the person just went to the form - we don't allow that. + return JError::raiseError(403, JText::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $id)); + } + + return parent::display($cacheable, $safeurlparams); + } +} diff --git a/dist/agosms-1.0.26/components/com_agosms/controllers/agosm.php b/dist/agosms-1.0.26/components/com_agosms/controllers/agosm.php new file mode 100644 index 00000000..34010fd7 --- /dev/null +++ b/dist/agosms-1.0.26/components/com_agosms/controllers/agosm.php @@ -0,0 +1,314 @@ +setRedirect($this->getReturnPage()); + } + } + + /** + * Method override to check if you can add a new record. + * + * @param array $data An array of input data. + * + * @return boolean + * + * @since 1.6 + */ + protected function allowAdd($data = array()) + { + $categoryId = ArrayHelper::getValue($data, 'catid', $this->input->getInt('id'), 'int'); + $allow = null; + + if ($categoryId) + { + // If the category has been passed in the URL check it. + $allow = JFactory::getUser()->authorise('core.create', $this->option . '.category.' . $categoryId); + } + + if ($allow !== null) + { + return $allow; + } + + // In the absense of better information, revert to the component permissions. + return parent::allowAdd($data); + } + + /** + * Method to check if you can add a new record. + * + * @param array $data An array of input data. + * @param string $key The name of the key for the primary key. + * + * @return boolean + * + * @since 1.6 + */ + protected function allowEdit($data = array(), $key = 'id') + { + $recordId = (int) isset($data[$key]) ? $data[$key] : 0; + $categoryId = 0; + + if ($recordId) + { + $categoryId = (int) $this->getModel()->getItem($recordId)->catid; + } + + if ($categoryId) + { + // The category has been set. Check the category permissions. + return JFactory::getUser()->authorise('core.edit', $this->option . '.category.' . $categoryId); + } + + // Since there is no asset tracking, revert to the component permissions. + return parent::allowEdit($data, $key); + } + + /** + * Method to cancel an edit. + * + * @param string $key The name of the primary key of the URL variable. + * + * @return boolean True if access level checks pass, false otherwise. + * + * @since 1.6 + */ + public function cancel($key = 'w_id') + { + $return = parent::cancel($key); + + // Redirect to the return page. + $this->setRedirect($this->getReturnPage()); + + return $return; + } + + /** + * Method to edit an existing record. + * + * @param string $key The name of the primary key of the URL variable. + * @param string $urlVar The name of the URL variable if different from the primary key (sometimes required to avoid router collisions). + * + * @return boolean True if access level check and checkout passes, false otherwise. + * + * @since 1.6 + */ + public function edit($key = null, $urlVar = 'w_id') + { + return parent::edit($key, $urlVar); + } + + /** + * Method to get a model object, loading it if required. + * + * @param string $name The model name. Optional. + * @param string $prefix The class prefix. Optional. + * @param array $config Configuration array for model. Optional. + * + * @return object The model. + * + * @since 1.5 + */ + public function getModel($name = 'form', $prefix = '', $config = array('ignore_request' => true)) + { + return parent::getModel($name, $prefix, $config); + } + + /** + * Gets the URL arguments to append to an item redirect. + * + * @param integer $recordId The primary key id for the item. + * @param string $urlVar The name of the URL variable for the id. + * + * @return string The arguments to append to the redirect URL. + * + * @since 1.6 + */ + protected function getRedirectToItemAppend($recordId = null, $urlVar = null) + { + $append = parent::getRedirectToItemAppend($recordId, $urlVar); + $itemId = $this->input->getInt('Itemid'); + $return = $this->getReturnPage(); + + if ($itemId) + { + $append .= '&Itemid=' . $itemId; + } + + if ($return) + { + $append .= '&return=' . base64_encode($return); + } + + return $append; + } + + /** + * Get the return URL if a "return" variable has been passed in the request + * + * @return string The return URL. + * + * @since 1.6 + */ + protected function getReturnPage() + { + $return = $this->input->get('return', null, 'base64'); + + if (empty($return) || !JUri::isInternal(base64_decode($return))) + { + return JUri::base(); + } + + return base64_decode($return); + } + + /** + * Method to save a record. + * + * @param string $key The name of the primary key of the URL variable. + * @param string $urlVar The name of the URL variable if different from the primary key (sometimes required to avoid router collisions). + * + * @return boolean True if successful, false otherwise. + * + * @since 1.6 + */ + public function save($key = null, $urlVar = 'w_id') + { + // Get the application + $app = JFactory::getApplication(); + + // Get the data from POST + $data = $this->input->post->get('jform', array(), 'array'); + + // Save the data in the session. + $app->setUserState('com_agosms.edit.agosm.data', $data); + $result = parent::save($key, $urlVar); + + // If ok, redirect to the return page. + if ($result) + { + // Flush the data from the session + $app->setUserState('com_agosms.edit.agosm.data', null); + $this->setRedirect($this->getReturnPage()); + } + + return $result; + } + + /** + * Go to a agosm + * + * @return void + * + * @since 1.6 + */ + public function go() + { + // Get the ID from the request + $id = $this->input->getInt('id'); + + // Get the model, requiring published items + $modelLink = $this->getModel('Agosm', '', array('ignore_request' => true)); + $modelLink->setState('filter.published', 1); + + // Get the item + $link = $modelLink->getItem($id); + + // Make sure the item was found. + if (empty($link)) + { + return JError::raiseWarning(404, JText::_('COM_AGOSMS_ERROR_AGOSM_NOT_FOUND')); + } + + // Check whether item access level allows access. + $groups = JFactory::getUser()->getAuthorisedViewLevels(); + + if (!in_array($link->access, $groups)) + { + return JError::raiseError(403, JText::_('JERROR_ALERTNOAUTHOR')); + } + + // Check whether category access level allows access. + $modelCat = $this->getModel('Category', 'AgosmsModel', array('ignore_request' => true)); + $modelCat->setState('filter.published', 1); + + // Get the category + $category = $modelCat->getCategory($link->catid); + + // Make sure the category was found. + if (empty($category)) + { + return JError::raiseWarning(404, JText::_('COM_AGOSMS_ERROR_AGOSM_NOT_FOUND')); + } + + // Check whether item access level allows access. + if (!in_array($category->access, $groups)) + { + return JError::raiseError(403, JText::_('JERROR_ALERTNOAUTHOR')); + } + + // Redirect to the URL + // @todo: Probably should check for a valid http link + if ($link->url) + { + $modelLink->hit($id); + JFactory::getApplication()->redirect($link->url, 301); + } + + return JError::raiseWarning(404, JText::_('COM_AGOSMS_ERROR_AGOSM_URL_INVALID')); + } +} diff --git a/dist/agosms-1.0.26/components/com_agosms/helpers/association.php b/dist/agosms-1.0.26/components/com_agosms/helpers/association.php new file mode 100644 index 00000000..d9cfd941 --- /dev/null +++ b/dist/agosms-1.0.26/components/com_agosms/helpers/association.php @@ -0,0 +1,64 @@ +input; + $view = is_null($view) ? $jinput->get('view') : $view; + $id = empty($id) ? $jinput->getInt('id') : $id; + + if ($view === 'agosm') + { + if ($id) + { + $associations = JLanguageAssociations::getAssociations('com_agosms', '#__agosms', 'com_agosms.item', $id); + + $return = array(); + + foreach ($associations as $tag => $item) + { + $return[$tag] = AgosmsHelperRoute::getAgosmRoute($item->id, (int) $item->catid, $item->language); + } + + return $return; + } + } + + if ($view == 'category' || $view == 'categories') + { + return self::getCategoryAssociations($id, 'com_agosms'); + } + + return array(); + } +} diff --git a/dist/agosms-1.0.26/components/com_agosms/helpers/category.php b/dist/agosms-1.0.26/components/com_agosms/helpers/category.php new file mode 100644 index 00000000..da1e431d --- /dev/null +++ b/dist/agosms-1.0.26/components/com_agosms/helpers/category.php @@ -0,0 +1,34 @@ +' . $button . ''; + } + + /** + * Create a link to edit an existing agosm + * + * @param object $agosm Agosm data + * @param \Joomla\Registry\Registry $params Item params + * @param array $attribs Unused + * + * @return string + */ + public static function edit($agosm, $params, $attribs = array()) + { + $uri = JUri::getInstance(); + + if ($params && $params->get('popup')) + { + return; + } + + if ($agosm->state < 0) + { + return; + } + + JHtml::_('bootstrap.tooltip'); + + $url = AgosmsHelperRoute::getFormRoute($agosm->id, base64_encode($uri)); + $icon = $agosm->state ? 'edit.png' : 'edit_unpublished.png'; + $text = JHtml::_('image', 'system/' . $icon, JText::_('JGLOBAL_EDIT'), null, true); + + if ($agosm->state == 0) + { + $overlib = JText::_('JUNPUBLISHED'); + } + else + { + $overlib = JText::_('JPUBLISHED'); + } + + $date = JHtml::_('date', $agosm->created); + $author = $agosm->created_by_alias ? $agosm->created_by_alias : $agosm->author; + + $overlib .= '<br />'; + $overlib .= $date; + $overlib .= '<br />'; + $overlib .= htmlspecialchars($author, ENT_COMPAT, 'UTF-8'); + + $button = JHtml::_('link', JRoute::_($url), $text); + + return '' . $button . ''; + } +} diff --git a/dist/agosms-1.0.26/components/com_agosms/helpers/route.php b/dist/agosms-1.0.26/components/com_agosms/helpers/route.php new file mode 100644 index 00000000..8682ffc4 --- /dev/null +++ b/dist/agosms-1.0.26/components/com_agosms/helpers/route.php @@ -0,0 +1,272 @@ + array((int) $id)); + + // Create the link + $link = 'index.php?option=com_agosms&view=agosm&id=' . $id; + + if ($catid > 1) + { + $categories = JCategories::getInstance('Agosms'); + $category = $categories->get($catid); + + if ($category) + { + $needles['category'] = array_reverse($category->getPath()); + $needles['categories'] = $needles['category']; + $link .= '&catid=' . $catid; + } + } + + if ($language && $language != "*" && JLanguageMultilang::isEnabled()) + { + self::buildLanguageLookup(); + + if (isset(self::$lang_lookup[$language])) + { + $link .= '&lang=' . self::$lang_lookup[$language]; + $needles['language'] = $language; + } + } + + if ($item = self::_findItem($needles)) + { + $link .= '&Itemid=' . $item; + } + + return $link; + } + + /** + * Ge the form route + * + * @param integer $id The id of the agosm. + * @param string $return The return page variable. + * + * @return string + */ + public static function getFormRoute($id, $return = null) + { + // Create the link. + if ($id) + { + $link = 'index.php?option=com_agosms&task=agosm.edit&w_id=' . $id; + } + else + { + $link = 'index.php?option=com_agosms&task=agosm.add&w_id=0'; + } + + if ($return) + { + $link .= '&return=' . $return; + } + + return $link; + } + + /** + * Get the Category Route + * + * @param JCategoryNode|string|integer $catid JCategoryNode object or category ID + * @param integer $language Language code + * + * @return string + */ + public static function getCategoryRoute($catid, $language = 0) + { + if ($catid instanceof JCategoryNode) + { + $id = $catid->id; + $category = $catid; + } + else + { + $id = (int) $catid; + $category = JCategories::getInstance('Agosms')->get($id); + } + + if ($id < 1 || !($category instanceof JCategoryNode)) + { + $link = ''; + } + else + { + $needles = array(); + + // Create the link + $link = 'index.php?option=com_agosms&view=category&id=' . $id; + + $catids = array_reverse($category->getPath()); + $needles['category'] = $catids; + $needles['categories'] = $catids; + + if ($language && $language != "*" && JLanguageMultilang::isEnabled()) + { + self::buildLanguageLookup(); + + if (isset(self::$lang_lookup[$language])) + { + $link .= '&lang=' . self::$lang_lookup[$language]; + $needles['language'] = $language; + } + } + + if ($item = self::_findItem($needles)) + { + $link .= '&Itemid=' . $item; + } + } + + return $link; + } + + /** + * Do a language lookup + * + * @return void + */ + protected static function buildLanguageLookup() + { + if (count(self::$lang_lookup) == 0) + { + $db = JFactory::getDbo(); + $query = $db->getQuery(true) + ->select('a.sef AS sef') + ->select('a.lang_code AS lang_code') + ->from('#__languages AS a'); + + $db->setQuery($query); + $langs = $db->loadObjectList(); + + foreach ($langs as $lang) + { + self::$lang_lookup[$lang->lang_code] = $lang->sef; + } + } + } + + /** + * Find items per given $needles + * + * @param array $needles A given array of needles to find + * + * @return void + */ + protected static function _findItem($needles = null) + { + $app = JFactory::getApplication(); + $menus = $app->getMenu('site'); + $language = isset($needles['language']) ? $needles['language'] : '*'; + + // Prepare the reverse lookup array. + if (!isset(self::$lookup[$language])) + { + self::$lookup[$language] = array(); + + $component = JComponentHelper::getComponent('com_agosms'); + + $attributes = array('component_id'); + $values = array($component->id); + + if ($language != '*') + { + $attributes[] = 'language'; + $values[] = array($needles['language'], '*'); + } + + $items = $menus->getItems($attributes, $values); + + if ($items) + { + foreach ($items as $item) + { + if (isset($item->query) && isset($item->query['view'])) + { + $view = $item->query['view']; + + if (!isset(self::$lookup[$language][$view])) + { + self::$lookup[$language][$view] = array(); + } + + if (isset($item->query['id'])) + { + /** + * Here it will become a bit tricky + * language != * can override existing entries + * language == * cannot override existing entries + */ + if (!isset(self::$lookup[$language][$view][$item->query['id']]) || $item->language != '*') + { + self::$lookup[$language][$view][$item->query['id']] = $item->id; + } + } + } + } + } + } + + if ($needles) + { + foreach ($needles as $view => $ids) + { + if (isset(self::$lookup[$language][$view])) + { + foreach ($ids as $id) + { + if (isset(self::$lookup[$language][$view][(int) $id])) + { + return self::$lookup[$language][$view][(int) $id]; + } + } + } + } + } + + // Check if the active menuitem matches the requested language + $active = $menus->getActive(); + + if ($active && ($language == '*' || in_array($active->language, array('*', $language)) || !JLanguageMultilang::isEnabled())) + { + return $active->id; + } + + // If not found, return language specific home link + $default = $menus->getDefault($language); + + return !empty($default->id) ? $default->id : null; + } +} diff --git a/dist/agosms-1.0.26/components/com_agosms/metadata.xml b/dist/agosms-1.0.26/components/com_agosms/metadata.xml new file mode 100644 index 00000000..9205b305 --- /dev/null +++ b/dist/agosms-1.0.26/components/com_agosms/metadata.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/dist/agosms-1.0.26/components/com_agosms/models/agosm.php b/dist/agosms-1.0.26/components/com_agosms/models/agosm.php new file mode 100644 index 00000000..bfe82f96 --- /dev/null +++ b/dist/agosms-1.0.26/components/com_agosms/models/agosm.php @@ -0,0 +1,208 @@ +input->getInt('id'); + $this->setState('agosm.id', $pk); + + // Load the parameters. + $params = $app->getParams(); + $this->setState('params', $params); + + $user = JFactory::getUser(); + + if ((!$user->authorise('core.edit.state', 'com_agosms')) && (!$user->authorise('core.edit', 'com_agosms'))) + { + $this->setState('filter.published', 1); + $this->setState('filter.archived', 2); + } + + $this->setState('filter.language', JLanguageMultilang::isEnabled()); + } + + /** + * Method to get an object. + * + * @param integer $id The id of the object to get. + * + * @return mixed Object on success, false on failure. + */ + public function getItem($pk = null) + { + $user = JFactory::getUser(); + + $pk = (!empty($pk)) ? $pk : (int) $this->getState('agosm.id'); + + if ($this->_item === null) + { + $this->_item = array(); + } + + if (!isset($this->_item[$pk])) + { + try + { + $db = $this->getDbo(); + $query = $db->getQuery(true) + ->select($this->getState('item.select', 'a.*')) + ->from('#__agosms AS a') + ->where('a.id = ' . (int) $pk); + + // Join on category table. + $query->select('c.title AS category_title, c.alias AS category_alias, c.access AS category_access') + ->innerJoin('#__categories AS c on c.id = a.catid') + ->where('c.published > 0'); + + // Join on user table. + $query->select('u.name AS author') + ->join('LEFT', '#__users AS u on u.id = a.created_by'); + + // Filter by language + if ($this->getState('filter.language')) + { + $query->where('a.language in (' . $db->quote(JFactory::getLanguage()->getTag()) . ',' . $db->quote('*') . ')'); + } + + // Join over the categories to get parent category titles + $query->select('parent.title as parent_title, parent.id as parent_id, parent.path as parent_route, parent.alias as parent_alias') + ->join('LEFT', '#__categories as parent ON parent.id = c.parent_id'); + + if ((!$user->authorise('core.edit.state', 'com_agosms')) && (!$user->authorise('core.edit', 'com_agosms'))) + { + // Filter by start and end dates. + $nullDate = $db->quote($db->getNullDate()); + $date = JFactory::getDate(); + + $nowDate = $db->quote($date->toSql()); + + $query->where('(a.publish_up = ' . $nullDate . ' OR a.publish_up <= ' . $nowDate . ')') + ->where('(a.publish_down = ' . $nullDate . ' OR a.publish_down >= ' . $nowDate . ')'); + } + + // Filter by published state. + $published = $this->getState('filter.published'); + $archived = $this->getState('filter.archived'); + + if (is_numeric($published)) + { + $query->where('(a.state = ' . (int) $published . ' OR a.state =' . (int) $archived . ')'); + } + + $db->setQuery($query); + + $data = $db->loadObject(); + + if (empty($data)) + { + JError::raiseError(404, JText::_('COM_AGOSMS_ERROR_AGOSM_NOT_FOUND')); + } + + // Check for published state if filter set. + if ((is_numeric($published) || is_numeric($archived)) && (($data->state != $published) && ($data->state != $archived))) + { + JError::raiseError(404, JText::_('COM_AGOSMS_ERROR_AGOSM_NOT_FOUND')); + } + + // Convert parameter fields to objects. + $data->params = new Registry($data->params); + $data->metadata = new Registry($data->metadata); + + // Compute access permissions. + if ($access = $this->getState('filter.access')) + { + // If the access filter has been set, we already know this user can view. + $data->params->set('access-view', true); + } + else + { + // If no access filter is set, the layout takes some responsibility for display of limited information. + $groups = $user->getAuthorisedViewLevels(); + $data->params->set('access-view', in_array($data->access, $groups) && in_array($data->category_access, $groups)); + } + + $this->_item[$pk] = $data; + } + catch (Exception $e) + { + $this->setError($e); + $this->_item[$pk] = false; + } + } + + return $this->_item[$pk]; + } + + /** + * Returns a reference to the a Table object, always creating it. + * + * @param string $type The table type to instantiate + * @param string $prefix A prefix for the table class name. Optional. + * @param array $config Configuration array for model. Optional. + * + * @return JTable A database object + * + * @since 1.6 + */ + public function getTable($type = 'Agosm', $prefix = 'AgosmsTable', $config = array()) + { + return JTable::getInstance($type, $prefix, $config); + } + + /** + * Method to increment the hit counter for the agosm + * + * @param integer $id Optional ID of the agosm. + * + * @return boolean True on success + */ + public function hit($pk = null) + { + if (empty($pk)) + { + $pk = $this->getState('agosm.id'); + } + + return $this->getTable('Agosm', 'AgosmsTable')->hit($pk); + } +} diff --git a/dist/agosms-1.0.26/components/com_agosms/models/categories.php b/dist/agosms-1.0.26/components/com_agosms/models/categories.php new file mode 100644 index 00000000..59fa086b --- /dev/null +++ b/dist/agosms-1.0.26/components/com_agosms/models/categories.php @@ -0,0 +1,140 @@ +setState('filter.extension', $this->_extension); + + // Get the parent id if defined. + $parentId = $app->input->getInt('id'); + $this->setState('filter.parentId', $parentId); + + $params = $app->getParams(); + $this->setState('params', $params); + + $this->setState('filter.published', 1); + $this->setState('filter.access', true); + } + + /** + * Method to get a store id based on model configuration state. + * + * This is necessary because the model is used by the component and + * different modules that might need different sets of data or different + * ordering requirements. + * + * @param string $id A prefix for the store id. + * + * @return string A store id. + */ + protected function getStoreId($id = '') + { + // Compile the store id. + $id .= ':' . $this->getState('filter.extension'); + $id .= ':' . $this->getState('filter.published'); + $id .= ':' . $this->getState('filter.access'); + $id .= ':' . $this->getState('filter.parentId'); + + return parent::getStoreId($id); + } + + /** + * Redefine the function and add some properties to make the styling more easy + * + * @return mixed An array of data items on success, false on failure. + */ + public function getItems() + { + if (!count($this->_items)) + { + $app = JFactory::getApplication(); + $menu = $app->getMenu(); + $active = $menu->getActive(); + $params = new JRegistry; + + if ($active) + { + $params->loadString($active->params); + } + + $options = array(); + $options['countItems'] = $params->get('show_cat_num_links', 1) || !$params->get('show_empty_categories_cat', 0); + $categories = JCategories::getInstance('Agosms', $options); + $this->_parent = $categories->get($this->getState('filter.parentId', 'root')); + + if (is_object($this->_parent)) + { + $this->_items = $this->_parent->getChildren(); + } + else + { + $this->_items = false; + } + } + + return $this->_items; + } + + /** + * Get the parent + * + * @return mixed An array of data items on success, false on failure. + */ + public function getParent() + { + if (!is_object($this->_parent)) + { + $this->getItems(); + } + + return $this->_parent; + } +} diff --git a/dist/agosms-1.0.26/components/com_agosms/models/category.php b/dist/agosms-1.0.26/components/com_agosms/models/category.php new file mode 100644 index 00000000..3282ad3e --- /dev/null +++ b/dist/agosms-1.0.26/components/com_agosms/models/category.php @@ -0,0 +1,408 @@ +_params)) + { + $params = new Registry; + $params->loadString($item->params); + $item->params = $params; + } + + // Get the tags + $item->tags = new JHelperTags; + $item->tags->getItemTags('com_agosms.agosm', $item->id); + } + + return $items; + } + + /** + * Method to get a JDatabaseQuery object for retrieving the data set from a database. + * + * @return JDatabaseQuery A JDatabaseQuery object to retrieve the data set. + * + * @since 1.6 + */ + protected function getListQuery() + { + $groups = implode(',', JFactory::getUser()->getAuthorisedViewLevels()); + + // Create a new query object. + $db = $this->getDbo(); + $query = $db->getQuery(true); + + // Select required fields from the categories. + $query->select($this->getState('list.select', 'a.*')) + ->from($db->quoteName('#__agosms') . ' AS a') + ->where('a.access IN (' . $groups . ')'); + + // Filter by category. + if ($categoryId = $this->getState('category.id')) + { + // Group by subcategory + if ($this->getState('category.group', 0)) + { + $query->select('c.title AS category_title') + ->where('c.parent_id = ' . (int) $categoryId) + ->join('LEFT', '#__categories AS c ON c.id = a.catid') + ->where('c.access IN (' . $groups . ')'); + } + else + { + $query->where('a.catid = ' . (int) $categoryId) + ->join('LEFT', '#__categories AS c ON c.id = a.catid') + ->where('c.access IN (' . $groups . ')'); + } + + // Filter by published category + $cpublished = $this->getState('filter.c.published'); + + if (is_numeric($cpublished)) + { + $query->where('c.published = ' . (int) $cpublished); + } + } + + // Join over the users for the author and modified_by names. + $query->select("CASE WHEN a.created_by_alias > ' ' THEN a.created_by_alias ELSE ua.name END AS author") + ->select("ua.email AS author_email") + ->join('LEFT', '#__users AS ua ON ua.id = a.created_by') + ->join('LEFT', '#__users AS uam ON uam.id = a.modified_by'); + + // Filter by state + $state = $this->getState('filter.state'); + + if (is_numeric($state)) + { + $query->where('a.state = ' . (int) $state); + } + + // Do not show trashed links on the front-end + $query->where('a.state != -2'); + + // Filter by start and end dates. + $nullDate = $db->quote($db->getNullDate()); + $nowDate = $db->quote(JFactory::getDate()->toSql()); + + if ($this->getState('filter.publish_date')) + { + $query->where('(a.publish_up = ' . $nullDate . ' OR a.publish_up <= ' . $nowDate . ')') + ->where('(a.publish_down = ' . $nullDate . ' OR a.publish_down >= ' . $nowDate . ')'); + } + + // Filter by language + if ($this->getState('filter.language')) + { + $query->where('a.language in (' . $db->quote(JFactory::getLanguage()->getTag()) . ',' . $db->quote('*') . ')'); + } + + // Filter by search in title + $search = $this->getState('list.filter'); + + if (!empty($search)) + { + $search = $db->quote('%' . $db->escape($search, true) . '%'); + $query->where('(a.title LIKE ' . $search . ')'); + } + + // If grouping by subcategory, add the subcategory list ordering clause. + if ($this->getState('category.group', 0)) + { + $query->order( + $db->escape($this->getState('category.ordering', 'c.lft')) . ' ' . + $db->escape($this->getState('category.direction', 'ASC')) + ); + } + + // Add the list ordering clause. + $query->order( + $db->escape($this->getState('list.ordering', 'a.ordering')) . ' ' . + $db->escape($this->getState('list.direction', 'ASC')) + ); + + return $query; + } + + /** + * Method to auto-populate the model state. + * + * Note. Calling getState in this method will result in recursion. + * + * @param string $ordering An optional ordering field. + * @param string $direction An optional direction (asc|desc). + * + * @return void + * + * @since 1.6 + */ + protected function populateState($ordering = null, $direction = null) + { + $app = JFactory::getApplication(); + $params = JComponentHelper::getParams('com_agosms'); + + // List state information + $limit = $app->getUserStateFromRequest('global.list.limit', 'limit', $app->get('list_limit'), 'uint'); + $this->setState('list.limit', $limit); + + $limitstart = $app->input->get('limitstart', 0, 'uint'); + $this->setState('list.start', $limitstart); + + // Optional filter text + $this->setState('list.filter', $app->input->getString('filter-search')); + + $orderCol = $app->input->get('filter_order', 'ordering'); + + if (!in_array($orderCol, $this->filter_fields)) + { + $orderCol = 'ordering'; + } + + $this->setState('list.ordering', $orderCol); + + $listOrder = $app->input->get('filter_order_Dir', 'ASC'); + + if (!in_array(strtoupper($listOrder), array('ASC', 'DESC', ''))) + { + $listOrder = 'ASC'; + } + + $this->setState('list.direction', $listOrder); + + $id = $app->input->get('id', 0, 'int'); + $this->setState('category.id', $id); + + $user = JFactory::getUser(); + + if ((!$user->authorise('core.edit.state', 'com_agosms')) && (!$user->authorise('core.edit', 'com_agosms'))) + { + // Limit to published for people who can't edit or edit.state. + $this->setState('filter.state', 1); + + // Filter by start and end dates. + $this->setState('filter.publish_date', true); + } + + $this->setState('filter.language', JLanguageMultilang::isEnabled()); + + // Load the parameters. + $this->setState('params', $params); + } + + /** + * Method to get category data for the current category + * + * @return object + * + * @since 1.5 + */ + public function getCategory() + { + if (!is_object($this->_item)) + { + $app = JFactory::getApplication(); + $menu = $app->getMenu(); + $active = $menu->getActive(); + $params = new Registry; + + if ($active) + { + $params->loadString($active->params); + } + + $options = array(); + $options['countItems'] = $params->get('show_cat_num_links_cat', 1) + || $params->get('show_empty_categories', 0); + + $categories = JCategories::getInstance('Agosms', $options); + $this->_item = $categories->get($this->getState('category.id', 'root')); + + if (is_object($this->_item)) + { + $this->_children = $this->_item->getChildren(); + $this->_parent = false; + + if ($this->_item->getParent()) + { + $this->_parent = $this->_item->getParent(); + } + + $this->_rightsibling = $this->_item->getSibling(); + $this->_leftsibling = $this->_item->getSibling(false); + } + else + { + $this->_children = false; + $this->_parent = false; + } + } + + return $this->_item; + } + + /** + * Get the parent category + * + * @return mixed An array of categories or false if an error occurs. + */ + public function getParent() + { + if (!is_object($this->_item)) + { + $this->getCategory(); + } + + return $this->_parent; + } + + /** + * Get the leftsibling (adjacent) categories. + * + * @return mixed An array of categories or false if an error occurs. + */ + public function &getLeftSibling() + { + if (!is_object($this->_item)) + { + $this->getCategory(); + } + + return $this->_leftsibling; + } + + /** + * Get the rightsibling (adjacent) categories. + * + * @return mixed An array of categories or false if an error occurs. + */ + public function &getRightSibling() + { + if (!is_object($this->_item)) + { + $this->getCategory(); + } + + return $this->_rightsibling; + } + + /** + * Get the child categories. + * + * @return mixed An array of categories or false if an error occurs. + */ + public function &getChildren() + { + if (!is_object($this->_item)) + { + $this->getCategory(); + } + + return $this->_children; + } + + /** + * Increment the hit counter for the category. + * + * @param integer $pk Optional primary key of the category to increment. + * + * @return boolean True if successful; false otherwise and internal error set. + * + * @since 3.2 + */ + public function hit($pk = 0) + { + $hitcount = JFactory::getApplication()->input->getInt('hitcount', 1); + + if ($hitcount) + { + $pk = (!empty($pk)) ? $pk : (int) $this->getState('category.id'); + $table = JTable::getInstance('Category', 'JTable'); + $table->load($pk); + $table->hit($pk); + } + + return true; + } +} diff --git a/dist/agosms-1.0.26/components/com_agosms/models/form.php b/dist/agosms-1.0.26/components/com_agosms/models/form.php new file mode 100644 index 00000000..073511e0 --- /dev/null +++ b/dist/agosms-1.0.26/components/com_agosms/models/form.php @@ -0,0 +1,104 @@ +getState('return_page')); + } + + /** + * Method to auto-populate the model state. + * + * Note. Calling getState in this method will result in recursion. + * + * @return void + * + * @since 1.6 + */ + protected function populateState() + { + $app = JFactory::getApplication(); + + // Load state from the request. + $pk = $app->input->getInt('w_id'); + $this->setState('agosm.id', $pk); + + // Add compatibility variable for default naming conventions. + $this->setState('form.id', $pk); + + $categoryId = $app->input->getInt('catid'); + $this->setState('agosm.catid', $categoryId); + + $return = $app->input->get('return', null, 'base64'); + + if (!JUri::isInternal(base64_decode($return))) + { + $return = null; + } + + $this->setState('return_page', base64_decode($return)); + + // Load the parameters. + $params = $app->getParams(); + $this->setState('params', $params); + + $this->setState('layout', $app->input->getString('layout')); + } + + /** + * Abstract method for getting the form from the model. + * + * @param array $data Data for the form. + * @param boolean $loadData True if the form is to load its own data (default case), false if not. + * + * @return mixed A JForm object on success, false on failure + * + * @since __DEPLOY_VERSION__ + */ + public function getForm($data = array(), $loadData = true) + { + $form = $this->loadForm('com_agosms.form', 'agosm', array('control' => 'jform', 'load_data' => $loadData)); + + // Disable the buttons and just allow editor none for not authenticated users + if (JFactory::getUser()->guest) + { + $form->setFieldAttribute('description', 'editor', 'none'); + $form->setFieldAttribute('description', 'buttons', 'no'); + } + + return $form; + } +} diff --git a/dist/agosms-1.0.26/components/com_agosms/models/forms/agosm.xml b/dist/agosms-1.0.26/components/com_agosms/models/forms/agosm.xml new file mode 100644 index 00000000..d5c5e405 --- /dev/null +++ b/dist/agosms-1.0.26/components/com_agosms/models/forms/agosm.xml @@ -0,0 +1,173 @@ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + + + + + + + + + + + + +
+
+
diff --git a/dist/agosms-1.0.26/components/com_agosms/router.php b/dist/agosms-1.0.26/components/com_agosms/router.php new file mode 100644 index 00000000..6803f33a --- /dev/null +++ b/dist/agosms-1.0.26/components/com_agosms/router.php @@ -0,0 +1,296 @@ +getMenu(); + $params = JComponentHelper::getParams('com_agosms'); + $advanced = $params->get('sef_advanced_link', 0); + + // We need a menu item. Either the one specified in the query, or the current active one if none specified + if (empty($query['Itemid'])) + { + $menuItem = $menu->getActive(); + } + else + { + $menuItem = $menu->getItem($query['Itemid']); + } + + $mView = (empty($menuItem->query['view'])) ? null : $menuItem->query['view']; + $mId = (empty($menuItem->query['id'])) ? null : $menuItem->query['id']; + + if (isset($query['view'])) + { + $view = $query['view']; + + if (empty($query['Itemid']) || empty($menuItem) || $menuItem->component != 'com_agosms') + { + $segments[] = $query['view']; + } + + // We need to keep the view for forms since they never have their own menu item + if ($view != 'form') + { + unset($query['view']); + } + } + + // Are we dealing with an agosm that is attached to a menu item? + if (isset($query['view']) && ($mView == $query['view']) and (isset($query['id'])) and ($mId == (int) $query['id'])) + { + unset($query['view']); + unset($query['catid']); + unset($query['id']); + + return $segments; + } + + if (isset($view) and ($view == 'category' or $view == 'agosm')) + { + if ($mId != (int) $query['id'] || $mView != $view) + { + if ($view == 'agosm' && isset($query['catid'])) + { + $catid = $query['catid']; + } + elseif (isset($query['id'])) + { + $catid = $query['id']; + } + + $menuCatid = $mId; + $categories = JCategories::getInstance('Agosms'); + $category = $categories->get($catid); + + if ($category) + { + // TODO Throw error that the category either not exists or is unpublished + $path = $category->getPath(); + $path = array_reverse($path); + + $array = array(); + + foreach ($path as $id) + { + if ((int) $id == (int) $menuCatid) + { + break; + } + + if ($advanced) + { + list($tmp, $id) = explode(':', $id, 2); + } + + $array[] = $id; + } + + $segments = array_merge($segments, array_reverse($array)); + } + + if ($view == 'agosm') + { + if ($advanced) + { + list($tmp, $id) = explode(':', $query['id'], 2); + } + else + { + $id = $query['id']; + } + + $segments[] = $id; + } + } + + unset($query['id']); + unset($query['catid']); + } + + if (isset($query['layout'])) + { + if (!empty($query['Itemid']) && isset($menuItem->query['layout'])) + { + if ($query['layout'] == $menuItem->query['layout']) + { + unset($query['layout']); + } + } + else + { + if ($query['layout'] == 'default') + { + unset($query['layout']); + } + } + } + + $total = count($segments); + + for ($i = 0; $i < $total; $i++) + { + $segments[$i] = str_replace(':', '-', $segments[$i]); + } + + return $segments; + } + + /** + * Parse the segments of a URL. + * + * @param array &$segments The segments of the URL to parse. + * + * @return array The URL attributes to be used by the application. + * + * @since 3.3 + */ + public function parse(&$segments) + { + $total = count($segments); + $vars = array(); + + for ($i = 0; $i < $total; $i++) + { + $segments[$i] = preg_replace('/-/', ':', $segments[$i], 1); + } + + // Get the active menu item. + $app = JFactory::getApplication(); + $menu = $app->getMenu(); + $item = $menu->getActive(); + $params = JComponentHelper::getParams('com_agosms'); + $advanced = $params->get('sef_advanced_link', 0); + + // Count route segments + $count = count($segments); + + // Standard routing for agosms. + if (!isset($item)) + { + $vars['view'] = $segments[0]; + $vars['id'] = $segments[$count - 1]; + + return $vars; + } + + // From the categories view, we can only jump to a category. + $id = (isset($item->query['id']) && $item->query['id'] > 1) ? $item->query['id'] : 'root'; + + $category = JCategories::getInstance('Agosms')->get($id); + + $categories = $category->getChildren(); + $found = 0; + + foreach ($segments as $segment) + { + foreach ($categories as $category) + { + if (($category->slug == $segment) || ($advanced && $category->alias == str_replace(':', '-', $segment))) + { + $vars['id'] = $category->id; + $vars['view'] = 'category'; + $categories = $category->getChildren(); + $found = 1; + + break; + } + } + + if ($found == 0) + { + if ($advanced) + { + $db = JFactory::getDbo(); + $query = $db->getQuery(true) + ->select($db->quoteName('id')) + ->from('#__agosms') + ->where($db->quoteName('catid') . ' = ' . (int) $vars['catid']) + ->where($db->quoteName('alias') . ' = ' . $db->quote(str_replace(':', '-', $segment))); + $db->setQuery($query); + $id = $db->loadResult(); + } + else + { + $id = $segment; + } + + $vars['id'] = $id; + $vars['view'] = 'agosm'; + + break; + } + + $found = 0; + } + + return $vars; + } +} + +/** + * Agosms router functions + * + * @param array &$query An array of URL arguments + * + * @return array The URL arguments to use to assemble the subsequent URL. + * + * Note. These functions are proxies for the new router interface + * for old SEF extensions. + * + * @deprecated 4.0 Use Class based routers instead + */ +function AgosmsBuildRoute(&$query) +{ + $router = new AgosmsRouter; + + return $router->build($query); +} + +/** + * Agosms router functions + * + * @param array $segments The segments of the URL to parse. + * + * @return array The URL attributes to be used by the application. + * + * Note. These functions are proxies for the new router interface + * for old SEF extensions. + * + * @deprecated 4.0 Use Class based routers instead + */ +function AgosmsParseRoute($segments) +{ + $router = new AgosmsRouter; + + return $router->parse($segments); +} diff --git a/dist/agosms-1.0.26/components/com_agosms/views/agosm/tmpl/default.php b/dist/agosms-1.0.26/components/com_agosms/views/agosm/tmpl/default.php new file mode 100644 index 00000000..5cb9b388 --- /dev/null +++ b/dist/agosms-1.0.26/components/com_agosms/views/agosm/tmpl/default.php @@ -0,0 +1,35 @@ +item->url); + +?> +
+ + + + item->event->afterDisplayTitle; ?> + + item->event->beforeDisplayContent; ?> +
+ + item->description; ?> +
+ + item->event->afterDisplayContent; ?> +
diff --git a/dist/agosms-1.0.26/components/com_agosms/views/agosm/tmpl/default.xml b/dist/agosms-1.0.26/components/com_agosms/views/agosm/tmpl/default.xml new file mode 100644 index 00000000..c167fece --- /dev/null +++ b/dist/agosms-1.0.26/components/com_agosms/views/agosm/tmpl/default.xml @@ -0,0 +1,30 @@ + + + + + + + + + + +
+ + +
+
+
diff --git a/dist/agosms-1.0.26/components/com_agosms/views/agosm/view.html.php b/dist/agosms-1.0.26/components/com_agosms/views/agosm/view.html.php new file mode 100644 index 00000000..36d8ba37 --- /dev/null +++ b/dist/agosms-1.0.26/components/com_agosms/views/agosm/view.html.php @@ -0,0 +1,63 @@ +item = $this->get('Item'); + $this->state = $this->get('State'); + $this->params = $this->state->get('params'); + + // Create a shortcut for $item. + $item = $this->item; + + $offset = $this->state->get('list.offset'); + + $dispatcher->trigger('onContentPrepare', array ('com_agosms.agosm', &$item, &$item->params, $offset)); + + $item->event = new stdClass; + + $results = $dispatcher->trigger('onContentAfterTitle', array('com_agosms.agosm', &$item, &$item->params, $offset)); + $item->event->afterDisplayTitle = trim(implode("\n", $results)); + + $results = $dispatcher->trigger('onContentBeforeDisplay', array('com_agosms.agosm', &$item, &$item->params, $offset)); + $item->event->beforeDisplayContent = trim(implode("\n", $results)); + + $results = $dispatcher->trigger('onContentAfterDisplay', array('com_agosms.agosm', &$item, &$item->params, $offset)); + $item->event->afterDisplayContent = trim(implode("\n", $results)); + + parent::display($tpl); + } +} diff --git a/dist/agosms-1.0.26/components/com_agosms/views/categories/tmpl/default.php b/dist/agosms-1.0.26/components/com_agosms/views/categories/tmpl/default.php new file mode 100644 index 00000000..9c57d988 --- /dev/null +++ b/dist/agosms-1.0.26/components/com_agosms/views/categories/tmpl/default.php @@ -0,0 +1,32 @@ +addScriptDeclaration(" +jQuery(function($) { + $('.categories-list').find('[id^=category-btn-]').each(function(index, btn) { + var btn = $(btn); + btn.on('click', function() { + btn.find('span').toggleClass('icon-plus'); + btn.find('span').toggleClass('icon-minus'); + }); + }); +});"); +?> +
+ loadTemplate('items'); + ?> +
diff --git a/dist/agosms-1.0.26/components/com_agosms/views/categories/tmpl/default.xml b/dist/agosms-1.0.26/components/com_agosms/views/categories/tmpl/default.xml new file mode 100644 index 00000000..5f00847a --- /dev/null +++ b/dist/agosms-1.0.26/components/com_agosms/views/categories/tmpl/default.xml @@ -0,0 +1,282 @@ + + + + + + + + + + + +
+ +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
diff --git a/dist/agosms-1.0.26/components/com_agosms/views/categories/tmpl/default_items.php b/dist/agosms-1.0.26/components/com_agosms/views/categories/tmpl/default_items.php new file mode 100644 index 00000000..9321662f --- /dev/null +++ b/dist/agosms-1.0.26/components/com_agosms/views/categories/tmpl/default_items.php @@ -0,0 +1,65 @@ +items[$this->parent->id]) > 0 && $this->maxLevelcat != 0) : +?> + items[$this->parent->id] as $id => $item) : ?> + params->get('show_empty_categories_cat') || $item->numitems || count($item->getChildren())) : + if (!isset($this->items[$this->parent->id][$id + 1])) + { + $class = ' class="last"'; + } + ?> +
> + + + params->get('show_subcat_desc_cat') == 1) :?> + description) : ?> +
+ description, '', 'com_agosms.categories'); ?> +
+ + + + getChildren()) > 0 && $this->maxLevelcat > 1) :?> +
+ items[$item->id] = $item->getChildren(); + $this->parent = $item; + $this->maxLevelcat--; + echo $this->loadTemplate('items'); + $this->parent = $item->getParent(); + $this->maxLevelcat++; + ?> +
+ +
+ + + diff --git a/dist/agosms-1.0.26/components/com_agosms/views/categories/view.html.php b/dist/agosms-1.0.26/components/com_agosms/views/categories/view.html.php new file mode 100644 index 00000000..f9e8c73f --- /dev/null +++ b/dist/agosms-1.0.26/components/com_agosms/views/categories/view.html.php @@ -0,0 +1,84 @@ +get('State'); + $items = $this->get('Items'); + $parent = $this->get('Parent'); + + // Check for errors. + if (count($errors = $this->get('Errors'))) + { + JError::raiseWarning(500, implode("\n", $errors)); + return false; + } + + if ($items === false) + { + return JError::raiseError(404, JText::_('JGLOBAL_CATEGORY_NOT_FOUND')); + } + + if ($parent == false) + { + return JError::raiseError(404, JText::_('JGLOBAL_CATEGORY_NOT_FOUND')); + } + + $params = &$state->params; + + $items = array($parent->id => $items); + + // Escape strings for HTML output + $this->pageclass_sfx = htmlspecialchars($params->get('pageclass_sfx')); + + $this->maxLevelcat = $params->get('maxLevelcat', -1); + $this->params = &$params; + $this->parent = &$parent; + $this->items = &$items; + + return parent::display($tpl); + } +} diff --git a/dist/agosms-1.0.26/components/com_agosms/views/category/metadata.xml b/dist/agosms-1.0.26/components/com_agosms/views/category/metadata.xml new file mode 100644 index 00000000..33a5cfd9 --- /dev/null +++ b/dist/agosms-1.0.26/components/com_agosms/views/category/metadata.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/dist/agosms-1.0.26/components/com_agosms/views/category/tmpl/default.php b/dist/agosms-1.0.26/components/com_agosms/views/category/tmpl/default.php new file mode 100644 index 00000000..ee5b6ff5 --- /dev/null +++ b/dist/agosms-1.0.26/components/com_agosms/views/category/tmpl/default.php @@ -0,0 +1,15 @@ +subtemplatename = 'items'; +echo JLayoutHelper::render('joomla.content.category_default', $this); diff --git a/dist/agosms-1.0.26/components/com_agosms/views/category/tmpl/default.xml b/dist/agosms-1.0.26/components/com_agosms/views/category/tmpl/default.xml new file mode 100644 index 00000000..d2dddc85 --- /dev/null +++ b/dist/agosms-1.0.26/components/com_agosms/views/category/tmpl/default.xml @@ -0,0 +1,223 @@ + + + + + + + + + + + +
+ +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+
+
diff --git a/dist/agosms-1.0.26/components/com_agosms/views/category/tmpl/default_children.php b/dist/agosms-1.0.26/components/com_agosms/views/category/tmpl/default_children.php new file mode 100644 index 00000000..0f381836 --- /dev/null +++ b/dist/agosms-1.0.26/components/com_agosms/views/category/tmpl/default_children.php @@ -0,0 +1,58 @@ +children[$this->category->id]) > 0 && $this->maxLevel != 0) : +?> +
    +children[$this->category->id] as $id => $child) : ?> + params->get('show_empty_categories') || $child->numitems || count($child->getChildren())) : + if (!isset($this->children[$this->category->id][$id + 1])) + { + $class = ' class="last"'; + } + ?> + > + + + escape($child->title); ?> + + + params->get('show_subcat_desc') == 1) :?> + description) : ?> +
    + description, '', 'com_agosms.category'); ?> +
    + + + + params->get('show_cat_num_links') == 1) :?> +
    +
    +
    numitems; ?>
    +
    + + + getChildren()) > 0 ) : + $this->children[$child->id] = $child->getChildren(); + $this->category = $child; + $this->maxLevel--; + echo $this->loadTemplate('children'); + $this->category = $child->getParent(); + $this->maxLevel++; + endif; ?> + + + +
+item->params; + +// Get the user object. +$user = JFactory::getUser(); + +// Check if user is allowed to add/edit based on agosms permissinos. +$canEdit = $user->authorise('core.edit', 'com_agosms.category.' . $this->category->id); +$canCreate = $user->authorise('core.create', 'com_agosms'); +$canEditState = $user->authorise('core.edit.state', 'com_agosms'); + +$n = count($this->items); +$listOrder = $this->escape($this->state->get('list.ordering')); +$listDirn = $this->escape($this->state->get('list.direction')); +?> + +items)) : ?> +

+ + +
+ params->get('filter_field') != 'hide' || $this->params->get('show_pagination_limit')) : ?> +
+ params->get('filter_field') != 'hide') : ?> +
+ + +
+ + + params->get('show_pagination_limit')) : ?> +
+ + pagination->getLimitBox(); ?> +
+ +
+ +
    + items as $i => $item) : ?> + access, $this->user->getAuthorisedViewLevels())) : ?> + items[$i]->state == 0) : ?> +
  • + +
  • + + params->get('show_link_hits', 1)) : ?> + + hits); ?> + + + + + + + + + +
    + params->get('icons', 1) == 0) : ?> + + params->get('icons', 1) == 1) : ?> + params->get('link_icons')) : ?> + + + params->get('link_icons') . '" alt="' . JText::_('COM_AGOSMS_LINK') . '" />'; ?> + + + + pageclass_sfx; ?> + link; ?> + params->get('width'); ?> + params->get('height'); ?> + + + + + items[$i]->state == 0) : ?> + + + + params->get('target', $this->params->get('target'))) + { + case 1: + // Open in a new window + echo '' . + $this->escape($item->title) . ''; + break; + + case 2: + // Open in a popup window + $attribs = 'toolbar=no,location=no,status=no,menubar=no,scrollbars=yes,resizable=yes,width=' . $this->escape($width) . ',height=' . $this->escape($height) . ''; + echo "" . + $this->escape($item->title) . ''; + break; + case 3: + // Open in a modal window + JHtml::_('behavior.modal', 'a.modal'); + echo '' . + $this->escape($item->title) . ' '; + break; + + default: + // Open in parent window + echo '' . + $this->escape($item->title) . ' '; + break; + } + ?> +
    + tags->getItemTags('com_agosms.agosm', $item->id); ?> + params->get('show_tags', 1)) : ?> + item->tagLayout = new JLayoutFile('joomla.content.tags'); ?> + item->tagLayout->render($tagsData); ?> + + params->get('show_link_description')) and ($item->description != '')) : ?> + images); ?> + image_first) and !empty($images->image_first)) : ?> + float_first)) ? $this->params->get('float_first') : $images->float_first; ?> +
    image_first_caption) : ?> + image_first_caption) . '"'; ?> + + src="image_first); ?>" alt="image_first_alt); ?>"/>
    + + image_second) and !empty($images->image_second)) : ?> + float_second)) ? $this->params->get('float_second') : $images->float_second; ?> +
    image_second_caption) : ?> + image_second_caption) . '"'; ?> + + src="image_second); ?>" alt="image_second_alt); ?>"/>
    + + description; ?> + +
  • + + +
+ + + params->get('show_pagination')) : ?> + + +
+ diff --git a/dist/agosms-1.0.26/components/com_agosms/views/category/view.feed.php b/dist/agosms-1.0.26/components/com_agosms/views/category/view.feed.php new file mode 100644 index 00000000..986de291 --- /dev/null +++ b/dist/agosms-1.0.26/components/com_agosms/views/category/view.feed.php @@ -0,0 +1,25 @@ +items as $item) + { + $item->slug = $item->alias ? ($item->id . ':' . $item->alias) : $item->id; + + if ($item->params->get('count_clicks', $this->params->get('count_clicks', 1)) == 1) + { + $item->link = JRoute::_('index.php?option=com_agosms&task=agosm.go&id=' . $item->id); + } + else + { + $item->link = $item->url; + } + + $temp = new JRegistry; + $temp->loadString($item->params); + $item->params = clone($this->params); + $item->params->merge($temp); + } + + return parent::display($tpl); + } + + /** + * Prepares the document + * + * @return void + */ + protected function prepareDocument() + { + parent::prepareDocument(); + + $app = JFactory::getApplication(); + $menus = $app->getMenu(); + $pathway = $app->getPathway(); + $title = null; + + // Because the application sets a default page title, + // we need to get it from the menu item itself + $menu = $menus->getActive(); + + if ($menu) + { + $this->params->def('page_heading', $this->params->get('page_title', $menu->title)); + } + else + { + $this->params->def('page_heading', JText::_('COM_AGOSMS_DEFAULT_PAGE_TITLE')); + } + + $id = (int) @$menu->query['id']; + + if ($menu && ($menu->query['option'] != 'com_agosms' || $id != $this->category->id)) + { + $this->params->set('page_subheading', $this->category->title); + $path = array(array('title' => $this->category->title, 'link' => '')); + $category = $this->category->getParent(); + + while (($menu->query['option'] != 'com_agosms' || $id != $category->id) && $category->id > 1) + { + $path[] = array('title' => $category->title, 'link' => AgosmsHelperRoute::getCategoryRoute($category->id)); + $category = $category->getParent(); + } + + $path = array_reverse($path); + + foreach ($path as $item) + { + $pathway->addItem($item['title'], $item['link']); + } + } + + parent::addFeed(); + } +} diff --git a/dist/agosms-1.0.26/components/com_agosms/views/form/metadata.xml b/dist/agosms-1.0.26/components/com_agosms/views/form/metadata.xml new file mode 100644 index 00000000..a8bbd4eb --- /dev/null +++ b/dist/agosms-1.0.26/components/com_agosms/views/form/metadata.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/dist/agosms-1.0.26/components/com_agosms/views/form/tmpl/edit.php b/dist/agosms-1.0.26/components/com_agosms/views/form/tmpl/edit.php new file mode 100644 index 00000000..d1b7e372 --- /dev/null +++ b/dist/agosms-1.0.26/components/com_agosms/views/form/tmpl/edit.php @@ -0,0 +1,98 @@ +params->get('captcha', JFactory::getApplication()->get('captcha', '0')); + +foreach (JPluginHelper::getPlugin('captcha') as $plugin) +{ + if ($captchaSet === $plugin->name) + { + $captchaEnabled = true; + break; + } +} + +// Create shortcut to parameters. +$params = $this->state->get('params'); +?> + + +
+ params->get('show_page_heading')) : ?> +

+ escape($this->params->get('page_heading')); ?> +

+ +
+ + form->renderField('title'); ?> + form->renderField('alias'); ?> + form->renderField('catid'); ?> + form->renderField('url'); ?> + form->renderField('tags'); ?> + + get('save_history', 0)) : ?> + form->renderField('version_note'); ?> + + + user->authorise('core.edit.state', 'com_agosms.agosm')) : ?> + form->renderField('state'); ?> + + form->renderField('language'); ?> + form->renderField('description'); ?> + +
+ + +
+ form->renderField('captcha'); ?> +
+ + +
+
+ +
+
+ +
+ get('save_history', 0)) : ?> +
+ form->getInput('contenthistory'); ?> +
+ +
+ + + + +
+
diff --git a/dist/agosms-1.0.26/components/com_agosms/views/form/tmpl/edit.xml b/dist/agosms-1.0.26/components/com_agosms/views/form/tmpl/edit.xml new file mode 100644 index 00000000..eaf50971 --- /dev/null +++ b/dist/agosms-1.0.26/components/com_agosms/views/form/tmpl/edit.xml @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/dist/agosms-1.0.26/components/com_agosms/views/form/view.html.php b/dist/agosms-1.0.26/components/com_agosms/views/form/view.html.php new file mode 100644 index 00000000..057994f7 --- /dev/null +++ b/dist/agosms-1.0.26/components/com_agosms/views/form/view.html.php @@ -0,0 +1,156 @@ +state = $this->get('State'); + $this->item = $this->get('Item'); + $this->form = $this->get('Form'); + $this->return_page = $this->get('ReturnPage'); + + if (empty($this->item->id)) + { + $authorised = ($user->authorise('core.create', 'com_agosms') || (count($user->getAuthorisedCategories('com_agosms', 'core.create')))); + } + else + { + $authorised = $user->authorise('core.edit', 'com_agosms.category.' . $this->item->catid); + } + + if ($authorised !== true) + { + JError::raiseError(403, JText::_('JERROR_ALERTNOAUTHOR')); + + return false; + } + + if (!empty($this->item)) + { + // Override the base agosm data with any data in the session. + $temp = (array) JFactory::getApplication()->getUserState('com_agosms.edit.agosm.data', array()); + + foreach ($temp as $k => $v) + { + $this->item->$k = $v; + } + + $this->form->bind($this->item); + } + + // Check for errors. + if (count($errors = $this->get('Errors'))) + { + JError::raiseWarning(500, implode("\n", $errors)); + + return false; + } + + // Create a shortcut to the parameters. + $params = &$this->state->params; + + // Escape strings for HTML output + $this->pageclass_sfx = htmlspecialchars($params->get('pageclass_sfx')); + + $this->params = $params; + $this->user = $user; + + $this->_prepareDocument(); + parent::display($tpl); + } + + /** + * Prepares the document + * + * @return void + */ + protected function _prepareDocument() + { + $app = JFactory::getApplication(); + $menus = $app->getMenu(); + $title = null; + + // Because the application sets a default page title, + // we need to get it from the menu item itself + $menu = $menus->getActive(); + + if (empty($this->item->id)) + { + $head = JText::_('COM_AGOSMS_FORM_SUBMIT_AGOSM'); + } + else + { + $head = JText::_('COM_AGOSMS_FORM_EDIT_AGOSM'); + } + + if ($menu) + { + $this->params->def('page_heading', $this->params->get('page_title', $menu->title)); + } + else + { + $this->params->def('page_heading', $head); + } + + $title = $this->params->def('page_title', $head); + + if ($app->get('sitename_pagetitles', 0) == 1) + { + $title = JText::sprintf('JPAGETITLE', $app->get('sitename'), $title); + } + elseif ($app->get('sitename_pagetitles', 0) == 2) + { + $title = JText::sprintf('JPAGETITLE', $title, $app->get('sitename')); + } + + $this->document->setTitle($title); + + if ($this->params->get('menu-meta_description')) + { + $this->document->setDescription($this->params->get('menu-meta_description')); + } + + if ($this->params->get('menu-meta_keywords')) + { + $this->document->setMetadata('keywords', $this->params->get('menu-meta_keywords')); + } + + if ($this->params->get('robots')) + { + $this->document->setMetadata('robots', $this->params->get('robots')); + } + } +} diff --git a/dist/agosms-1.0.26/language/de-DE/de-DE.com_agosms.ini b/dist/agosms-1.0.26/language/de-DE/de-DE.com_agosms.ini new file mode 100644 index 00000000..ad7db080 --- /dev/null +++ b/dist/agosms-1.0.26/language/de-DE/de-DE.com_agosms.ini @@ -0,0 +1,4 @@ +COM_AGOSM="Agosm" +COM_AGOSM_ACCESS_UPDATE_DESC="Geheimes Word für die Aktualisierung. Sie müssen auch das Installer-Plugin aktivieren. Wenn Sie das geheime Wort verloren haben, können Sie eine E-Mail an info@astrid-guenther.de schreiben." +COM_AGOSM_ACCESS_UPDATE_LABEL="Geheimes Word" +COM_AGOSM_CONFIGURATION="Agosm Konfiguration" \ No newline at end of file diff --git a/dist/agosms-1.0.26/language/de-DE/de-DE.com_agosms.sys.ini b/dist/agosms-1.0.26/language/de-DE/de-DE.com_agosms.sys.ini new file mode 100644 index 00000000..ad7db080 --- /dev/null +++ b/dist/agosms-1.0.26/language/de-DE/de-DE.com_agosms.sys.ini @@ -0,0 +1,4 @@ +COM_AGOSM="Agosm" +COM_AGOSM_ACCESS_UPDATE_DESC="Geheimes Word für die Aktualisierung. Sie müssen auch das Installer-Plugin aktivieren. Wenn Sie das geheime Wort verloren haben, können Sie eine E-Mail an info@astrid-guenther.de schreiben." +COM_AGOSM_ACCESS_UPDATE_LABEL="Geheimes Word" +COM_AGOSM_CONFIGURATION="Agosm Konfiguration" \ No newline at end of file diff --git a/dist/agosms-1.0.26/language/en-GB/en-GB.com_agosms.ini b/dist/agosms-1.0.26/language/en-GB/en-GB.com_agosms.ini new file mode 100644 index 00000000..49e3f776 --- /dev/null +++ b/dist/agosms-1.0.26/language/en-GB/en-GB.com_agosms.ini @@ -0,0 +1,4 @@ +COM_AGOSM="Agosm" +COM_AGOSM_ACCESS_UPDATE_DESC="Secret Word for updating. You also need to activate the Installer Plugin. If you lost the secret word you can write an email to info@astrid-guenther.de." +COM_AGOSM_ACCESS_UPDATE_LABEL="Secret Word for updating" +COM_AGOSM_CONFIGURATION="Agosm Configuration" \ No newline at end of file diff --git a/dist/agosms-1.0.26/language/en-GB/en-GB.com_agosms.sys.ini b/dist/agosms-1.0.26/language/en-GB/en-GB.com_agosms.sys.ini new file mode 100644 index 00000000..49e3f776 --- /dev/null +++ b/dist/agosms-1.0.26/language/en-GB/en-GB.com_agosms.sys.ini @@ -0,0 +1,4 @@ +COM_AGOSM="Agosm" +COM_AGOSM_ACCESS_UPDATE_DESC="Secret Word for updating. You also need to activate the Installer Plugin. If you lost the secret word you can write an email to info@astrid-guenther.de." +COM_AGOSM_ACCESS_UPDATE_LABEL="Secret Word for updating" +COM_AGOSM_CONFIGURATION="Agosm Configuration" \ No newline at end of file diff --git a/dist/agosms-1.0.26/media/com_agosms/js/admin-agosms-button-default.js b/dist/agosms-1.0.26/media/com_agosms/js/admin-agosms-button-default.js new file mode 100644 index 00000000..4f5adcaa --- /dev/null +++ b/dist/agosms-1.0.26/media/com_agosms/js/admin-agosms-button-default.js @@ -0,0 +1,45 @@ +/** + * @copyright Copyright (C) 2005 - 2017 Open Source Matters, Inc. All rights reserved. + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ +(function () { + "use strict"; + document.addEventListener('DOMContentLoaded', function () { + const cordsTextField = document.getElementById("jform_paramsmodal_cords_map"); + const unique = cordsTextField.getAttribute('data-unique'); + window['mymap' + unique] = L.map('mapid' + unique).setView([50.27264, 7.26469], 3); + L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png').addTo(window['mymap' + unique]); + window['mymap' + unique].on('click', onMapClick); + var marker = L.marker([50.28, 7.27]).addTo(window['mymap' + unique]).bindPopup(Joomla.JText._('COM_AGOSMS_BUTTON_DEFAULT_POPUP_PROMPT')).openPopup(); + cordsTextField.value = "50.28, 7.27"; + function onMapClick(e) { + const cordsTextField = document.getElementById("jform_paramsmodal_cords_map"); + if (cordsTextField) { + cordsTextField.value = e.latlng.lat + ', ' + e.latlng.lng; + var lat = (e.latlng.lat); + var lng = (e.latlng.lng); + var newLatLng = new L.LatLng(lat, lng); + marker.setLatLng(newLatLng); + } + } + + var parts = window.location.search.substr(1).split("&"); + var $_GET = {}; + for (var i = 0; i < parts.length; i++) { + var temp = parts[i].split("="); + $_GET[decodeURIComponent(temp[0])] = decodeURIComponent(temp[1]); + } + + const buttonSaveSelected = document.getElementById("buttonsaveselected"); + const cordsParentTextField = window.parent.document.getElementById($_GET.fieldid); + if (buttonSaveSelected && cordsParentTextField && cordsTextField) { + buttonSaveSelected.addEventListener('click', function () { + cordsParentTextField.setAttribute("readonly", false); + cordsParentTextField.value = cordsTextField.value; + cordsParentTextField.setAttribute("readonly", true); + window.parent.jModalClose(); + }); + } + + }); +})(); diff --git a/dist/agosms-1.0.26/media/com_agosms/js/admin-agosms-modal.js b/dist/agosms-1.0.26/media/com_agosms/js/admin-agosms-modal.js new file mode 100644 index 00000000..35f71330 --- /dev/null +++ b/dist/agosms-1.0.26/media/com_agosms/js/admin-agosms-modal.js @@ -0,0 +1,61 @@ +/** + * @copyright Copyright (C) 2005 - 2017 Open Source Matters, Inc. All rights reserved. + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ +(function() { + "use strict"; + /** + * Javascript to insert the link + * View element calls jSelectWeblink when an weblink is clicked + * jSelectWeblink creates the link tag, sends it to the editor, + * and closes the select frame. + **/ + window.jSelectWeblink = function (id, title, catid, object, link, lang) { + var hreflang = '', editor, tag; + + if (!Joomla.getOptions('xtd-weblinks')) { + // Something went wrong! + window.parent.jModalClose(); + return false; + } + + editor = Joomla.getOptions('xtd-weblinks').editor; + + if (lang !== '') + { + hreflang = ' hreflang="' + lang + '"'; + } + + tag = '' + title + ''; + + /** Use the API, if editor supports it **/ + if (window.Joomla && window.Joomla.editors && Joomla.editors.instances && Joomla.editors.instances.hasOwnProperty(editor)) { + Joomla.editors.instances[editor].replaceSelection(tag) + } else { + window.parent.jInsertEditorText(tag, editor); + } + + window.parent.jModalClose(); + }; + + document.addEventListener('DOMContentLoaded', function(){ + // Get the elements + var elements = document.querySelectorAll('.select-link'); + + for(var i = 0, l = elements.length; l>i; i++) { + // Listen for click event + elements[i].addEventListener('click', function (event) { + event.preventDefault(); + var functionName = event.target.getAttribute('data-function'); + + if (functionName === 'jSelectWeblink') { + // Used in xtd_weblinks + window[functionName](event.target.getAttribute('data-id'), event.target.getAttribute('data-title'), event.target.getAttribute('data-cat-id'), null, event.target.getAttribute('data-uri'), event.target.getAttribute('data-language', null)); + } else { + // Used in com_menus + window.parent[functionName](event.target.getAttribute('data-id'), event.target.getAttribute('data-title'), event.target.getAttribute('data-cat-id'), null, event.target.getAttribute('data-uri'), event.target.getAttribute('data-language', null)); + } + }) + } + }); +})(); diff --git a/dist/agosms-1.0.26/media/com_agosms/js/admin-agosms-modal.min.js b/dist/agosms-1.0.26/media/com_agosms/js/admin-agosms-modal.min.js new file mode 100644 index 00000000..dcc3689c --- /dev/null +++ b/dist/agosms-1.0.26/media/com_agosms/js/admin-agosms-modal.min.js @@ -0,0 +1 @@ +!function(){"use strict";window.jSelectWeblink=function(a,b,c,d,e,f){var h,i,g="";if(!Joomla.getOptions("xtd-weblinks"))return window.parent.jModalClose(),!1;h=Joomla.getOptions("xtd-weblinks").editor,""!==f&&(g=' hreflang="'+f+'"'),i="'+b+"",window.Joomla&&window.Joomla.editors&&Joomla.editors.instances&&Joomla.editors.instances.hasOwnProperty(h)?Joomla.editors.instances[h].replaceSelection(i):window.parent.jInsertEditorText(i,h),window.parent.jModalClose()},document.addEventListener("DOMContentLoaded",function(){for(var a=document.querySelectorAll(".select-link"),b=0,c=a.length;c>b;b++)a[b].addEventListener("click",function(a){a.preventDefault();var b=a.target.getAttribute("data-function");"jSelectWeblink"===b?window[b](a.target.getAttribute("data-id"),a.target.getAttribute("data-title"),a.target.getAttribute("data-cat-id"),null,a.target.getAttribute("data-uri"),a.target.getAttribute("data-language",null)):window.parent[b](a.target.getAttribute("data-id"),a.target.getAttribute("data-title"),a.target.getAttribute("data-cat-id"),null,a.target.getAttribute("data-uri"),a.target.getAttribute("data-language",null))})})}(); \ No newline at end of file diff --git a/dist/agosms-1.0.26/media/com_agosms/leaflet/images/layers-2x.png b/dist/agosms-1.0.26/media/com_agosms/leaflet/images/layers-2x.png new file mode 100644 index 00000000..200c333d Binary files /dev/null and b/dist/agosms-1.0.26/media/com_agosms/leaflet/images/layers-2x.png differ diff --git a/dist/agosms-1.0.26/media/com_agosms/leaflet/images/layers.png b/dist/agosms-1.0.26/media/com_agosms/leaflet/images/layers.png new file mode 100644 index 00000000..1a72e578 Binary files /dev/null and b/dist/agosms-1.0.26/media/com_agosms/leaflet/images/layers.png differ diff --git a/dist/agosms-1.0.26/media/com_agosms/leaflet/images/marker-icon-2x.png b/dist/agosms-1.0.26/media/com_agosms/leaflet/images/marker-icon-2x.png new file mode 100644 index 00000000..88f9e501 Binary files /dev/null and b/dist/agosms-1.0.26/media/com_agosms/leaflet/images/marker-icon-2x.png differ diff --git a/dist/agosms-1.0.26/media/com_agosms/leaflet/images/marker-icon.png b/dist/agosms-1.0.26/media/com_agosms/leaflet/images/marker-icon.png new file mode 100644 index 00000000..950edf24 Binary files /dev/null and b/dist/agosms-1.0.26/media/com_agosms/leaflet/images/marker-icon.png differ diff --git a/dist/agosms-1.0.26/media/com_agosms/leaflet/images/marker-shadow.png b/dist/agosms-1.0.26/media/com_agosms/leaflet/images/marker-shadow.png new file mode 100644 index 00000000..9fd29795 Binary files /dev/null and b/dist/agosms-1.0.26/media/com_agosms/leaflet/images/marker-shadow.png differ diff --git a/dist/agosms-1.0.26/media/com_agosms/leaflet/leaflet-src.js b/dist/agosms-1.0.26/media/com_agosms/leaflet/leaflet-src.js new file mode 100644 index 00000000..0206a731 --- /dev/null +++ b/dist/agosms-1.0.26/media/com_agosms/leaflet/leaflet-src.js @@ -0,0 +1,13802 @@ +/* @preserve + * Leaflet 1.3.1+Detached: ba6f97fff8647e724e4dfe66d2ed7da11f908989.ba6f97f, a JS library for interactive maps. http://leafletjs.com + * (c) 2010-2017 Vladimir Agafonkin, (c) 2010-2011 CloudMade + */ + +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : + typeof define === 'function' && define.amd ? define(['exports'], factory) : + (factory((global.L = {}))); +}(this, (function (exports) { 'use strict'; + +var version = "1.3.1+HEAD.ba6f97f"; + +/* + * @namespace Util + * + * Various utility functions, used by Leaflet internally. + */ + +var freeze = Object.freeze; +Object.freeze = function (obj) { return obj; }; + +// @function extend(dest: Object, src?: Object): Object +// Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut. +function extend(dest) { + var i, j, len, src; + + for (j = 1, len = arguments.length; j < len; j++) { + src = arguments[j]; + for (i in src) { + dest[i] = src[i]; + } + } + return dest; +} + +// @function create(proto: Object, properties?: Object): Object +// Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create) +var create = Object.create || (function () { + function F() {} + return function (proto) { + F.prototype = proto; + return new F(); + }; +})(); + +// @function bind(fn: Function, …): Function +// Returns a new function bound to the arguments passed, like [Function.prototype.bind](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Function/bind). +// Has a `L.bind()` shortcut. +function bind(fn, obj) { + var slice = Array.prototype.slice; + + if (fn.bind) { + return fn.bind.apply(fn, slice.call(arguments, 1)); + } + + var args = slice.call(arguments, 2); + + return function () { + return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments); + }; +} + +// @property lastId: Number +// Last unique ID used by [`stamp()`](#util-stamp) +var lastId = 0; + +// @function stamp(obj: Object): Number +// Returns the unique ID of an object, assigning it one if it doesn't have it. +function stamp(obj) { + /*eslint-disable */ + obj._leaflet_id = obj._leaflet_id || ++lastId; + return obj._leaflet_id; + /* eslint-enable */ +} + +// @function throttle(fn: Function, time: Number, context: Object): Function +// Returns a function which executes function `fn` with the given scope `context` +// (so that the `this` keyword refers to `context` inside `fn`'s code). The function +// `fn` will be called no more than one time per given amount of `time`. The arguments +// received by the bound function will be any arguments passed when binding the +// function, followed by any arguments passed when invoking the bound function. +// Has an `L.throttle` shortcut. +function throttle(fn, time, context) { + var lock, args, wrapperFn, later; + + later = function () { + // reset lock and call if queued + lock = false; + if (args) { + wrapperFn.apply(context, args); + args = false; + } + }; + + wrapperFn = function () { + if (lock) { + // called too soon, queue to call later + args = arguments; + + } else { + // call and lock until later + fn.apply(context, arguments); + setTimeout(later, time); + lock = true; + } + }; + + return wrapperFn; +} + +// @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number +// Returns the number `num` modulo `range` in such a way so it lies within +// `range[0]` and `range[1]`. The returned value will be always smaller than +// `range[1]` unless `includeMax` is set to `true`. +function wrapNum(x, range, includeMax) { + var max = range[1], + min = range[0], + d = max - min; + return x === max && includeMax ? x : ((x - min) % d + d) % d + min; +} + +// @function falseFn(): Function +// Returns a function which always returns `false`. +function falseFn() { return false; } + +// @function formatNum(num: Number, digits?: Number): Number +// Returns the number `num` rounded to `digits` decimals, or to 6 decimals by default. +function formatNum(num, digits) { + var pow = Math.pow(10, (digits === undefined ? 6 : digits)); + return Math.round(num * pow) / pow; +} + +// @function trim(str: String): String +// Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim) +function trim(str) { + return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, ''); +} + +// @function splitWords(str: String): String[] +// Trims and splits the string on whitespace and returns the array of parts. +function splitWords(str) { + return trim(str).split(/\s+/); +} + +// @function setOptions(obj: Object, options: Object): Object +// Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut. +function setOptions(obj, options) { + if (!obj.hasOwnProperty('options')) { + obj.options = obj.options ? create(obj.options) : {}; + } + for (var i in options) { + obj.options[i] = options[i]; + } + return obj.options; +} + +// @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String +// Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}` +// translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will +// be appended at the end. If `uppercase` is `true`, the parameter names will +// be uppercased (e.g. `'?A=foo&B=bar'`) +function getParamString(obj, existingUrl, uppercase) { + var params = []; + for (var i in obj) { + params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i])); + } + return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&'); +} + +var templateRe = /\{ *([\w_-]+) *\}/g; + +// @function template(str: String, data: Object): String +// Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'` +// and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string +// `('Hello foo, bar')`. You can also specify functions instead of strings for +// data values — they will be evaluated passing `data` as an argument. +function template(str, data) { + return str.replace(templateRe, function (str, key) { + var value = data[key]; + + if (value === undefined) { + throw new Error('No value provided for variable ' + str); + + } else if (typeof value === 'function') { + value = value(data); + } + return value; + }); +} + +// @function isArray(obj): Boolean +// Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray) +var isArray = Array.isArray || function (obj) { + return (Object.prototype.toString.call(obj) === '[object Array]'); +}; + +// @function indexOf(array: Array, el: Object): Number +// Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf) +function indexOf(array, el) { + for (var i = 0; i < array.length; i++) { + if (array[i] === el) { return i; } + } + return -1; +} + +// @property emptyImageUrl: String +// Data URI string containing a base64-encoded empty GIF image. +// Used as a hack to free memory from unused images on WebKit-powered +// mobile devices (by setting image `src` to this string). +var emptyImageUrl = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='; + +// inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/ + +function getPrefixed(name) { + return window['webkit' + name] || window['moz' + name] || window['ms' + name]; +} + +var lastTime = 0; + +// fallback for IE 7-8 +function timeoutDefer(fn) { + var time = +new Date(), + timeToCall = Math.max(0, 16 - (time - lastTime)); + + lastTime = time + timeToCall; + return window.setTimeout(fn, timeToCall); +} + +var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer; +var cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') || + getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); }; + +// @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number +// Schedules `fn` to be executed when the browser repaints. `fn` is bound to +// `context` if given. When `immediate` is set, `fn` is called immediately if +// the browser doesn't have native support for +// [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame), +// otherwise it's delayed. Returns a request ID that can be used to cancel the request. +function requestAnimFrame(fn, context, immediate) { + if (immediate && requestFn === timeoutDefer) { + fn.call(context); + } else { + return requestFn.call(window, bind(fn, context)); + } +} + +// @function cancelAnimFrame(id: Number): undefined +// Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame). +function cancelAnimFrame(id) { + if (id) { + cancelFn.call(window, id); + } +} + + +var Util = (Object.freeze || Object)({ + freeze: freeze, + extend: extend, + create: create, + bind: bind, + lastId: lastId, + stamp: stamp, + throttle: throttle, + wrapNum: wrapNum, + falseFn: falseFn, + formatNum: formatNum, + trim: trim, + splitWords: splitWords, + setOptions: setOptions, + getParamString: getParamString, + template: template, + isArray: isArray, + indexOf: indexOf, + emptyImageUrl: emptyImageUrl, + requestFn: requestFn, + cancelFn: cancelFn, + requestAnimFrame: requestAnimFrame, + cancelAnimFrame: cancelAnimFrame +}); + +// @class Class +// @aka L.Class + +// @section +// @uninheritable + +// Thanks to John Resig and Dean Edwards for inspiration! + +function Class() {} + +Class.extend = function (props) { + + // @function extend(props: Object): Function + // [Extends the current class](#class-inheritance) given the properties to be included. + // Returns a Javascript function that is a class constructor (to be called with `new`). + var NewClass = function () { + + // call the constructor + if (this.initialize) { + this.initialize.apply(this, arguments); + } + + // call all constructor hooks + this.callInitHooks(); + }; + + var parentProto = NewClass.__super__ = this.prototype; + + var proto = create(parentProto); + proto.constructor = NewClass; + + NewClass.prototype = proto; + + // inherit parent's statics + for (var i in this) { + if (this.hasOwnProperty(i) && i !== 'prototype' && i !== '__super__') { + NewClass[i] = this[i]; + } + } + + // mix static properties into the class + if (props.statics) { + extend(NewClass, props.statics); + delete props.statics; + } + + // mix includes into the prototype + if (props.includes) { + checkDeprecatedMixinEvents(props.includes); + extend.apply(null, [proto].concat(props.includes)); + delete props.includes; + } + + // merge options + if (proto.options) { + props.options = extend(create(proto.options), props.options); + } + + // mix given properties into the prototype + extend(proto, props); + + proto._initHooks = []; + + // add method for calling all hooks + proto.callInitHooks = function () { + + if (this._initHooksCalled) { return; } + + if (parentProto.callInitHooks) { + parentProto.callInitHooks.call(this); + } + + this._initHooksCalled = true; + + for (var i = 0, len = proto._initHooks.length; i < len; i++) { + proto._initHooks[i].call(this); + } + }; + + return NewClass; +}; + + +// @function include(properties: Object): this +// [Includes a mixin](#class-includes) into the current class. +Class.include = function (props) { + extend(this.prototype, props); + return this; +}; + +// @function mergeOptions(options: Object): this +// [Merges `options`](#class-options) into the defaults of the class. +Class.mergeOptions = function (options) { + extend(this.prototype.options, options); + return this; +}; + +// @function addInitHook(fn: Function): this +// Adds a [constructor hook](#class-constructor-hooks) to the class. +Class.addInitHook = function (fn) { // (Function) || (String, args...) + var args = Array.prototype.slice.call(arguments, 1); + + var init = typeof fn === 'function' ? fn : function () { + this[fn].apply(this, args); + }; + + this.prototype._initHooks = this.prototype._initHooks || []; + this.prototype._initHooks.push(init); + return this; +}; + +function checkDeprecatedMixinEvents(includes) { + if (typeof L === 'undefined' || !L || !L.Mixin) { return; } + + includes = isArray(includes) ? includes : [includes]; + + for (var i = 0; i < includes.length; i++) { + if (includes[i] === L.Mixin.Events) { + console.warn('Deprecated include of L.Mixin.Events: ' + + 'this property will be removed in future releases, ' + + 'please inherit from L.Evented instead.', new Error().stack); + } + } +} + +/* + * @class Evented + * @aka L.Evented + * @inherits Class + * + * A set of methods shared between event-powered classes (like `Map` and `Marker`). Generally, events allow you to execute some function when something happens with an object (e.g. the user clicks on the map, causing the map to fire `'click'` event). + * + * @example + * + * ```js + * map.on('click', function(e) { + * alert(e.latlng); + * } ); + * ``` + * + * Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function: + * + * ```js + * function onClick(e) { ... } + * + * map.on('click', onClick); + * map.off('click', onClick); + * ``` + */ + +var Events = { + /* @method on(type: String, fn: Function, context?: Object): this + * Adds a listener function (`fn`) to a particular event type of the object. You can optionally specify the context of the listener (object the this keyword will point to). You can also pass several space-separated types (e.g. `'click dblclick'`). + * + * @alternative + * @method on(eventMap: Object): this + * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}` + */ + on: function (types, fn, context) { + + // types can be a map of types/handlers + if (typeof types === 'object') { + for (var type in types) { + // we don't process space-separated events here for performance; + // it's a hot path since Layer uses the on(obj) syntax + this._on(type, types[type], fn); + } + + } else { + // types can be a string of space-separated words + types = splitWords(types); + + for (var i = 0, len = types.length; i < len; i++) { + this._on(types[i], fn, context); + } + } + + return this; + }, + + /* @method off(type: String, fn?: Function, context?: Object): this + * Removes a previously added listener function. If no function is specified, it will remove all the listeners of that particular event from the object. Note that if you passed a custom context to `on`, you must pass the same context to `off` in order to remove the listener. + * + * @alternative + * @method off(eventMap: Object): this + * Removes a set of type/listener pairs. + * + * @alternative + * @method off: this + * Removes all listeners to all events on the object. + */ + off: function (types, fn, context) { + + if (!types) { + // clear all listeners if called without arguments + delete this._events; + + } else if (typeof types === 'object') { + for (var type in types) { + this._off(type, types[type], fn); + } + + } else { + types = splitWords(types); + + for (var i = 0, len = types.length; i < len; i++) { + this._off(types[i], fn, context); + } + } + + return this; + }, + + // attach listener (without syntactic sugar now) + _on: function (type, fn, context) { + this._events = this._events || {}; + + /* get/init listeners for type */ + var typeListeners = this._events[type]; + if (!typeListeners) { + typeListeners = []; + this._events[type] = typeListeners; + } + + if (context === this) { + // Less memory footprint. + context = undefined; + } + var newListener = {fn: fn, ctx: context}, + listeners = typeListeners; + + // check if fn already there + for (var i = 0, len = listeners.length; i < len; i++) { + if (listeners[i].fn === fn && listeners[i].ctx === context) { + return; + } + } + + listeners.push(newListener); + }, + + _off: function (type, fn, context) { + var listeners, + i, + len; + + if (!this._events) { return; } + + listeners = this._events[type]; + + if (!listeners) { + return; + } + + if (!fn) { + // Set all removed listeners to noop so they are not called if remove happens in fire + for (i = 0, len = listeners.length; i < len; i++) { + listeners[i].fn = falseFn; + } + // clear all listeners for a type if function isn't specified + delete this._events[type]; + return; + } + + if (context === this) { + context = undefined; + } + + if (listeners) { + + // find fn and remove it + for (i = 0, len = listeners.length; i < len; i++) { + var l = listeners[i]; + if (l.ctx !== context) { continue; } + if (l.fn === fn) { + + // set the removed listener to noop so that's not called if remove happens in fire + l.fn = falseFn; + + if (this._firingCount) { + /* copy array in case events are being fired */ + this._events[type] = listeners = listeners.slice(); + } + listeners.splice(i, 1); + + return; + } + } + } + }, + + // @method fire(type: String, data?: Object, propagate?: Boolean): this + // Fires an event of the specified type. You can optionally provide an data + // object — the first argument of the listener function will contain its + // properties. The event can optionally be propagated to event parents. + fire: function (type, data, propagate) { + if (!this.listens(type, propagate)) { return this; } + + var event = extend({}, data, { + type: type, + target: this, + sourceTarget: data && data.sourceTarget || this + }); + + if (this._events) { + var listeners = this._events[type]; + + if (listeners) { + this._firingCount = (this._firingCount + 1) || 1; + for (var i = 0, len = listeners.length; i < len; i++) { + var l = listeners[i]; + l.fn.call(l.ctx || this, event); + } + + this._firingCount--; + } + } + + if (propagate) { + // propagate the event to parents (set with addEventParent) + this._propagateEvent(event); + } + + return this; + }, + + // @method listens(type: String): Boolean + // Returns `true` if a particular event type has any listeners attached to it. + listens: function (type, propagate) { + var listeners = this._events && this._events[type]; + if (listeners && listeners.length) { return true; } + + if (propagate) { + // also check parents for listeners if event propagates + for (var id in this._eventParents) { + if (this._eventParents[id].listens(type, propagate)) { return true; } + } + } + return false; + }, + + // @method once(…): this + // Behaves as [`on(…)`](#evented-on), except the listener will only get fired once and then removed. + once: function (types, fn, context) { + + if (typeof types === 'object') { + for (var type in types) { + this.once(type, types[type], fn); + } + return this; + } + + var handler = bind(function () { + this + .off(types, fn, context) + .off(types, handler, context); + }, this); + + // add a listener that's executed once and removed after that + return this + .on(types, fn, context) + .on(types, handler, context); + }, + + // @method addEventParent(obj: Evented): this + // Adds an event parent - an `Evented` that will receive propagated events + addEventParent: function (obj) { + this._eventParents = this._eventParents || {}; + this._eventParents[stamp(obj)] = obj; + return this; + }, + + // @method removeEventParent(obj: Evented): this + // Removes an event parent, so it will stop receiving propagated events + removeEventParent: function (obj) { + if (this._eventParents) { + delete this._eventParents[stamp(obj)]; + } + return this; + }, + + _propagateEvent: function (e) { + for (var id in this._eventParents) { + this._eventParents[id].fire(e.type, extend({ + layer: e.target, + propagatedFrom: e.target + }, e), true); + } + } +}; + +// aliases; we should ditch those eventually + +// @method addEventListener(…): this +// Alias to [`on(…)`](#evented-on) +Events.addEventListener = Events.on; + +// @method removeEventListener(…): this +// Alias to [`off(…)`](#evented-off) + +// @method clearAllEventListeners(…): this +// Alias to [`off()`](#evented-off) +Events.removeEventListener = Events.clearAllEventListeners = Events.off; + +// @method addOneTimeEventListener(…): this +// Alias to [`once(…)`](#evented-once) +Events.addOneTimeEventListener = Events.once; + +// @method fireEvent(…): this +// Alias to [`fire(…)`](#evented-fire) +Events.fireEvent = Events.fire; + +// @method hasEventListeners(…): Boolean +// Alias to [`listens(…)`](#evented-listens) +Events.hasEventListeners = Events.listens; + +var Evented = Class.extend(Events); + +/* + * @class Point + * @aka L.Point + * + * Represents a point with `x` and `y` coordinates in pixels. + * + * @example + * + * ```js + * var point = L.point(200, 300); + * ``` + * + * All Leaflet methods and options that accept `Point` objects also accept them in a simple Array form (unless noted otherwise), so these lines are equivalent: + * + * ```js + * map.panBy([200, 300]); + * map.panBy(L.point(200, 300)); + * ``` + * + * Note that `Point` does not inherit from Leafet's `Class` object, + * which means new classes can't inherit from it, and new methods + * can't be added to it with the `include` function. + */ + +function Point(x, y, round) { + // @property x: Number; The `x` coordinate of the point + this.x = (round ? Math.round(x) : x); + // @property y: Number; The `y` coordinate of the point + this.y = (round ? Math.round(y) : y); +} + +var trunc = Math.trunc || function (v) { + return v > 0 ? Math.floor(v) : Math.ceil(v); +}; + +Point.prototype = { + + // @method clone(): Point + // Returns a copy of the current point. + clone: function () { + return new Point(this.x, this.y); + }, + + // @method add(otherPoint: Point): Point + // Returns the result of addition of the current and the given points. + add: function (point) { + // non-destructive, returns a new point + return this.clone()._add(toPoint(point)); + }, + + _add: function (point) { + // destructive, used directly for performance in situations where it's safe to modify existing point + this.x += point.x; + this.y += point.y; + return this; + }, + + // @method subtract(otherPoint: Point): Point + // Returns the result of subtraction of the given point from the current. + subtract: function (point) { + return this.clone()._subtract(toPoint(point)); + }, + + _subtract: function (point) { + this.x -= point.x; + this.y -= point.y; + return this; + }, + + // @method divideBy(num: Number): Point + // Returns the result of division of the current point by the given number. + divideBy: function (num) { + return this.clone()._divideBy(num); + }, + + _divideBy: function (num) { + this.x /= num; + this.y /= num; + return this; + }, + + // @method multiplyBy(num: Number): Point + // Returns the result of multiplication of the current point by the given number. + multiplyBy: function (num) { + return this.clone()._multiplyBy(num); + }, + + _multiplyBy: function (num) { + this.x *= num; + this.y *= num; + return this; + }, + + // @method scaleBy(scale: Point): Point + // Multiply each coordinate of the current point by each coordinate of + // `scale`. In linear algebra terms, multiply the point by the + // [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation) + // defined by `scale`. + scaleBy: function (point) { + return new Point(this.x * point.x, this.y * point.y); + }, + + // @method unscaleBy(scale: Point): Point + // Inverse of `scaleBy`. Divide each coordinate of the current point by + // each coordinate of `scale`. + unscaleBy: function (point) { + return new Point(this.x / point.x, this.y / point.y); + }, + + // @method round(): Point + // Returns a copy of the current point with rounded coordinates. + round: function () { + return this.clone()._round(); + }, + + _round: function () { + this.x = Math.round(this.x); + this.y = Math.round(this.y); + return this; + }, + + // @method floor(): Point + // Returns a copy of the current point with floored coordinates (rounded down). + floor: function () { + return this.clone()._floor(); + }, + + _floor: function () { + this.x = Math.floor(this.x); + this.y = Math.floor(this.y); + return this; + }, + + // @method ceil(): Point + // Returns a copy of the current point with ceiled coordinates (rounded up). + ceil: function () { + return this.clone()._ceil(); + }, + + _ceil: function () { + this.x = Math.ceil(this.x); + this.y = Math.ceil(this.y); + return this; + }, + + // @method trunc(): Point + // Returns a copy of the current point with truncated coordinates (rounded towards zero). + trunc: function () { + return this.clone()._trunc(); + }, + + _trunc: function () { + this.x = trunc(this.x); + this.y = trunc(this.y); + return this; + }, + + // @method distanceTo(otherPoint: Point): Number + // Returns the cartesian distance between the current and the given points. + distanceTo: function (point) { + point = toPoint(point); + + var x = point.x - this.x, + y = point.y - this.y; + + return Math.sqrt(x * x + y * y); + }, + + // @method equals(otherPoint: Point): Boolean + // Returns `true` if the given point has the same coordinates. + equals: function (point) { + point = toPoint(point); + + return point.x === this.x && + point.y === this.y; + }, + + // @method contains(otherPoint: Point): Boolean + // Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values). + contains: function (point) { + point = toPoint(point); + + return Math.abs(point.x) <= Math.abs(this.x) && + Math.abs(point.y) <= Math.abs(this.y); + }, + + // @method toString(): String + // Returns a string representation of the point for debugging purposes. + toString: function () { + return 'Point(' + + formatNum(this.x) + ', ' + + formatNum(this.y) + ')'; + } +}; + +// @factory L.point(x: Number, y: Number, round?: Boolean) +// Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values. + +// @alternative +// @factory L.point(coords: Number[]) +// Expects an array of the form `[x, y]` instead. + +// @alternative +// @factory L.point(coords: Object) +// Expects a plain object of the form `{x: Number, y: Number}` instead. +function toPoint(x, y, round) { + if (x instanceof Point) { + return x; + } + if (isArray(x)) { + return new Point(x[0], x[1]); + } + if (x === undefined || x === null) { + return x; + } + if (typeof x === 'object' && 'x' in x && 'y' in x) { + return new Point(x.x, x.y); + } + return new Point(x, y, round); +} + +/* + * @class Bounds + * @aka L.Bounds + * + * Represents a rectangular area in pixel coordinates. + * + * @example + * + * ```js + * var p1 = L.point(10, 10), + * p2 = L.point(40, 60), + * bounds = L.bounds(p1, p2); + * ``` + * + * All Leaflet methods that accept `Bounds` objects also accept them in a simple Array form (unless noted otherwise), so the bounds example above can be passed like this: + * + * ```js + * otherBounds.intersects([[10, 10], [40, 60]]); + * ``` + * + * Note that `Bounds` does not inherit from Leafet's `Class` object, + * which means new classes can't inherit from it, and new methods + * can't be added to it with the `include` function. + */ + +function Bounds(a, b) { + if (!a) { return; } + + var points = b ? [a, b] : a; + + for (var i = 0, len = points.length; i < len; i++) { + this.extend(points[i]); + } +} + +Bounds.prototype = { + // @method extend(point: Point): this + // Extends the bounds to contain the given point. + extend: function (point) { // (Point) + point = toPoint(point); + + // @property min: Point + // The top left corner of the rectangle. + // @property max: Point + // The bottom right corner of the rectangle. + if (!this.min && !this.max) { + this.min = point.clone(); + this.max = point.clone(); + } else { + this.min.x = Math.min(point.x, this.min.x); + this.max.x = Math.max(point.x, this.max.x); + this.min.y = Math.min(point.y, this.min.y); + this.max.y = Math.max(point.y, this.max.y); + } + return this; + }, + + // @method getCenter(round?: Boolean): Point + // Returns the center point of the bounds. + getCenter: function (round) { + return new Point( + (this.min.x + this.max.x) / 2, + (this.min.y + this.max.y) / 2, round); + }, + + // @method getBottomLeft(): Point + // Returns the bottom-left point of the bounds. + getBottomLeft: function () { + return new Point(this.min.x, this.max.y); + }, + + // @method getTopRight(): Point + // Returns the top-right point of the bounds. + getTopRight: function () { // -> Point + return new Point(this.max.x, this.min.y); + }, + + // @method getTopLeft(): Point + // Returns the top-left point of the bounds (i.e. [`this.min`](#bounds-min)). + getTopLeft: function () { + return this.min; // left, top + }, + + // @method getBottomRight(): Point + // Returns the bottom-right point of the bounds (i.e. [`this.max`](#bounds-max)). + getBottomRight: function () { + return this.max; // right, bottom + }, + + // @method getSize(): Point + // Returns the size of the given bounds + getSize: function () { + return this.max.subtract(this.min); + }, + + // @method contains(otherBounds: Bounds): Boolean + // Returns `true` if the rectangle contains the given one. + // @alternative + // @method contains(point: Point): Boolean + // Returns `true` if the rectangle contains the given point. + contains: function (obj) { + var min, max; + + if (typeof obj[0] === 'number' || obj instanceof Point) { + obj = toPoint(obj); + } else { + obj = toBounds(obj); + } + + if (obj instanceof Bounds) { + min = obj.min; + max = obj.max; + } else { + min = max = obj; + } + + return (min.x >= this.min.x) && + (max.x <= this.max.x) && + (min.y >= this.min.y) && + (max.y <= this.max.y); + }, + + // @method intersects(otherBounds: Bounds): Boolean + // Returns `true` if the rectangle intersects the given bounds. Two bounds + // intersect if they have at least one point in common. + intersects: function (bounds) { // (Bounds) -> Boolean + bounds = toBounds(bounds); + + var min = this.min, + max = this.max, + min2 = bounds.min, + max2 = bounds.max, + xIntersects = (max2.x >= min.x) && (min2.x <= max.x), + yIntersects = (max2.y >= min.y) && (min2.y <= max.y); + + return xIntersects && yIntersects; + }, + + // @method overlaps(otherBounds: Bounds): Boolean + // Returns `true` if the rectangle overlaps the given bounds. Two bounds + // overlap if their intersection is an area. + overlaps: function (bounds) { // (Bounds) -> Boolean + bounds = toBounds(bounds); + + var min = this.min, + max = this.max, + min2 = bounds.min, + max2 = bounds.max, + xOverlaps = (max2.x > min.x) && (min2.x < max.x), + yOverlaps = (max2.y > min.y) && (min2.y < max.y); + + return xOverlaps && yOverlaps; + }, + + isValid: function () { + return !!(this.min && this.max); + } +}; + + +// @factory L.bounds(corner1: Point, corner2: Point) +// Creates a Bounds object from two corners coordinate pairs. +// @alternative +// @factory L.bounds(points: Point[]) +// Creates a Bounds object from the given array of points. +function toBounds(a, b) { + if (!a || a instanceof Bounds) { + return a; + } + return new Bounds(a, b); +} + +/* + * @class LatLngBounds + * @aka L.LatLngBounds + * + * Represents a rectangular geographical area on a map. + * + * @example + * + * ```js + * var corner1 = L.latLng(40.712, -74.227), + * corner2 = L.latLng(40.774, -74.125), + * bounds = L.latLngBounds(corner1, corner2); + * ``` + * + * All Leaflet methods that accept LatLngBounds objects also accept them in a simple Array form (unless noted otherwise), so the bounds example above can be passed like this: + * + * ```js + * map.fitBounds([ + * [40.712, -74.227], + * [40.774, -74.125] + * ]); + * ``` + * + * Caution: if the area crosses the antimeridian (often confused with the International Date Line), you must specify corners _outside_ the [-180, 180] degrees longitude range. + * + * Note that `LatLngBounds` does not inherit from Leafet's `Class` object, + * which means new classes can't inherit from it, and new methods + * can't be added to it with the `include` function. + */ + +function LatLngBounds(corner1, corner2) { // (LatLng, LatLng) or (LatLng[]) + if (!corner1) { return; } + + var latlngs = corner2 ? [corner1, corner2] : corner1; + + for (var i = 0, len = latlngs.length; i < len; i++) { + this.extend(latlngs[i]); + } +} + +LatLngBounds.prototype = { + + // @method extend(latlng: LatLng): this + // Extend the bounds to contain the given point + + // @alternative + // @method extend(otherBounds: LatLngBounds): this + // Extend the bounds to contain the given bounds + extend: function (obj) { + var sw = this._southWest, + ne = this._northEast, + sw2, ne2; + + if (obj instanceof LatLng) { + sw2 = obj; + ne2 = obj; + + } else if (obj instanceof LatLngBounds) { + sw2 = obj._southWest; + ne2 = obj._northEast; + + if (!sw2 || !ne2) { return this; } + + } else { + return obj ? this.extend(toLatLng(obj) || toLatLngBounds(obj)) : this; + } + + if (!sw && !ne) { + this._southWest = new LatLng(sw2.lat, sw2.lng); + this._northEast = new LatLng(ne2.lat, ne2.lng); + } else { + sw.lat = Math.min(sw2.lat, sw.lat); + sw.lng = Math.min(sw2.lng, sw.lng); + ne.lat = Math.max(ne2.lat, ne.lat); + ne.lng = Math.max(ne2.lng, ne.lng); + } + + return this; + }, + + // @method pad(bufferRatio: Number): LatLngBounds + // Returns bounds created by extending or retracting the current bounds by a given ratio in each direction. + // For example, a ratio of 0.5 extends the bounds by 50% in each direction. + // Negative values will retract the bounds. + pad: function (bufferRatio) { + var sw = this._southWest, + ne = this._northEast, + heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio, + widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio; + + return new LatLngBounds( + new LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer), + new LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer)); + }, + + // @method getCenter(): LatLng + // Returns the center point of the bounds. + getCenter: function () { + return new LatLng( + (this._southWest.lat + this._northEast.lat) / 2, + (this._southWest.lng + this._northEast.lng) / 2); + }, + + // @method getSouthWest(): LatLng + // Returns the south-west point of the bounds. + getSouthWest: function () { + return this._southWest; + }, + + // @method getNorthEast(): LatLng + // Returns the north-east point of the bounds. + getNorthEast: function () { + return this._northEast; + }, + + // @method getNorthWest(): LatLng + // Returns the north-west point of the bounds. + getNorthWest: function () { + return new LatLng(this.getNorth(), this.getWest()); + }, + + // @method getSouthEast(): LatLng + // Returns the south-east point of the bounds. + getSouthEast: function () { + return new LatLng(this.getSouth(), this.getEast()); + }, + + // @method getWest(): Number + // Returns the west longitude of the bounds + getWest: function () { + return this._southWest.lng; + }, + + // @method getSouth(): Number + // Returns the south latitude of the bounds + getSouth: function () { + return this._southWest.lat; + }, + + // @method getEast(): Number + // Returns the east longitude of the bounds + getEast: function () { + return this._northEast.lng; + }, + + // @method getNorth(): Number + // Returns the north latitude of the bounds + getNorth: function () { + return this._northEast.lat; + }, + + // @method contains(otherBounds: LatLngBounds): Boolean + // Returns `true` if the rectangle contains the given one. + + // @alternative + // @method contains (latlng: LatLng): Boolean + // Returns `true` if the rectangle contains the given point. + contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean + if (typeof obj[0] === 'number' || obj instanceof LatLng || 'lat' in obj) { + obj = toLatLng(obj); + } else { + obj = toLatLngBounds(obj); + } + + var sw = this._southWest, + ne = this._northEast, + sw2, ne2; + + if (obj instanceof LatLngBounds) { + sw2 = obj.getSouthWest(); + ne2 = obj.getNorthEast(); + } else { + sw2 = ne2 = obj; + } + + return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) && + (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng); + }, + + // @method intersects(otherBounds: LatLngBounds): Boolean + // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common. + intersects: function (bounds) { + bounds = toLatLngBounds(bounds); + + var sw = this._southWest, + ne = this._northEast, + sw2 = bounds.getSouthWest(), + ne2 = bounds.getNorthEast(), + + latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat), + lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng); + + return latIntersects && lngIntersects; + }, + + // @method overlaps(otherBounds: Bounds): Boolean + // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area. + overlaps: function (bounds) { + bounds = toLatLngBounds(bounds); + + var sw = this._southWest, + ne = this._northEast, + sw2 = bounds.getSouthWest(), + ne2 = bounds.getNorthEast(), + + latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat), + lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng); + + return latOverlaps && lngOverlaps; + }, + + // @method toBBoxString(): String + // Returns a string with bounding box coordinates in a 'southwest_lng,southwest_lat,northeast_lng,northeast_lat' format. Useful for sending requests to web services that return geo data. + toBBoxString: function () { + return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(','); + }, + + // @method equals(otherBounds: LatLngBounds, maxMargin?: Number): Boolean + // Returns `true` if the rectangle is equivalent (within a small margin of error) to the given bounds. The margin of error can be overridden by setting `maxMargin` to a small number. + equals: function (bounds, maxMargin) { + if (!bounds) { return false; } + + bounds = toLatLngBounds(bounds); + + return this._southWest.equals(bounds.getSouthWest(), maxMargin) && + this._northEast.equals(bounds.getNorthEast(), maxMargin); + }, + + // @method isValid(): Boolean + // Returns `true` if the bounds are properly initialized. + isValid: function () { + return !!(this._southWest && this._northEast); + } +}; + +// TODO International date line? + +// @factory L.latLngBounds(corner1: LatLng, corner2: LatLng) +// Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle. + +// @alternative +// @factory L.latLngBounds(latlngs: LatLng[]) +// Creates a `LatLngBounds` object defined by the geographical points it contains. Very useful for zooming the map to fit a particular set of locations with [`fitBounds`](#map-fitbounds). +function toLatLngBounds(a, b) { + if (a instanceof LatLngBounds) { + return a; + } + return new LatLngBounds(a, b); +} + +/* @class LatLng + * @aka L.LatLng + * + * Represents a geographical point with a certain latitude and longitude. + * + * @example + * + * ``` + * var latlng = L.latLng(50.5, 30.5); + * ``` + * + * All Leaflet methods that accept LatLng objects also accept them in a simple Array form and simple object form (unless noted otherwise), so these lines are equivalent: + * + * ``` + * map.panTo([50, 30]); + * map.panTo({lon: 30, lat: 50}); + * map.panTo({lat: 50, lng: 30}); + * map.panTo(L.latLng(50, 30)); + * ``` + * + * Note that `LatLng` does not inherit from Leafet's `Class` object, + * which means new classes can't inherit from it, and new methods + * can't be added to it with the `include` function. + */ + +function LatLng(lat, lng, alt) { + if (isNaN(lat) || isNaN(lng)) { + throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')'); + } + + // @property lat: Number + // Latitude in degrees + this.lat = +lat; + + // @property lng: Number + // Longitude in degrees + this.lng = +lng; + + // @property alt: Number + // Altitude in meters (optional) + if (alt !== undefined) { + this.alt = +alt; + } +} + +LatLng.prototype = { + // @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean + // Returns `true` if the given `LatLng` point is at the same position (within a small margin of error). The margin of error can be overridden by setting `maxMargin` to a small number. + equals: function (obj, maxMargin) { + if (!obj) { return false; } + + obj = toLatLng(obj); + + var margin = Math.max( + Math.abs(this.lat - obj.lat), + Math.abs(this.lng - obj.lng)); + + return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin); + }, + + // @method toString(): String + // Returns a string representation of the point (for debugging purposes). + toString: function (precision) { + return 'LatLng(' + + formatNum(this.lat, precision) + ', ' + + formatNum(this.lng, precision) + ')'; + }, + + // @method distanceTo(otherLatLng: LatLng): Number + // Returns the distance (in meters) to the given `LatLng` calculated using the [Spherical Law of Cosines](https://en.wikipedia.org/wiki/Spherical_law_of_cosines). + distanceTo: function (other) { + return Earth.distance(this, toLatLng(other)); + }, + + // @method wrap(): LatLng + // Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees. + wrap: function () { + return Earth.wrapLatLng(this); + }, + + // @method toBounds(sizeInMeters: Number): LatLngBounds + // Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters/2` meters apart from the `LatLng`. + toBounds: function (sizeInMeters) { + var latAccuracy = 180 * sizeInMeters / 40075017, + lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat); + + return toLatLngBounds( + [this.lat - latAccuracy, this.lng - lngAccuracy], + [this.lat + latAccuracy, this.lng + lngAccuracy]); + }, + + clone: function () { + return new LatLng(this.lat, this.lng, this.alt); + } +}; + + + +// @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng +// Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude). + +// @alternative +// @factory L.latLng(coords: Array): LatLng +// Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead. + +// @alternative +// @factory L.latLng(coords: Object): LatLng +// Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead. + +function toLatLng(a, b, c) { + if (a instanceof LatLng) { + return a; + } + if (isArray(a) && typeof a[0] !== 'object') { + if (a.length === 3) { + return new LatLng(a[0], a[1], a[2]); + } + if (a.length === 2) { + return new LatLng(a[0], a[1]); + } + return null; + } + if (a === undefined || a === null) { + return a; + } + if (typeof a === 'object' && 'lat' in a) { + return new LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt); + } + if (b === undefined) { + return null; + } + return new LatLng(a, b, c); +} + +/* + * @namespace CRS + * @crs L.CRS.Base + * Object that defines coordinate reference systems for projecting + * geographical points into pixel (screen) coordinates and back (and to + * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See + * [spatial reference system](http://en.wikipedia.org/wiki/Coordinate_reference_system). + * + * Leaflet defines the most usual CRSs by default. If you want to use a + * CRS not defined by default, take a look at the + * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin. + * + * Note that the CRS instances do not inherit from Leafet's `Class` object, + * and can't be instantiated. Also, new classes can't inherit from them, + * and methods can't be added to them with the `include` function. + */ + +var CRS = { + // @method latLngToPoint(latlng: LatLng, zoom: Number): Point + // Projects geographical coordinates into pixel coordinates for a given zoom. + latLngToPoint: function (latlng, zoom) { + var projectedPoint = this.projection.project(latlng), + scale = this.scale(zoom); + + return this.transformation._transform(projectedPoint, scale); + }, + + // @method pointToLatLng(point: Point, zoom: Number): LatLng + // The inverse of `latLngToPoint`. Projects pixel coordinates on a given + // zoom into geographical coordinates. + pointToLatLng: function (point, zoom) { + var scale = this.scale(zoom), + untransformedPoint = this.transformation.untransform(point, scale); + + return this.projection.unproject(untransformedPoint); + }, + + // @method project(latlng: LatLng): Point + // Projects geographical coordinates into coordinates in units accepted for + // this CRS (e.g. meters for EPSG:3857, for passing it to WMS services). + project: function (latlng) { + return this.projection.project(latlng); + }, + + // @method unproject(point: Point): LatLng + // Given a projected coordinate returns the corresponding LatLng. + // The inverse of `project`. + unproject: function (point) { + return this.projection.unproject(point); + }, + + // @method scale(zoom: Number): Number + // Returns the scale used when transforming projected coordinates into + // pixel coordinates for a particular zoom. For example, it returns + // `256 * 2^zoom` for Mercator-based CRS. + scale: function (zoom) { + return 256 * Math.pow(2, zoom); + }, + + // @method zoom(scale: Number): Number + // Inverse of `scale()`, returns the zoom level corresponding to a scale + // factor of `scale`. + zoom: function (scale) { + return Math.log(scale / 256) / Math.LN2; + }, + + // @method getProjectedBounds(zoom: Number): Bounds + // Returns the projection's bounds scaled and transformed for the provided `zoom`. + getProjectedBounds: function (zoom) { + if (this.infinite) { return null; } + + var b = this.projection.bounds, + s = this.scale(zoom), + min = this.transformation.transform(b.min, s), + max = this.transformation.transform(b.max, s); + + return new Bounds(min, max); + }, + + // @method distance(latlng1: LatLng, latlng2: LatLng): Number + // Returns the distance between two geographical coordinates. + + // @property code: String + // Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`) + // + // @property wrapLng: Number[] + // An array of two numbers defining whether the longitude (horizontal) coordinate + // axis wraps around a given range and how. Defaults to `[-180, 180]` in most + // geographical CRSs. If `undefined`, the longitude axis does not wrap around. + // + // @property wrapLat: Number[] + // Like `wrapLng`, but for the latitude (vertical) axis. + + // wrapLng: [min, max], + // wrapLat: [min, max], + + // @property infinite: Boolean + // If true, the coordinate space will be unbounded (infinite in both axes) + infinite: false, + + // @method wrapLatLng(latlng: LatLng): LatLng + // Returns a `LatLng` where lat and lng has been wrapped according to the + // CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds. + wrapLatLng: function (latlng) { + var lng = this.wrapLng ? wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng, + lat = this.wrapLat ? wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat, + alt = latlng.alt; + + return new LatLng(lat, lng, alt); + }, + + // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds + // Returns a `LatLngBounds` with the same size as the given one, ensuring + // that its center is within the CRS's bounds. + // Only accepts actual `L.LatLngBounds` instances, not arrays. + wrapLatLngBounds: function (bounds) { + var center = bounds.getCenter(), + newCenter = this.wrapLatLng(center), + latShift = center.lat - newCenter.lat, + lngShift = center.lng - newCenter.lng; + + if (latShift === 0 && lngShift === 0) { + return bounds; + } + + var sw = bounds.getSouthWest(), + ne = bounds.getNorthEast(), + newSw = new LatLng(sw.lat - latShift, sw.lng - lngShift), + newNe = new LatLng(ne.lat - latShift, ne.lng - lngShift); + + return new LatLngBounds(newSw, newNe); + } +}; + +/* + * @namespace CRS + * @crs L.CRS.Earth + * + * Serves as the base for CRS that are global such that they cover the earth. + * Can only be used as the base for other CRS and cannot be used directly, + * since it does not have a `code`, `projection` or `transformation`. `distance()` returns + * meters. + */ + +var Earth = extend({}, CRS, { + wrapLng: [-180, 180], + + // Mean Earth Radius, as recommended for use by + // the International Union of Geodesy and Geophysics, + // see http://rosettacode.org/wiki/Haversine_formula + R: 6371000, + + // distance between two geographical points using spherical law of cosines approximation + distance: function (latlng1, latlng2) { + var rad = Math.PI / 180, + lat1 = latlng1.lat * rad, + lat2 = latlng2.lat * rad, + sinDLat = Math.sin((latlng2.lat - latlng1.lat) * rad / 2), + sinDLon = Math.sin((latlng2.lng - latlng1.lng) * rad / 2), + a = sinDLat * sinDLat + Math.cos(lat1) * Math.cos(lat2) * sinDLon * sinDLon, + c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + return this.R * c; + } +}); + +/* + * @namespace Projection + * @projection L.Projection.SphericalMercator + * + * Spherical Mercator projection — the most common projection for online maps, + * used by almost all free and commercial tile providers. Assumes that Earth is + * a sphere. Used by the `EPSG:3857` CRS. + */ + +var SphericalMercator = { + + R: 6378137, + MAX_LATITUDE: 85.0511287798, + + project: function (latlng) { + var d = Math.PI / 180, + max = this.MAX_LATITUDE, + lat = Math.max(Math.min(max, latlng.lat), -max), + sin = Math.sin(lat * d); + + return new Point( + this.R * latlng.lng * d, + this.R * Math.log((1 + sin) / (1 - sin)) / 2); + }, + + unproject: function (point) { + var d = 180 / Math.PI; + + return new LatLng( + (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d, + point.x * d / this.R); + }, + + bounds: (function () { + var d = 6378137 * Math.PI; + return new Bounds([-d, -d], [d, d]); + })() +}; + +/* + * @class Transformation + * @aka L.Transformation + * + * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d` + * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing + * the reverse. Used by Leaflet in its projections code. + * + * @example + * + * ```js + * var transformation = L.transformation(2, 5, -1, 10), + * p = L.point(1, 2), + * p2 = transformation.transform(p), // L.point(7, 8) + * p3 = transformation.untransform(p2); // L.point(1, 2) + * ``` + */ + + +// factory new L.Transformation(a: Number, b: Number, c: Number, d: Number) +// Creates a `Transformation` object with the given coefficients. +function Transformation(a, b, c, d) { + if (isArray(a)) { + // use array properties + this._a = a[0]; + this._b = a[1]; + this._c = a[2]; + this._d = a[3]; + return; + } + this._a = a; + this._b = b; + this._c = c; + this._d = d; +} + +Transformation.prototype = { + // @method transform(point: Point, scale?: Number): Point + // Returns a transformed point, optionally multiplied by the given scale. + // Only accepts actual `L.Point` instances, not arrays. + transform: function (point, scale) { // (Point, Number) -> Point + return this._transform(point.clone(), scale); + }, + + // destructive transform (faster) + _transform: function (point, scale) { + scale = scale || 1; + point.x = scale * (this._a * point.x + this._b); + point.y = scale * (this._c * point.y + this._d); + return point; + }, + + // @method untransform(point: Point, scale?: Number): Point + // Returns the reverse transformation of the given point, optionally divided + // by the given scale. Only accepts actual `L.Point` instances, not arrays. + untransform: function (point, scale) { + scale = scale || 1; + return new Point( + (point.x / scale - this._b) / this._a, + (point.y / scale - this._d) / this._c); + } +}; + +// factory L.transformation(a: Number, b: Number, c: Number, d: Number) + +// @factory L.transformation(a: Number, b: Number, c: Number, d: Number) +// Instantiates a Transformation object with the given coefficients. + +// @alternative +// @factory L.transformation(coefficients: Array): Transformation +// Expects an coefficients array of the form +// `[a: Number, b: Number, c: Number, d: Number]`. + +function toTransformation(a, b, c, d) { + return new Transformation(a, b, c, d); +} + +/* + * @namespace CRS + * @crs L.CRS.EPSG3857 + * + * The most common CRS for online maps, used by almost all free and commercial + * tile providers. Uses Spherical Mercator projection. Set in by default in + * Map's `crs` option. + */ + +var EPSG3857 = extend({}, Earth, { + code: 'EPSG:3857', + projection: SphericalMercator, + + transformation: (function () { + var scale = 0.5 / (Math.PI * SphericalMercator.R); + return toTransformation(scale, 0.5, -scale, 0.5); + }()) +}); + +var EPSG900913 = extend({}, EPSG3857, { + code: 'EPSG:900913' +}); + +// @namespace SVG; @section +// There are several static functions which can be called without instantiating L.SVG: + +// @function create(name: String): SVGElement +// Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement), +// corresponding to the class name passed. For example, using 'line' will return +// an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement). +function svgCreate(name) { + return document.createElementNS('http://www.w3.org/2000/svg', name); +} + +// @function pointsToPath(rings: Point[], closed: Boolean): String +// Generates a SVG path string for multiple rings, with each ring turning +// into "M..L..L.." instructions +function pointsToPath(rings, closed) { + var str = '', + i, j, len, len2, points, p; + + for (i = 0, len = rings.length; i < len; i++) { + points = rings[i]; + + for (j = 0, len2 = points.length; j < len2; j++) { + p = points[j]; + str += (j ? 'L' : 'M') + p.x + ' ' + p.y; + } + + // closes the ring for polygons; "x" is VML syntax + str += closed ? (svg ? 'z' : 'x') : ''; + } + + // SVG complains about empty path strings + return str || 'M0 0'; +} + +/* + * @namespace Browser + * @aka L.Browser + * + * A namespace with static properties for browser/feature detection used by Leaflet internally. + * + * @example + * + * ```js + * if (L.Browser.ielt9) { + * alert('Upgrade your browser, dude!'); + * } + * ``` + */ + +var style$1 = document.documentElement.style; + +// @property ie: Boolean; `true` for all Internet Explorer versions (not Edge). +var ie = 'ActiveXObject' in window; + +// @property ielt9: Boolean; `true` for Internet Explorer versions less than 9. +var ielt9 = ie && !document.addEventListener; + +// @property edge: Boolean; `true` for the Edge web browser. +var edge = 'msLaunchUri' in navigator && !('documentMode' in document); + +// @property webkit: Boolean; +// `true` for webkit-based browsers like Chrome and Safari (including mobile versions). +var webkit = userAgentContains('webkit'); + +// @property android: Boolean +// `true` for any browser running on an Android platform. +var android = userAgentContains('android'); + +// @property android23: Boolean; `true` for browsers running on Android 2 or Android 3. +var android23 = userAgentContains('android 2') || userAgentContains('android 3'); + +/* See https://stackoverflow.com/a/17961266 for details on detecting stock Android */ +var webkitVer = parseInt(/WebKit\/([0-9]+)|$/.exec(navigator.userAgent)[1], 10); // also matches AppleWebKit +// @property androidStock: Boolean; `true` for the Android stock browser (i.e. not Chrome) +var androidStock = android && userAgentContains('Google') && webkitVer < 537 && !('AudioNode' in window); + +// @property opera: Boolean; `true` for the Opera browser +var opera = !!window.opera; + +// @property chrome: Boolean; `true` for the Chrome browser. +var chrome = userAgentContains('chrome'); + +// @property gecko: Boolean; `true` for gecko-based browsers like Firefox. +var gecko = userAgentContains('gecko') && !webkit && !opera && !ie; + +// @property safari: Boolean; `true` for the Safari browser. +var safari = !chrome && userAgentContains('safari'); + +var phantom = userAgentContains('phantom'); + +// @property opera12: Boolean +// `true` for the Opera browser supporting CSS transforms (version 12 or later). +var opera12 = 'OTransition' in style$1; + +// @property win: Boolean; `true` when the browser is running in a Windows platform +var win = navigator.platform.indexOf('Win') === 0; + +// @property ie3d: Boolean; `true` for all Internet Explorer versions supporting CSS transforms. +var ie3d = ie && ('transition' in style$1); + +// @property webkit3d: Boolean; `true` for webkit-based browsers supporting CSS transforms. +var webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23; + +// @property gecko3d: Boolean; `true` for gecko-based browsers supporting CSS transforms. +var gecko3d = 'MozPerspective' in style$1; + +// @property any3d: Boolean +// `true` for all browsers supporting CSS transforms. +var any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantom; + +// @property mobile: Boolean; `true` for all browsers running in a mobile device. +var mobile = typeof orientation !== 'undefined' || userAgentContains('mobile'); + +// @property mobileWebkit: Boolean; `true` for all webkit-based browsers in a mobile device. +var mobileWebkit = mobile && webkit; + +// @property mobileWebkit3d: Boolean +// `true` for all webkit-based browsers in a mobile device supporting CSS transforms. +var mobileWebkit3d = mobile && webkit3d; + +// @property msPointer: Boolean +// `true` for browsers implementing the Microsoft touch events model (notably IE10). +var msPointer = !window.PointerEvent && window.MSPointerEvent; + +// @property pointer: Boolean +// `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx). +var pointer = !!(window.PointerEvent || msPointer); + +// @property touch: Boolean +// `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events). +// This does not necessarily mean that the browser is running in a computer with +// a touchscreen, it only means that the browser is capable of understanding +// touch events. +var touch = !window.L_NO_TOUCH && (pointer || 'ontouchstart' in window || + (window.DocumentTouch && document instanceof window.DocumentTouch)); + +// @property mobileOpera: Boolean; `true` for the Opera browser in a mobile device. +var mobileOpera = mobile && opera; + +// @property mobileGecko: Boolean +// `true` for gecko-based browsers running in a mobile device. +var mobileGecko = mobile && gecko; + +// @property retina: Boolean +// `true` for browsers on a high-resolution "retina" screen. +var retina = (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1; + + +// @property canvas: Boolean +// `true` when the browser supports [``](https://developer.mozilla.org/docs/Web/API/Canvas_API). +var canvas = (function () { + return !!document.createElement('canvas').getContext; +}()); + +// @property svg: Boolean +// `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG). +var svg = !!(document.createElementNS && svgCreate('svg').createSVGRect); + +// @property vml: Boolean +// `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language). +var vml = !svg && (function () { + try { + var div = document.createElement('div'); + div.innerHTML = ''; + + var shape = div.firstChild; + shape.style.behavior = 'url(#default#VML)'; + + return shape && (typeof shape.adj === 'object'); + + } catch (e) { + return false; + } +}()); + + +function userAgentContains(str) { + return navigator.userAgent.toLowerCase().indexOf(str) >= 0; +} + + +var Browser = (Object.freeze || Object)({ + ie: ie, + ielt9: ielt9, + edge: edge, + webkit: webkit, + android: android, + android23: android23, + androidStock: androidStock, + opera: opera, + chrome: chrome, + gecko: gecko, + safari: safari, + phantom: phantom, + opera12: opera12, + win: win, + ie3d: ie3d, + webkit3d: webkit3d, + gecko3d: gecko3d, + any3d: any3d, + mobile: mobile, + mobileWebkit: mobileWebkit, + mobileWebkit3d: mobileWebkit3d, + msPointer: msPointer, + pointer: pointer, + touch: touch, + mobileOpera: mobileOpera, + mobileGecko: mobileGecko, + retina: retina, + canvas: canvas, + svg: svg, + vml: vml +}); + +/* + * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices. + */ + + +var POINTER_DOWN = msPointer ? 'MSPointerDown' : 'pointerdown'; +var POINTER_MOVE = msPointer ? 'MSPointerMove' : 'pointermove'; +var POINTER_UP = msPointer ? 'MSPointerUp' : 'pointerup'; +var POINTER_CANCEL = msPointer ? 'MSPointerCancel' : 'pointercancel'; +var TAG_WHITE_LIST = ['INPUT', 'SELECT', 'OPTION']; + +var _pointers = {}; +var _pointerDocListener = false; + +// DomEvent.DoubleTap needs to know about this +var _pointersCount = 0; + +// Provides a touch events wrapper for (ms)pointer events. +// ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890 + +function addPointerListener(obj, type, handler, id) { + if (type === 'touchstart') { + _addPointerStart(obj, handler, id); + + } else if (type === 'touchmove') { + _addPointerMove(obj, handler, id); + + } else if (type === 'touchend') { + _addPointerEnd(obj, handler, id); + } + + return this; +} + +function removePointerListener(obj, type, id) { + var handler = obj['_leaflet_' + type + id]; + + if (type === 'touchstart') { + obj.removeEventListener(POINTER_DOWN, handler, false); + + } else if (type === 'touchmove') { + obj.removeEventListener(POINTER_MOVE, handler, false); + + } else if (type === 'touchend') { + obj.removeEventListener(POINTER_UP, handler, false); + obj.removeEventListener(POINTER_CANCEL, handler, false); + } + + return this; +} + +function _addPointerStart(obj, handler, id) { + var onDown = bind(function (e) { + if (e.pointerType !== 'mouse' && e.MSPOINTER_TYPE_MOUSE && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) { + // In IE11, some touch events needs to fire for form controls, or + // the controls will stop working. We keep a whitelist of tag names that + // need these events. For other target tags, we prevent default on the event. + if (TAG_WHITE_LIST.indexOf(e.target.tagName) < 0) { + preventDefault(e); + } else { + return; + } + } + + _handlePointer(e, handler); + }); + + obj['_leaflet_touchstart' + id] = onDown; + obj.addEventListener(POINTER_DOWN, onDown, false); + + // need to keep track of what pointers and how many are active to provide e.touches emulation + if (!_pointerDocListener) { + // we listen documentElement as any drags that end by moving the touch off the screen get fired there + document.documentElement.addEventListener(POINTER_DOWN, _globalPointerDown, true); + document.documentElement.addEventListener(POINTER_MOVE, _globalPointerMove, true); + document.documentElement.addEventListener(POINTER_UP, _globalPointerUp, true); + document.documentElement.addEventListener(POINTER_CANCEL, _globalPointerUp, true); + + _pointerDocListener = true; + } +} + +function _globalPointerDown(e) { + _pointers[e.pointerId] = e; + _pointersCount++; +} + +function _globalPointerMove(e) { + if (_pointers[e.pointerId]) { + _pointers[e.pointerId] = e; + } +} + +function _globalPointerUp(e) { + delete _pointers[e.pointerId]; + _pointersCount--; +} + +function _handlePointer(e, handler) { + e.touches = []; + for (var i in _pointers) { + e.touches.push(_pointers[i]); + } + e.changedTouches = [e]; + + handler(e); +} + +function _addPointerMove(obj, handler, id) { + var onMove = function (e) { + // don't fire touch moves when mouse isn't down + if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; } + + _handlePointer(e, handler); + }; + + obj['_leaflet_touchmove' + id] = onMove; + obj.addEventListener(POINTER_MOVE, onMove, false); +} + +function _addPointerEnd(obj, handler, id) { + var onUp = function (e) { + _handlePointer(e, handler); + }; + + obj['_leaflet_touchend' + id] = onUp; + obj.addEventListener(POINTER_UP, onUp, false); + obj.addEventListener(POINTER_CANCEL, onUp, false); +} + +/* + * Extends the event handling code with double tap support for mobile browsers. + */ + +var _touchstart = msPointer ? 'MSPointerDown' : pointer ? 'pointerdown' : 'touchstart'; +var _touchend = msPointer ? 'MSPointerUp' : pointer ? 'pointerup' : 'touchend'; +var _pre = '_leaflet_'; + +// inspired by Zepto touch code by Thomas Fuchs +function addDoubleTapListener(obj, handler, id) { + var last, touch$$1, + doubleTap = false, + delay = 250; + + function onTouchStart(e) { + var count; + + if (pointer) { + if ((!edge) || e.pointerType === 'mouse') { return; } + count = _pointersCount; + } else { + count = e.touches.length; + } + + if (count > 1) { return; } + + var now = Date.now(), + delta = now - (last || now); + + touch$$1 = e.touches ? e.touches[0] : e; + doubleTap = (delta > 0 && delta <= delay); + last = now; + } + + function onTouchEnd(e) { + if (doubleTap && !touch$$1.cancelBubble) { + if (pointer) { + if ((!edge) || e.pointerType === 'mouse') { return; } + // work around .type being readonly with MSPointer* events + var newTouch = {}, + prop, i; + + for (i in touch$$1) { + prop = touch$$1[i]; + newTouch[i] = prop && prop.bind ? prop.bind(touch$$1) : prop; + } + touch$$1 = newTouch; + } + touch$$1.type = 'dblclick'; + handler(touch$$1); + last = null; + } + } + + obj[_pre + _touchstart + id] = onTouchStart; + obj[_pre + _touchend + id] = onTouchEnd; + obj[_pre + 'dblclick' + id] = handler; + + obj.addEventListener(_touchstart, onTouchStart, false); + obj.addEventListener(_touchend, onTouchEnd, false); + + // On some platforms (notably, chrome<55 on win10 + touchscreen + mouse), + // the browser doesn't fire touchend/pointerup events but does fire + // native dblclicks. See #4127. + // Edge 14 also fires native dblclicks, but only for pointerType mouse, see #5180. + obj.addEventListener('dblclick', handler, false); + + return this; +} + +function removeDoubleTapListener(obj, id) { + var touchstart = obj[_pre + _touchstart + id], + touchend = obj[_pre + _touchend + id], + dblclick = obj[_pre + 'dblclick' + id]; + + obj.removeEventListener(_touchstart, touchstart, false); + obj.removeEventListener(_touchend, touchend, false); + if (!edge) { + obj.removeEventListener('dblclick', dblclick, false); + } + + return this; +} + +/* + * @namespace DomEvent + * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally. + */ + +// Inspired by John Resig, Dean Edwards and YUI addEvent implementations. + +// @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this +// Adds a listener function (`fn`) to a particular DOM event type of the +// element `el`. You can optionally specify the context of the listener +// (object the `this` keyword will point to). You can also pass several +// space-separated types (e.g. `'click dblclick'`). + +// @alternative +// @function on(el: HTMLElement, eventMap: Object, context?: Object): this +// Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}` +function on(obj, types, fn, context) { + + if (typeof types === 'object') { + for (var type in types) { + addOne(obj, type, types[type], fn); + } + } else { + types = splitWords(types); + + for (var i = 0, len = types.length; i < len; i++) { + addOne(obj, types[i], fn, context); + } + } + + return this; +} + +var eventsKey = '_leaflet_events'; + +// @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this +// Removes a previously added listener function. +// Note that if you passed a custom context to on, you must pass the same +// context to `off` in order to remove the listener. + +// @alternative +// @function off(el: HTMLElement, eventMap: Object, context?: Object): this +// Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}` +function off(obj, types, fn, context) { + + if (typeof types === 'object') { + for (var type in types) { + removeOne(obj, type, types[type], fn); + } + } else if (types) { + types = splitWords(types); + + for (var i = 0, len = types.length; i < len; i++) { + removeOne(obj, types[i], fn, context); + } + } else { + for (var j in obj[eventsKey]) { + removeOne(obj, j, obj[eventsKey][j]); + } + delete obj[eventsKey]; + } + + return this; +} + +function addOne(obj, type, fn, context) { + var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''); + + if (obj[eventsKey] && obj[eventsKey][id]) { return this; } + + var handler = function (e) { + return fn.call(context || obj, e || window.event); + }; + + var originalHandler = handler; + + if (pointer && type.indexOf('touch') === 0) { + // Needs DomEvent.Pointer.js + addPointerListener(obj, type, handler, id); + + } else if (touch && (type === 'dblclick') && addDoubleTapListener && + !(pointer && chrome)) { + // Chrome >55 does not need the synthetic dblclicks from addDoubleTapListener + // See #5180 + addDoubleTapListener(obj, handler, id); + + } else if ('addEventListener' in obj) { + + if (type === 'mousewheel') { + obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false); + + } else if ((type === 'mouseenter') || (type === 'mouseleave')) { + handler = function (e) { + e = e || window.event; + if (isExternalTarget(obj, e)) { + originalHandler(e); + } + }; + obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false); + + } else { + if (type === 'click' && android) { + handler = function (e) { + filterClick(e, originalHandler); + }; + } + obj.addEventListener(type, handler, false); + } + + } else if ('attachEvent' in obj) { + obj.attachEvent('on' + type, handler); + } + + obj[eventsKey] = obj[eventsKey] || {}; + obj[eventsKey][id] = handler; +} + +function removeOne(obj, type, fn, context) { + + var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''), + handler = obj[eventsKey] && obj[eventsKey][id]; + + if (!handler) { return this; } + + if (pointer && type.indexOf('touch') === 0) { + removePointerListener(obj, type, id); + + } else if (touch && (type === 'dblclick') && removeDoubleTapListener && + !(pointer && chrome)) { + removeDoubleTapListener(obj, id); + + } else if ('removeEventListener' in obj) { + + if (type === 'mousewheel') { + obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false); + + } else { + obj.removeEventListener( + type === 'mouseenter' ? 'mouseover' : + type === 'mouseleave' ? 'mouseout' : type, handler, false); + } + + } else if ('detachEvent' in obj) { + obj.detachEvent('on' + type, handler); + } + + obj[eventsKey][id] = null; +} + +// @function stopPropagation(ev: DOMEvent): this +// Stop the given event from propagation to parent elements. Used inside the listener functions: +// ```js +// L.DomEvent.on(div, 'click', function (ev) { +// L.DomEvent.stopPropagation(ev); +// }); +// ``` +function stopPropagation(e) { + + if (e.stopPropagation) { + e.stopPropagation(); + } else if (e.originalEvent) { // In case of Leaflet event. + e.originalEvent._stopped = true; + } else { + e.cancelBubble = true; + } + skipped(e); + + return this; +} + +// @function disableScrollPropagation(el: HTMLElement): this +// Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants). +function disableScrollPropagation(el) { + addOne(el, 'mousewheel', stopPropagation); + return this; +} + +// @function disableClickPropagation(el: HTMLElement): this +// Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`, +// `'mousedown'` and `'touchstart'` events (plus browser variants). +function disableClickPropagation(el) { + on(el, 'mousedown touchstart dblclick', stopPropagation); + addOne(el, 'click', fakeStop); + return this; +} + +// @function preventDefault(ev: DOMEvent): this +// Prevents the default action of the DOM Event `ev` from happening (such as +// following a link in the href of the a element, or doing a POST request +// with page reload when a `
` is submitted). +// Use it inside listener functions. +function preventDefault(e) { + if (e.preventDefault) { + e.preventDefault(); + } else { + e.returnValue = false; + } + return this; +} + +// @function stop(ev: DOMEvent): this +// Does `stopPropagation` and `preventDefault` at the same time. +function stop(e) { + preventDefault(e); + stopPropagation(e); + return this; +} + +// @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point +// Gets normalized mouse position from a DOM event relative to the +// `container` or to the whole page if not specified. +function getMousePosition(e, container) { + if (!container) { + return new Point(e.clientX, e.clientY); + } + + var rect = container.getBoundingClientRect(); + + var scaleX = rect.width / container.offsetWidth || 1; + var scaleY = rect.height / container.offsetHeight || 1; + return new Point( + e.clientX / scaleX - rect.left - container.clientLeft, + e.clientY / scaleY - rect.top - container.clientTop); +} + +// Chrome on Win scrolls double the pixels as in other platforms (see #4538), +// and Firefox scrolls device pixels, not CSS pixels +var wheelPxFactor = + (win && chrome) ? 2 * window.devicePixelRatio : + gecko ? window.devicePixelRatio : 1; + +// @function getWheelDelta(ev: DOMEvent): Number +// Gets normalized wheel delta from a mousewheel DOM event, in vertical +// pixels scrolled (negative if scrolling down). +// Events from pointing devices without precise scrolling are mapped to +// a best guess of 60 pixels. +function getWheelDelta(e) { + return (edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta + (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels + (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines + (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages + (e.deltaX || e.deltaZ) ? 0 : // Skip horizontal/depth wheel events + e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels + (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines + e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages + 0; +} + +var skipEvents = {}; + +function fakeStop(e) { + // fakes stopPropagation by setting a special event flag, checked/reset with skipped(e) + skipEvents[e.type] = true; +} + +function skipped(e) { + var events = skipEvents[e.type]; + // reset when checking, as it's only used in map container and propagates outside of the map + skipEvents[e.type] = false; + return events; +} + +// check if element really left/entered the event target (for mouseenter/mouseleave) +function isExternalTarget(el, e) { + + var related = e.relatedTarget; + + if (!related) { return true; } + + try { + while (related && (related !== el)) { + related = related.parentNode; + } + } catch (err) { + return false; + } + return (related !== el); +} + +var lastClick; + +// this is a horrible workaround for a bug in Android where a single touch triggers two click events +function filterClick(e, handler) { + var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)), + elapsed = lastClick && (timeStamp - lastClick); + + // are they closer together than 500ms yet more than 100ms? + // Android typically triggers them ~300ms apart while multiple listeners + // on the same event should be triggered far faster; + // or check if click is simulated on the element, and if it is, reject any non-simulated events + + if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) { + stop(e); + return; + } + lastClick = timeStamp; + + handler(e); +} + + + + +var DomEvent = (Object.freeze || Object)({ + on: on, + off: off, + stopPropagation: stopPropagation, + disableScrollPropagation: disableScrollPropagation, + disableClickPropagation: disableClickPropagation, + preventDefault: preventDefault, + stop: stop, + getMousePosition: getMousePosition, + getWheelDelta: getWheelDelta, + fakeStop: fakeStop, + skipped: skipped, + isExternalTarget: isExternalTarget, + addListener: on, + removeListener: off +}); + +/* + * @namespace DomUtil + * + * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model) + * tree, used by Leaflet internally. + * + * Most functions expecting or returning a `HTMLElement` also work for + * SVG elements. The only difference is that classes refer to CSS classes + * in HTML and SVG classes in SVG. + */ + + +// @property TRANSFORM: String +// Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit). +var TRANSFORM = testProp( + ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']); + +// webkitTransition comes first because some browser versions that drop vendor prefix don't do +// the same for the transitionend event, in particular the Android 4.1 stock browser + +// @property TRANSITION: String +// Vendor-prefixed transition style name. +var TRANSITION = testProp( + ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']); + +// @property TRANSITION_END: String +// Vendor-prefixed transitionend event name. +var TRANSITION_END = + TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend'; + + +// @function get(id: String|HTMLElement): HTMLElement +// Returns an element given its DOM id, or returns the element itself +// if it was passed directly. +function get(id) { + return typeof id === 'string' ? document.getElementById(id) : id; +} + +// @function getStyle(el: HTMLElement, styleAttrib: String): String +// Returns the value for a certain style attribute on an element, +// including computed values or values set through CSS. +function getStyle(el, style) { + var value = el.style[style] || (el.currentStyle && el.currentStyle[style]); + + if ((!value || value === 'auto') && document.defaultView) { + var css = document.defaultView.getComputedStyle(el, null); + value = css ? css[style] : null; + } + return value === 'auto' ? null : value; +} + +// @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement +// Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element. +function create$1(tagName, className, container) { + var el = document.createElement(tagName); + el.className = className || ''; + + if (container) { + container.appendChild(el); + } + return el; +} + +// @function remove(el: HTMLElement) +// Removes `el` from its parent element +function remove(el) { + var parent = el.parentNode; + if (parent) { + parent.removeChild(el); + } +} + +// @function empty(el: HTMLElement) +// Removes all of `el`'s children elements from `el` +function empty(el) { + while (el.firstChild) { + el.removeChild(el.firstChild); + } +} + +// @function toFront(el: HTMLElement) +// Makes `el` the last child of its parent, so it renders in front of the other children. +function toFront(el) { + var parent = el.parentNode; + if (parent.lastChild !== el) { + parent.appendChild(el); + } +} + +// @function toBack(el: HTMLElement) +// Makes `el` the first child of its parent, so it renders behind the other children. +function toBack(el) { + var parent = el.parentNode; + if (parent.firstChild !== el) { + parent.insertBefore(el, parent.firstChild); + } +} + +// @function hasClass(el: HTMLElement, name: String): Boolean +// Returns `true` if the element's class attribute contains `name`. +function hasClass(el, name) { + if (el.classList !== undefined) { + return el.classList.contains(name); + } + var className = getClass(el); + return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className); +} + +// @function addClass(el: HTMLElement, name: String) +// Adds `name` to the element's class attribute. +function addClass(el, name) { + if (el.classList !== undefined) { + var classes = splitWords(name); + for (var i = 0, len = classes.length; i < len; i++) { + el.classList.add(classes[i]); + } + } else if (!hasClass(el, name)) { + var className = getClass(el); + setClass(el, (className ? className + ' ' : '') + name); + } +} + +// @function removeClass(el: HTMLElement, name: String) +// Removes `name` from the element's class attribute. +function removeClass(el, name) { + if (el.classList !== undefined) { + el.classList.remove(name); + } else { + setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' '))); + } +} + +// @function setClass(el: HTMLElement, name: String) +// Sets the element's class. +function setClass(el, name) { + if (el.className.baseVal === undefined) { + el.className = name; + } else { + // in case of SVG element + el.className.baseVal = name; + } +} + +// @function getClass(el: HTMLElement): String +// Returns the element's class. +function getClass(el) { + return el.className.baseVal === undefined ? el.className : el.className.baseVal; +} + +// @function setOpacity(el: HTMLElement, opacity: Number) +// Set the opacity of an element (including old IE support). +// `opacity` must be a number from `0` to `1`. +function setOpacity(el, value) { + if ('opacity' in el.style) { + el.style.opacity = value; + } else if ('filter' in el.style) { + _setOpacityIE(el, value); + } +} + +function _setOpacityIE(el, value) { + var filter = false, + filterName = 'DXImageTransform.Microsoft.Alpha'; + + // filters collection throws an error if we try to retrieve a filter that doesn't exist + try { + filter = el.filters.item(filterName); + } catch (e) { + // don't set opacity to 1 if we haven't already set an opacity, + // it isn't needed and breaks transparent pngs. + if (value === 1) { return; } + } + + value = Math.round(value * 100); + + if (filter) { + filter.Enabled = (value !== 100); + filter.Opacity = value; + } else { + el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')'; + } +} + +// @function testProp(props: String[]): String|false +// Goes through the array of style names and returns the first name +// that is a valid style name for an element. If no such name is found, +// it returns false. Useful for vendor-prefixed styles like `transform`. +function testProp(props) { + var style = document.documentElement.style; + + for (var i = 0; i < props.length; i++) { + if (props[i] in style) { + return props[i]; + } + } + return false; +} + +// @function setTransform(el: HTMLElement, offset: Point, scale?: Number) +// Resets the 3D CSS transform of `el` so it is translated by `offset` pixels +// and optionally scaled by `scale`. Does not have an effect if the +// browser doesn't support 3D CSS transforms. +function setTransform(el, offset, scale) { + var pos = offset || new Point(0, 0); + + el.style[TRANSFORM] = + (ie3d ? + 'translate(' + pos.x + 'px,' + pos.y + 'px)' : + 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') + + (scale ? ' scale(' + scale + ')' : ''); +} + +// @function setPosition(el: HTMLElement, position: Point) +// Sets the position of `el` to coordinates specified by `position`, +// using CSS translate or top/left positioning depending on the browser +// (used by Leaflet internally to position its layers). +function setPosition(el, point) { + + /*eslint-disable */ + el._leaflet_pos = point; + /* eslint-enable */ + + if (any3d) { + setTransform(el, point); + } else { + el.style.left = point.x + 'px'; + el.style.top = point.y + 'px'; + } +} + +// @function getPosition(el: HTMLElement): Point +// Returns the coordinates of an element previously positioned with setPosition. +function getPosition(el) { + // this method is only used for elements previously positioned using setPosition, + // so it's safe to cache the position for performance + + return el._leaflet_pos || new Point(0, 0); +} + +// @function disableTextSelection() +// Prevents the user from generating `selectstart` DOM events, usually generated +// when the user drags the mouse through a page with text. Used internally +// by Leaflet to override the behaviour of any click-and-drag interaction on +// the map. Affects drag interactions on the whole document. + +// @function enableTextSelection() +// Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection). +var disableTextSelection; +var enableTextSelection; +var _userSelect; +if ('onselectstart' in document) { + disableTextSelection = function () { + on(window, 'selectstart', preventDefault); + }; + enableTextSelection = function () { + off(window, 'selectstart', preventDefault); + }; +} else { + var userSelectProperty = testProp( + ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']); + + disableTextSelection = function () { + if (userSelectProperty) { + var style = document.documentElement.style; + _userSelect = style[userSelectProperty]; + style[userSelectProperty] = 'none'; + } + }; + enableTextSelection = function () { + if (userSelectProperty) { + document.documentElement.style[userSelectProperty] = _userSelect; + _userSelect = undefined; + } + }; +} + +// @function disableImageDrag() +// As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but +// for `dragstart` DOM events, usually generated when the user drags an image. +function disableImageDrag() { + on(window, 'dragstart', preventDefault); +} + +// @function enableImageDrag() +// Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection). +function enableImageDrag() { + off(window, 'dragstart', preventDefault); +} + +var _outlineElement; +var _outlineStyle; +// @function preventOutline(el: HTMLElement) +// Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline) +// of the element `el` invisible. Used internally by Leaflet to prevent +// focusable elements from displaying an outline when the user performs a +// drag interaction on them. +function preventOutline(element) { + while (element.tabIndex === -1) { + element = element.parentNode; + } + if (!element.style) { return; } + restoreOutline(); + _outlineElement = element; + _outlineStyle = element.style.outline; + element.style.outline = 'none'; + on(window, 'keydown', restoreOutline); +} + +// @function restoreOutline() +// Cancels the effects of a previous [`L.DomUtil.preventOutline`](). +function restoreOutline() { + if (!_outlineElement) { return; } + _outlineElement.style.outline = _outlineStyle; + _outlineElement = undefined; + _outlineStyle = undefined; + off(window, 'keydown', restoreOutline); +} + + +var DomUtil = (Object.freeze || Object)({ + TRANSFORM: TRANSFORM, + TRANSITION: TRANSITION, + TRANSITION_END: TRANSITION_END, + get: get, + getStyle: getStyle, + create: create$1, + remove: remove, + empty: empty, + toFront: toFront, + toBack: toBack, + hasClass: hasClass, + addClass: addClass, + removeClass: removeClass, + setClass: setClass, + getClass: getClass, + setOpacity: setOpacity, + testProp: testProp, + setTransform: setTransform, + setPosition: setPosition, + getPosition: getPosition, + disableTextSelection: disableTextSelection, + enableTextSelection: enableTextSelection, + disableImageDrag: disableImageDrag, + enableImageDrag: enableImageDrag, + preventOutline: preventOutline, + restoreOutline: restoreOutline +}); + +/* + * @class PosAnimation + * @aka L.PosAnimation + * @inherits Evented + * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9. + * + * @example + * ```js + * var fx = new L.PosAnimation(); + * fx.run(el, [300, 500], 0.5); + * ``` + * + * @constructor L.PosAnimation() + * Creates a `PosAnimation` object. + * + */ + +var PosAnimation = Evented.extend({ + + // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number) + // Run an animation of a given element to a new position, optionally setting + // duration in seconds (`0.25` by default) and easing linearity factor (3rd + // argument of the [cubic bezier curve](http://cubic-bezier.com/#0,0,.5,1), + // `0.5` by default). + run: function (el, newPos, duration, easeLinearity) { + this.stop(); + + this._el = el; + this._inProgress = true; + this._duration = duration || 0.25; + this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2); + + this._startPos = getPosition(el); + this._offset = newPos.subtract(this._startPos); + this._startTime = +new Date(); + + // @event start: Event + // Fired when the animation starts + this.fire('start'); + + this._animate(); + }, + + // @method stop() + // Stops the animation (if currently running). + stop: function () { + if (!this._inProgress) { return; } + + this._step(true); + this._complete(); + }, + + _animate: function () { + // animation loop + this._animId = requestAnimFrame(this._animate, this); + this._step(); + }, + + _step: function (round) { + var elapsed = (+new Date()) - this._startTime, + duration = this._duration * 1000; + + if (elapsed < duration) { + this._runFrame(this._easeOut(elapsed / duration), round); + } else { + this._runFrame(1); + this._complete(); + } + }, + + _runFrame: function (progress, round) { + var pos = this._startPos.add(this._offset.multiplyBy(progress)); + if (round) { + pos._round(); + } + setPosition(this._el, pos); + + // @event step: Event + // Fired continuously during the animation. + this.fire('step'); + }, + + _complete: function () { + cancelAnimFrame(this._animId); + + this._inProgress = false; + // @event end: Event + // Fired when the animation ends. + this.fire('end'); + }, + + _easeOut: function (t) { + return 1 - Math.pow(1 - t, this._easeOutPower); + } +}); + +/* + * @class Map + * @aka L.Map + * @inherits Evented + * + * The central class of the API — it is used to create a map on a page and manipulate it. + * + * @example + * + * ```js + * // initialize the map on the "map" div with a given center and zoom + * var map = L.map('map', { + * center: [51.505, -0.09], + * zoom: 13 + * }); + * ``` + * + */ + +var Map = Evented.extend({ + + options: { + // @section Map State Options + // @option crs: CRS = L.CRS.EPSG3857 + // The [Coordinate Reference System](#crs) to use. Don't change this if you're not + // sure what it means. + crs: EPSG3857, + + // @option center: LatLng = undefined + // Initial geographic center of the map + center: undefined, + + // @option zoom: Number = undefined + // Initial map zoom level + zoom: undefined, + + // @option minZoom: Number = * + // Minimum zoom level of the map. + // If not specified and at least one `GridLayer` or `TileLayer` is in the map, + // the lowest of their `minZoom` options will be used instead. + minZoom: undefined, + + // @option maxZoom: Number = * + // Maximum zoom level of the map. + // If not specified and at least one `GridLayer` or `TileLayer` is in the map, + // the highest of their `maxZoom` options will be used instead. + maxZoom: undefined, + + // @option layers: Layer[] = [] + // Array of layers that will be added to the map initially + layers: [], + + // @option maxBounds: LatLngBounds = null + // When this option is set, the map restricts the view to the given + // geographical bounds, bouncing the user back if the user tries to pan + // outside the view. To set the restriction dynamically, use + // [`setMaxBounds`](#map-setmaxbounds) method. + maxBounds: undefined, + + // @option renderer: Renderer = * + // The default method for drawing vector layers on the map. `L.SVG` + // or `L.Canvas` by default depending on browser support. + renderer: undefined, + + + // @section Animation Options + // @option zoomAnimation: Boolean = true + // Whether the map zoom animation is enabled. By default it's enabled + // in all browsers that support CSS3 Transitions except Android. + zoomAnimation: true, + + // @option zoomAnimationThreshold: Number = 4 + // Won't animate zoom if the zoom difference exceeds this value. + zoomAnimationThreshold: 4, + + // @option fadeAnimation: Boolean = true + // Whether the tile fade animation is enabled. By default it's enabled + // in all browsers that support CSS3 Transitions except Android. + fadeAnimation: true, + + // @option markerZoomAnimation: Boolean = true + // Whether markers animate their zoom with the zoom animation, if disabled + // they will disappear for the length of the animation. By default it's + // enabled in all browsers that support CSS3 Transitions except Android. + markerZoomAnimation: true, + + // @option transform3DLimit: Number = 2^23 + // Defines the maximum size of a CSS translation transform. The default + // value should not be changed unless a web browser positions layers in + // the wrong place after doing a large `panBy`. + transform3DLimit: 8388608, // Precision limit of a 32-bit float + + // @section Interaction Options + // @option zoomSnap: Number = 1 + // Forces the map's zoom level to always be a multiple of this, particularly + // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom. + // By default, the zoom level snaps to the nearest integer; lower values + // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0` + // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom. + zoomSnap: 1, + + // @option zoomDelta: Number = 1 + // Controls how much the map's zoom level will change after a + // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+` + // or `-` on the keyboard, or using the [zoom controls](#control-zoom). + // Values smaller than `1` (e.g. `0.5`) allow for greater granularity. + zoomDelta: 1, + + // @option trackResize: Boolean = true + // Whether the map automatically handles browser window resize to update itself. + trackResize: true + }, + + initialize: function (id, options) { // (HTMLElement or String, Object) + options = setOptions(this, options); + + this._initContainer(id); + this._initLayout(); + + // hack for https://github.com/Leaflet/Leaflet/issues/1980 + this._onResize = bind(this._onResize, this); + + this._initEvents(); + + if (options.maxBounds) { + this.setMaxBounds(options.maxBounds); + } + + if (options.zoom !== undefined) { + this._zoom = this._limitZoom(options.zoom); + } + + if (options.center && options.zoom !== undefined) { + this.setView(toLatLng(options.center), options.zoom, {reset: true}); + } + + this._handlers = []; + this._layers = {}; + this._zoomBoundLayers = {}; + this._sizeChanged = true; + + this.callInitHooks(); + + // don't animate on browsers without hardware-accelerated transitions or old Android/Opera + this._zoomAnimated = TRANSITION && any3d && !mobileOpera && + this.options.zoomAnimation; + + // zoom transitions run with the same duration for all layers, so if one of transitionend events + // happens after starting zoom animation (propagating to the map pane), we know that it ended globally + if (this._zoomAnimated) { + this._createAnimProxy(); + on(this._proxy, TRANSITION_END, this._catchTransitionEnd, this); + } + + this._addLayers(this.options.layers); + }, + + + // @section Methods for modifying map state + + // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this + // Sets the view of the map (geographical center and zoom) with the given + // animation options. + setView: function (center, zoom, options) { + + zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom); + center = this._limitCenter(toLatLng(center), zoom, this.options.maxBounds); + options = options || {}; + + this._stop(); + + if (this._loaded && !options.reset && options !== true) { + + if (options.animate !== undefined) { + options.zoom = extend({animate: options.animate}, options.zoom); + options.pan = extend({animate: options.animate, duration: options.duration}, options.pan); + } + + // try animating pan or zoom + var moved = (this._zoom !== zoom) ? + this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) : + this._tryAnimatedPan(center, options.pan); + + if (moved) { + // prevent resize handler call, the view will refresh after animation anyway + clearTimeout(this._sizeTimer); + return this; + } + } + + // animation didn't start, just reset the map view + this._resetView(center, zoom); + + return this; + }, + + // @method setZoom(zoom: Number, options?: Zoom/pan options): this + // Sets the zoom of the map. + setZoom: function (zoom, options) { + if (!this._loaded) { + this._zoom = zoom; + return this; + } + return this.setView(this.getCenter(), zoom, {zoom: options}); + }, + + // @method zoomIn(delta?: Number, options?: Zoom options): this + // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default). + zoomIn: function (delta, options) { + delta = delta || (any3d ? this.options.zoomDelta : 1); + return this.setZoom(this._zoom + delta, options); + }, + + // @method zoomOut(delta?: Number, options?: Zoom options): this + // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default). + zoomOut: function (delta, options) { + delta = delta || (any3d ? this.options.zoomDelta : 1); + return this.setZoom(this._zoom - delta, options); + }, + + // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this + // Zooms the map while keeping a specified geographical point on the map + // stationary (e.g. used internally for scroll zoom and double-click zoom). + // @alternative + // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this + // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary. + setZoomAround: function (latlng, zoom, options) { + var scale = this.getZoomScale(zoom), + viewHalf = this.getSize().divideBy(2), + containerPoint = latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng), + + centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale), + newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset)); + + return this.setView(newCenter, zoom, {zoom: options}); + }, + + _getBoundsCenterZoom: function (bounds, options) { + + options = options || {}; + bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds); + + var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]), + paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]), + + zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR)); + + zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom; + + if (zoom === Infinity) { + return { + center: bounds.getCenter(), + zoom: zoom + }; + } + + var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2), + + swPoint = this.project(bounds.getSouthWest(), zoom), + nePoint = this.project(bounds.getNorthEast(), zoom), + center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom); + + return { + center: center, + zoom: zoom + }; + }, + + // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this + // Sets a map view that contains the given geographical bounds with the + // maximum zoom level possible. + fitBounds: function (bounds, options) { + + bounds = toLatLngBounds(bounds); + + if (!bounds.isValid()) { + throw new Error('Bounds are not valid.'); + } + + var target = this._getBoundsCenterZoom(bounds, options); + return this.setView(target.center, target.zoom, options); + }, + + // @method fitWorld(options?: fitBounds options): this + // Sets a map view that mostly contains the whole world with the maximum + // zoom level possible. + fitWorld: function (options) { + return this.fitBounds([[-90, -180], [90, 180]], options); + }, + + // @method panTo(latlng: LatLng, options?: Pan options): this + // Pans the map to a given center. + panTo: function (center, options) { // (LatLng) + return this.setView(center, this._zoom, {pan: options}); + }, + + // @method panBy(offset: Point, options?: Pan options): this + // Pans the map by a given number of pixels (animated). + panBy: function (offset, options) { + offset = toPoint(offset).round(); + options = options || {}; + + if (!offset.x && !offset.y) { + return this.fire('moveend'); + } + // If we pan too far, Chrome gets issues with tiles + // and makes them disappear or appear in the wrong place (slightly offset) #2602 + if (options.animate !== true && !this.getSize().contains(offset)) { + this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom()); + return this; + } + + if (!this._panAnim) { + this._panAnim = new PosAnimation(); + + this._panAnim.on({ + 'step': this._onPanTransitionStep, + 'end': this._onPanTransitionEnd + }, this); + } + + // don't fire movestart if animating inertia + if (!options.noMoveStart) { + this.fire('movestart'); + } + + // animate pan unless animate: false specified + if (options.animate !== false) { + addClass(this._mapPane, 'leaflet-pan-anim'); + + var newPos = this._getMapPanePos().subtract(offset).round(); + this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity); + } else { + this._rawPanBy(offset); + this.fire('move').fire('moveend'); + } + + return this; + }, + + // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this + // Sets the view of the map (geographical center and zoom) performing a smooth + // pan-zoom animation. + flyTo: function (targetCenter, targetZoom, options) { + + options = options || {}; + if (options.animate === false || !any3d) { + return this.setView(targetCenter, targetZoom, options); + } + + this._stop(); + + var from = this.project(this.getCenter()), + to = this.project(targetCenter), + size = this.getSize(), + startZoom = this._zoom; + + targetCenter = toLatLng(targetCenter); + targetZoom = targetZoom === undefined ? startZoom : targetZoom; + + var w0 = Math.max(size.x, size.y), + w1 = w0 * this.getZoomScale(startZoom, targetZoom), + u1 = (to.distanceTo(from)) || 1, + rho = 1.42, + rho2 = rho * rho; + + function r(i) { + var s1 = i ? -1 : 1, + s2 = i ? w1 : w0, + t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1, + b1 = 2 * s2 * rho2 * u1, + b = t1 / b1, + sq = Math.sqrt(b * b + 1) - b; + + // workaround for floating point precision bug when sq = 0, log = -Infinite, + // thus triggering an infinite loop in flyTo + var log = sq < 0.000000001 ? -18 : Math.log(sq); + + return log; + } + + function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; } + function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; } + function tanh(n) { return sinh(n) / cosh(n); } + + var r0 = r(0); + + function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); } + function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; } + + function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); } + + var start = Date.now(), + S = (r(1) - r0) / rho, + duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8; + + function frame() { + var t = (Date.now() - start) / duration, + s = easeOut(t) * S; + + if (t <= 1) { + this._flyToFrame = requestAnimFrame(frame, this); + + this._move( + this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom), + this.getScaleZoom(w0 / w(s), startZoom), + {flyTo: true}); + + } else { + this + ._move(targetCenter, targetZoom) + ._moveEnd(true); + } + } + + this._moveStart(true, options.noMoveStart); + + frame.call(this); + return this; + }, + + // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this + // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto), + // but takes a bounds parameter like [`fitBounds`](#map-fitbounds). + flyToBounds: function (bounds, options) { + var target = this._getBoundsCenterZoom(bounds, options); + return this.flyTo(target.center, target.zoom, options); + }, + + // @method setMaxBounds(bounds: Bounds): this + // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option). + setMaxBounds: function (bounds) { + bounds = toLatLngBounds(bounds); + + if (!bounds.isValid()) { + this.options.maxBounds = null; + return this.off('moveend', this._panInsideMaxBounds); + } else if (this.options.maxBounds) { + this.off('moveend', this._panInsideMaxBounds); + } + + this.options.maxBounds = bounds; + + if (this._loaded) { + this._panInsideMaxBounds(); + } + + return this.on('moveend', this._panInsideMaxBounds); + }, + + // @method setMinZoom(zoom: Number): this + // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option). + setMinZoom: function (zoom) { + var oldZoom = this.options.minZoom; + this.options.minZoom = zoom; + + if (this._loaded && oldZoom !== zoom) { + this.fire('zoomlevelschange'); + + if (this.getZoom() < this.options.minZoom) { + return this.setZoom(zoom); + } + } + + return this; + }, + + // @method setMaxZoom(zoom: Number): this + // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option). + setMaxZoom: function (zoom) { + var oldZoom = this.options.maxZoom; + this.options.maxZoom = zoom; + + if (this._loaded && oldZoom !== zoom) { + this.fire('zoomlevelschange'); + + if (this.getZoom() > this.options.maxZoom) { + return this.setZoom(zoom); + } + } + + return this; + }, + + // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this + // Pans the map to the closest view that would lie inside the given bounds (if it's not already), controlling the animation using the options specific, if any. + panInsideBounds: function (bounds, options) { + this._enforcingBounds = true; + var center = this.getCenter(), + newCenter = this._limitCenter(center, this._zoom, toLatLngBounds(bounds)); + + if (!center.equals(newCenter)) { + this.panTo(newCenter, options); + } + + this._enforcingBounds = false; + return this; + }, + + // @method invalidateSize(options: Zoom/pan options): this + // Checks if the map container size changed and updates the map if so — + // call it after you've changed the map size dynamically, also animating + // pan by default. If `options.pan` is `false`, panning will not occur. + // If `options.debounceMoveend` is `true`, it will delay `moveend` event so + // that it doesn't happen often even if the method is called many + // times in a row. + + // @alternative + // @method invalidateSize(animate: Boolean): this + // Checks if the map container size changed and updates the map if so — + // call it after you've changed the map size dynamically, also animating + // pan by default. + invalidateSize: function (options) { + if (!this._loaded) { return this; } + + options = extend({ + animate: false, + pan: true + }, options === true ? {animate: true} : options); + + var oldSize = this.getSize(); + this._sizeChanged = true; + this._lastCenter = null; + + var newSize = this.getSize(), + oldCenter = oldSize.divideBy(2).round(), + newCenter = newSize.divideBy(2).round(), + offset = oldCenter.subtract(newCenter); + + if (!offset.x && !offset.y) { return this; } + + if (options.animate && options.pan) { + this.panBy(offset); + + } else { + if (options.pan) { + this._rawPanBy(offset); + } + + this.fire('move'); + + if (options.debounceMoveend) { + clearTimeout(this._sizeTimer); + this._sizeTimer = setTimeout(bind(this.fire, this, 'moveend'), 200); + } else { + this.fire('moveend'); + } + } + + // @section Map state change events + // @event resize: ResizeEvent + // Fired when the map is resized. + return this.fire('resize', { + oldSize: oldSize, + newSize: newSize + }); + }, + + // @section Methods for modifying map state + // @method stop(): this + // Stops the currently running `panTo` or `flyTo` animation, if any. + stop: function () { + this.setZoom(this._limitZoom(this._zoom)); + if (!this.options.zoomSnap) { + this.fire('viewreset'); + } + return this._stop(); + }, + + // @section Geolocation methods + // @method locate(options?: Locate options): this + // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound) + // event with location data on success or a [`locationerror`](#map-locationerror) event on failure, + // and optionally sets the map view to the user's location with respect to + // detection accuracy (or to the world view if geolocation failed). + // Note that, if your page doesn't use HTTPS, this method will fail in + // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins)) + // See `Locate options` for more details. + locate: function (options) { + + options = this._locateOptions = extend({ + timeout: 10000, + watch: false + // setView: false + // maxZoom: + // maximumAge: 0 + // enableHighAccuracy: false + }, options); + + if (!('geolocation' in navigator)) { + this._handleGeolocationError({ + code: 0, + message: 'Geolocation not supported.' + }); + return this; + } + + var onResponse = bind(this._handleGeolocationResponse, this), + onError = bind(this._handleGeolocationError, this); + + if (options.watch) { + this._locationWatchId = + navigator.geolocation.watchPosition(onResponse, onError, options); + } else { + navigator.geolocation.getCurrentPosition(onResponse, onError, options); + } + return this; + }, + + // @method stopLocate(): this + // Stops watching location previously initiated by `map.locate({watch: true})` + // and aborts resetting the map view if map.locate was called with + // `{setView: true}`. + stopLocate: function () { + if (navigator.geolocation && navigator.geolocation.clearWatch) { + navigator.geolocation.clearWatch(this._locationWatchId); + } + if (this._locateOptions) { + this._locateOptions.setView = false; + } + return this; + }, + + _handleGeolocationError: function (error) { + var c = error.code, + message = error.message || + (c === 1 ? 'permission denied' : + (c === 2 ? 'position unavailable' : 'timeout')); + + if (this._locateOptions.setView && !this._loaded) { + this.fitWorld(); + } + + // @section Location events + // @event locationerror: ErrorEvent + // Fired when geolocation (using the [`locate`](#map-locate) method) failed. + this.fire('locationerror', { + code: c, + message: 'Geolocation error: ' + message + '.' + }); + }, + + _handleGeolocationResponse: function (pos) { + var lat = pos.coords.latitude, + lng = pos.coords.longitude, + latlng = new LatLng(lat, lng), + bounds = latlng.toBounds(pos.coords.accuracy), + options = this._locateOptions; + + if (options.setView) { + var zoom = this.getBoundsZoom(bounds); + this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom); + } + + var data = { + latlng: latlng, + bounds: bounds, + timestamp: pos.timestamp + }; + + for (var i in pos.coords) { + if (typeof pos.coords[i] === 'number') { + data[i] = pos.coords[i]; + } + } + + // @event locationfound: LocationEvent + // Fired when geolocation (using the [`locate`](#map-locate) method) + // went successfully. + this.fire('locationfound', data); + }, + + // TODO Appropriate docs section? + // @section Other Methods + // @method addHandler(name: String, HandlerClass: Function): this + // Adds a new `Handler` to the map, given its name and constructor function. + addHandler: function (name, HandlerClass) { + if (!HandlerClass) { return this; } + + var handler = this[name] = new HandlerClass(this); + + this._handlers.push(handler); + + if (this.options[name]) { + handler.enable(); + } + + return this; + }, + + // @method remove(): this + // Destroys the map and clears all related event listeners. + remove: function () { + + this._initEvents(true); + + if (this._containerId !== this._container._leaflet_id) { + throw new Error('Map container is being reused by another instance'); + } + + try { + // throws error in IE6-8 + delete this._container._leaflet_id; + delete this._containerId; + } catch (e) { + /*eslint-disable */ + this._container._leaflet_id = undefined; + /* eslint-enable */ + this._containerId = undefined; + } + + if (this._locationWatchId !== undefined) { + this.stopLocate(); + } + + this._stop(); + + remove(this._mapPane); + + if (this._clearControlPos) { + this._clearControlPos(); + } + + this._clearHandlers(); + + if (this._loaded) { + // @section Map state change events + // @event unload: Event + // Fired when the map is destroyed with [remove](#map-remove) method. + this.fire('unload'); + } + + var i; + for (i in this._layers) { + this._layers[i].remove(); + } + for (i in this._panes) { + remove(this._panes[i]); + } + + this._layers = []; + this._panes = []; + delete this._mapPane; + delete this._renderer; + + return this; + }, + + // @section Other Methods + // @method createPane(name: String, container?: HTMLElement): HTMLElement + // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already, + // then returns it. The pane is created as a child of `container`, or + // as a child of the main map pane if not set. + createPane: function (name, container) { + var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''), + pane = create$1('div', className, container || this._mapPane); + + if (name) { + this._panes[name] = pane; + } + return pane; + }, + + // @section Methods for Getting Map State + + // @method getCenter(): LatLng + // Returns the geographical center of the map view + getCenter: function () { + this._checkIfLoaded(); + + if (this._lastCenter && !this._moved()) { + return this._lastCenter; + } + return this.layerPointToLatLng(this._getCenterLayerPoint()); + }, + + // @method getZoom(): Number + // Returns the current zoom level of the map view + getZoom: function () { + return this._zoom; + }, + + // @method getBounds(): LatLngBounds + // Returns the geographical bounds visible in the current map view + getBounds: function () { + var bounds = this.getPixelBounds(), + sw = this.unproject(bounds.getBottomLeft()), + ne = this.unproject(bounds.getTopRight()); + + return new LatLngBounds(sw, ne); + }, + + // @method getMinZoom(): Number + // Returns the minimum zoom level of the map (if set in the `minZoom` option of the map or of any layers), or `0` by default. + getMinZoom: function () { + return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom; + }, + + // @method getMaxZoom(): Number + // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers). + getMaxZoom: function () { + return this.options.maxZoom === undefined ? + (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) : + this.options.maxZoom; + }, + + // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean): Number + // Returns the maximum zoom level on which the given bounds fit to the map + // view in its entirety. If `inside` (optional) is set to `true`, the method + // instead returns the minimum zoom level on which the map view fits into + // the given bounds in its entirety. + getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number + bounds = toLatLngBounds(bounds); + padding = toPoint(padding || [0, 0]); + + var zoom = this.getZoom() || 0, + min = this.getMinZoom(), + max = this.getMaxZoom(), + nw = bounds.getNorthWest(), + se = bounds.getSouthEast(), + size = this.getSize().subtract(padding), + boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).getSize(), + snap = any3d ? this.options.zoomSnap : 1, + scalex = size.x / boundsSize.x, + scaley = size.y / boundsSize.y, + scale = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley); + + zoom = this.getScaleZoom(scale, zoom); + + if (snap) { + zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level + zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap; + } + + return Math.max(min, Math.min(max, zoom)); + }, + + // @method getSize(): Point + // Returns the current size of the map container (in pixels). + getSize: function () { + if (!this._size || this._sizeChanged) { + this._size = new Point( + this._container.clientWidth || 0, + this._container.clientHeight || 0); + + this._sizeChanged = false; + } + return this._size.clone(); + }, + + // @method getPixelBounds(): Bounds + // Returns the bounds of the current map view in projected pixel + // coordinates (sometimes useful in layer and overlay implementations). + getPixelBounds: function (center, zoom) { + var topLeftPoint = this._getTopLeftPoint(center, zoom); + return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize())); + }, + + // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to + // the map pane? "left point of the map layer" can be confusing, specially + // since there can be negative offsets. + // @method getPixelOrigin(): Point + // Returns the projected pixel coordinates of the top left point of + // the map layer (useful in custom layer and overlay implementations). + getPixelOrigin: function () { + this._checkIfLoaded(); + return this._pixelOrigin; + }, + + // @method getPixelWorldBounds(zoom?: Number): Bounds + // Returns the world's bounds in pixel coordinates for zoom level `zoom`. + // If `zoom` is omitted, the map's current zoom level is used. + getPixelWorldBounds: function (zoom) { + return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom); + }, + + // @section Other Methods + + // @method getPane(pane: String|HTMLElement): HTMLElement + // Returns a [map pane](#map-pane), given its name or its HTML element (its identity). + getPane: function (pane) { + return typeof pane === 'string' ? this._panes[pane] : pane; + }, + + // @method getPanes(): Object + // Returns a plain object containing the names of all [panes](#map-pane) as keys and + // the panes as values. + getPanes: function () { + return this._panes; + }, + + // @method getContainer: HTMLElement + // Returns the HTML element that contains the map. + getContainer: function () { + return this._container; + }, + + + // @section Conversion Methods + + // @method getZoomScale(toZoom: Number, fromZoom: Number): Number + // Returns the scale factor to be applied to a map transition from zoom level + // `fromZoom` to `toZoom`. Used internally to help with zoom animations. + getZoomScale: function (toZoom, fromZoom) { + // TODO replace with universal implementation after refactoring projections + var crs = this.options.crs; + fromZoom = fromZoom === undefined ? this._zoom : fromZoom; + return crs.scale(toZoom) / crs.scale(fromZoom); + }, + + // @method getScaleZoom(scale: Number, fromZoom: Number): Number + // Returns the zoom level that the map would end up at, if it is at `fromZoom` + // level and everything is scaled by a factor of `scale`. Inverse of + // [`getZoomScale`](#map-getZoomScale). + getScaleZoom: function (scale, fromZoom) { + var crs = this.options.crs; + fromZoom = fromZoom === undefined ? this._zoom : fromZoom; + var zoom = crs.zoom(scale * crs.scale(fromZoom)); + return isNaN(zoom) ? Infinity : zoom; + }, + + // @method project(latlng: LatLng, zoom: Number): Point + // Projects a geographical coordinate `LatLng` according to the projection + // of the map's CRS, then scales it according to `zoom` and the CRS's + // `Transformation`. The result is pixel coordinate relative to + // the CRS origin. + project: function (latlng, zoom) { + zoom = zoom === undefined ? this._zoom : zoom; + return this.options.crs.latLngToPoint(toLatLng(latlng), zoom); + }, + + // @method unproject(point: Point, zoom: Number): LatLng + // Inverse of [`project`](#map-project). + unproject: function (point, zoom) { + zoom = zoom === undefined ? this._zoom : zoom; + return this.options.crs.pointToLatLng(toPoint(point), zoom); + }, + + // @method layerPointToLatLng(point: Point): LatLng + // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin), + // returns the corresponding geographical coordinate (for the current zoom level). + layerPointToLatLng: function (point) { + var projectedPoint = toPoint(point).add(this.getPixelOrigin()); + return this.unproject(projectedPoint); + }, + + // @method latLngToLayerPoint(latlng: LatLng): Point + // Given a geographical coordinate, returns the corresponding pixel coordinate + // relative to the [origin pixel](#map-getpixelorigin). + latLngToLayerPoint: function (latlng) { + var projectedPoint = this.project(toLatLng(latlng))._round(); + return projectedPoint._subtract(this.getPixelOrigin()); + }, + + // @method wrapLatLng(latlng: LatLng): LatLng + // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the + // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the + // CRS's bounds. + // By default this means longitude is wrapped around the dateline so its + // value is between -180 and +180 degrees. + wrapLatLng: function (latlng) { + return this.options.crs.wrapLatLng(toLatLng(latlng)); + }, + + // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds + // Returns a `LatLngBounds` with the same size as the given one, ensuring that + // its center is within the CRS's bounds. + // By default this means the center longitude is wrapped around the dateline so its + // value is between -180 and +180 degrees, and the majority of the bounds + // overlaps the CRS's bounds. + wrapLatLngBounds: function (latlng) { + return this.options.crs.wrapLatLngBounds(toLatLngBounds(latlng)); + }, + + // @method distance(latlng1: LatLng, latlng2: LatLng): Number + // Returns the distance between two geographical coordinates according to + // the map's CRS. By default this measures distance in meters. + distance: function (latlng1, latlng2) { + return this.options.crs.distance(toLatLng(latlng1), toLatLng(latlng2)); + }, + + // @method containerPointToLayerPoint(point: Point): Point + // Given a pixel coordinate relative to the map container, returns the corresponding + // pixel coordinate relative to the [origin pixel](#map-getpixelorigin). + containerPointToLayerPoint: function (point) { // (Point) + return toPoint(point).subtract(this._getMapPanePos()); + }, + + // @method layerPointToContainerPoint(point: Point): Point + // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin), + // returns the corresponding pixel coordinate relative to the map container. + layerPointToContainerPoint: function (point) { // (Point) + return toPoint(point).add(this._getMapPanePos()); + }, + + // @method containerPointToLatLng(point: Point): LatLng + // Given a pixel coordinate relative to the map container, returns + // the corresponding geographical coordinate (for the current zoom level). + containerPointToLatLng: function (point) { + var layerPoint = this.containerPointToLayerPoint(toPoint(point)); + return this.layerPointToLatLng(layerPoint); + }, + + // @method latLngToContainerPoint(latlng: LatLng): Point + // Given a geographical coordinate, returns the corresponding pixel coordinate + // relative to the map container. + latLngToContainerPoint: function (latlng) { + return this.layerPointToContainerPoint(this.latLngToLayerPoint(toLatLng(latlng))); + }, + + // @method mouseEventToContainerPoint(ev: MouseEvent): Point + // Given a MouseEvent object, returns the pixel coordinate relative to the + // map container where the event took place. + mouseEventToContainerPoint: function (e) { + return getMousePosition(e, this._container); + }, + + // @method mouseEventToLayerPoint(ev: MouseEvent): Point + // Given a MouseEvent object, returns the pixel coordinate relative to + // the [origin pixel](#map-getpixelorigin) where the event took place. + mouseEventToLayerPoint: function (e) { + return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e)); + }, + + // @method mouseEventToLatLng(ev: MouseEvent): LatLng + // Given a MouseEvent object, returns geographical coordinate where the + // event took place. + mouseEventToLatLng: function (e) { // (MouseEvent) + return this.layerPointToLatLng(this.mouseEventToLayerPoint(e)); + }, + + + // map initialization methods + + _initContainer: function (id) { + var container = this._container = get(id); + + if (!container) { + throw new Error('Map container not found.'); + } else if (container._leaflet_id) { + throw new Error('Map container is already initialized.'); + } + + on(container, 'scroll', this._onScroll, this); + this._containerId = stamp(container); + }, + + _initLayout: function () { + var container = this._container; + + this._fadeAnimated = this.options.fadeAnimation && any3d; + + addClass(container, 'leaflet-container' + + (touch ? ' leaflet-touch' : '') + + (retina ? ' leaflet-retina' : '') + + (ielt9 ? ' leaflet-oldie' : '') + + (safari ? ' leaflet-safari' : '') + + (this._fadeAnimated ? ' leaflet-fade-anim' : '')); + + var position = getStyle(container, 'position'); + + if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') { + container.style.position = 'relative'; + } + + this._initPanes(); + + if (this._initControlPos) { + this._initControlPos(); + } + }, + + _initPanes: function () { + var panes = this._panes = {}; + this._paneRenderers = {}; + + // @section + // + // Panes are DOM elements used to control the ordering of layers on the map. You + // can access panes with [`map.getPane`](#map-getpane) or + // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the + // [`map.createPane`](#map-createpane) method. + // + // Every map has the following default panes that differ only in zIndex. + // + // @pane mapPane: HTMLElement = 'auto' + // Pane that contains all other map panes + + this._mapPane = this.createPane('mapPane', this._container); + setPosition(this._mapPane, new Point(0, 0)); + + // @pane tilePane: HTMLElement = 200 + // Pane for `GridLayer`s and `TileLayer`s + this.createPane('tilePane'); + // @pane overlayPane: HTMLElement = 400 + // Pane for vectors (`Path`s, like `Polyline`s and `Polygon`s), `ImageOverlay`s and `VideoOverlay`s + this.createPane('shadowPane'); + // @pane shadowPane: HTMLElement = 500 + // Pane for overlay shadows (e.g. `Marker` shadows) + this.createPane('overlayPane'); + // @pane markerPane: HTMLElement = 600 + // Pane for `Icon`s of `Marker`s + this.createPane('markerPane'); + // @pane tooltipPane: HTMLElement = 650 + // Pane for `Tooltip`s. + this.createPane('tooltipPane'); + // @pane popupPane: HTMLElement = 700 + // Pane for `Popup`s. + this.createPane('popupPane'); + + if (!this.options.markerZoomAnimation) { + addClass(panes.markerPane, 'leaflet-zoom-hide'); + addClass(panes.shadowPane, 'leaflet-zoom-hide'); + } + }, + + + // private methods that modify map state + + // @section Map state change events + _resetView: function (center, zoom) { + setPosition(this._mapPane, new Point(0, 0)); + + var loading = !this._loaded; + this._loaded = true; + zoom = this._limitZoom(zoom); + + this.fire('viewprereset'); + + var zoomChanged = this._zoom !== zoom; + this + ._moveStart(zoomChanged, false) + ._move(center, zoom) + ._moveEnd(zoomChanged); + + // @event viewreset: Event + // Fired when the map needs to redraw its content (this usually happens + // on map zoom or load). Very useful for creating custom overlays. + this.fire('viewreset'); + + // @event load: Event + // Fired when the map is initialized (when its center and zoom are set + // for the first time). + if (loading) { + this.fire('load'); + } + }, + + _moveStart: function (zoomChanged, noMoveStart) { + // @event zoomstart: Event + // Fired when the map zoom is about to change (e.g. before zoom animation). + // @event movestart: Event + // Fired when the view of the map starts changing (e.g. user starts dragging the map). + if (zoomChanged) { + this.fire('zoomstart'); + } + if (!noMoveStart) { + this.fire('movestart'); + } + return this; + }, + + _move: function (center, zoom, data) { + if (zoom === undefined) { + zoom = this._zoom; + } + var zoomChanged = this._zoom !== zoom; + + this._zoom = zoom; + this._lastCenter = center; + this._pixelOrigin = this._getNewPixelOrigin(center); + + // @event zoom: Event + // Fired repeatedly during any change in zoom level, including zoom + // and fly animations. + if (zoomChanged || (data && data.pinch)) { // Always fire 'zoom' if pinching because #3530 + this.fire('zoom', data); + } + + // @event move: Event + // Fired repeatedly during any movement of the map, including pan and + // fly animations. + return this.fire('move', data); + }, + + _moveEnd: function (zoomChanged) { + // @event zoomend: Event + // Fired when the map has changed, after any animations. + if (zoomChanged) { + this.fire('zoomend'); + } + + // @event moveend: Event + // Fired when the center of the map stops changing (e.g. user stopped + // dragging the map). + return this.fire('moveend'); + }, + + _stop: function () { + cancelAnimFrame(this._flyToFrame); + if (this._panAnim) { + this._panAnim.stop(); + } + return this; + }, + + _rawPanBy: function (offset) { + setPosition(this._mapPane, this._getMapPanePos().subtract(offset)); + }, + + _getZoomSpan: function () { + return this.getMaxZoom() - this.getMinZoom(); + }, + + _panInsideMaxBounds: function () { + if (!this._enforcingBounds) { + this.panInsideBounds(this.options.maxBounds); + } + }, + + _checkIfLoaded: function () { + if (!this._loaded) { + throw new Error('Set map center and zoom first.'); + } + }, + + // DOM event handling + + // @section Interaction events + _initEvents: function (remove$$1) { + this._targets = {}; + this._targets[stamp(this._container)] = this; + + var onOff = remove$$1 ? off : on; + + // @event click: MouseEvent + // Fired when the user clicks (or taps) the map. + // @event dblclick: MouseEvent + // Fired when the user double-clicks (or double-taps) the map. + // @event mousedown: MouseEvent + // Fired when the user pushes the mouse button on the map. + // @event mouseup: MouseEvent + // Fired when the user releases the mouse button on the map. + // @event mouseover: MouseEvent + // Fired when the mouse enters the map. + // @event mouseout: MouseEvent + // Fired when the mouse leaves the map. + // @event mousemove: MouseEvent + // Fired while the mouse moves over the map. + // @event contextmenu: MouseEvent + // Fired when the user pushes the right mouse button on the map, prevents + // default browser context menu from showing if there are listeners on + // this event. Also fired on mobile when the user holds a single touch + // for a second (also called long press). + // @event keypress: KeyboardEvent + // Fired when the user presses a key from the keyboard while the map is focused. + onOff(this._container, 'click dblclick mousedown mouseup ' + + 'mouseover mouseout mousemove contextmenu keypress', this._handleDOMEvent, this); + + if (this.options.trackResize) { + onOff(window, 'resize', this._onResize, this); + } + + if (any3d && this.options.transform3DLimit) { + (remove$$1 ? this.off : this.on).call(this, 'moveend', this._onMoveEnd); + } + }, + + _onResize: function () { + cancelAnimFrame(this._resizeRequest); + this._resizeRequest = requestAnimFrame( + function () { this.invalidateSize({debounceMoveend: true}); }, this); + }, + + _onScroll: function () { + this._container.scrollTop = 0; + this._container.scrollLeft = 0; + }, + + _onMoveEnd: function () { + var pos = this._getMapPanePos(); + if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) { + // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have + // a pixel offset on very high values, see: http://jsfiddle.net/dg6r5hhb/ + this._resetView(this.getCenter(), this.getZoom()); + } + }, + + _findEventTargets: function (e, type) { + var targets = [], + target, + isHover = type === 'mouseout' || type === 'mouseover', + src = e.target || e.srcElement, + dragging = false; + + while (src) { + target = this._targets[stamp(src)]; + if (target && (type === 'click' || type === 'preclick') && !e._simulated && this._draggableMoved(target)) { + // Prevent firing click after you just dragged an object. + dragging = true; + break; + } + if (target && target.listens(type, true)) { + if (isHover && !isExternalTarget(src, e)) { break; } + targets.push(target); + if (isHover) { break; } + } + if (src === this._container) { break; } + src = src.parentNode; + } + if (!targets.length && !dragging && !isHover && isExternalTarget(src, e)) { + targets = [this]; + } + return targets; + }, + + _handleDOMEvent: function (e) { + if (!this._loaded || skipped(e)) { return; } + + var type = e.type; + + if (type === 'mousedown' || type === 'keypress') { + // prevents outline when clicking on keyboard-focusable element + preventOutline(e.target || e.srcElement); + } + + this._fireDOMEvent(e, type); + }, + + _mouseEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu'], + + _fireDOMEvent: function (e, type, targets) { + + if (e.type === 'click') { + // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups). + // @event preclick: MouseEvent + // Fired before mouse click on the map (sometimes useful when you + // want something to happen on click before any existing click + // handlers start running). + var synth = extend({}, e); + synth.type = 'preclick'; + this._fireDOMEvent(synth, synth.type, targets); + } + + if (e._stopped) { return; } + + // Find the layer the event is propagating from and its parents. + targets = (targets || []).concat(this._findEventTargets(e, type)); + + if (!targets.length) { return; } + + var target = targets[0]; + if (type === 'contextmenu' && target.listens(type, true)) { + preventDefault(e); + } + + var data = { + originalEvent: e + }; + + if (e.type !== 'keypress') { + var isMarker = target.getLatLng && (!target._radius || target._radius <= 10); + data.containerPoint = isMarker ? + this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e); + data.layerPoint = this.containerPointToLayerPoint(data.containerPoint); + data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint); + } + + for (var i = 0; i < targets.length; i++) { + targets[i].fire(type, data, true); + if (data.originalEvent._stopped || + (targets[i].options.bubblingMouseEvents === false && indexOf(this._mouseEvents, type) !== -1)) { return; } + } + }, + + _draggableMoved: function (obj) { + obj = obj.dragging && obj.dragging.enabled() ? obj : this; + return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved()); + }, + + _clearHandlers: function () { + for (var i = 0, len = this._handlers.length; i < len; i++) { + this._handlers[i].disable(); + } + }, + + // @section Other Methods + + // @method whenReady(fn: Function, context?: Object): this + // Runs the given function `fn` when the map gets initialized with + // a view (center and zoom) and at least one layer, or immediately + // if it's already initialized, optionally passing a function context. + whenReady: function (callback, context) { + if (this._loaded) { + callback.call(context || this, {target: this}); + } else { + this.on('load', callback, context); + } + return this; + }, + + + // private methods for getting map state + + _getMapPanePos: function () { + return getPosition(this._mapPane) || new Point(0, 0); + }, + + _moved: function () { + var pos = this._getMapPanePos(); + return pos && !pos.equals([0, 0]); + }, + + _getTopLeftPoint: function (center, zoom) { + var pixelOrigin = center && zoom !== undefined ? + this._getNewPixelOrigin(center, zoom) : + this.getPixelOrigin(); + return pixelOrigin.subtract(this._getMapPanePos()); + }, + + _getNewPixelOrigin: function (center, zoom) { + var viewHalf = this.getSize()._divideBy(2); + return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round(); + }, + + _latLngToNewLayerPoint: function (latlng, zoom, center) { + var topLeft = this._getNewPixelOrigin(center, zoom); + return this.project(latlng, zoom)._subtract(topLeft); + }, + + _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) { + var topLeft = this._getNewPixelOrigin(center, zoom); + return toBounds([ + this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft), + this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft), + this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft), + this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft) + ]); + }, + + // layer point of the current center + _getCenterLayerPoint: function () { + return this.containerPointToLayerPoint(this.getSize()._divideBy(2)); + }, + + // offset of the specified place to the current center in pixels + _getCenterOffset: function (latlng) { + return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint()); + }, + + // adjust center for view to get inside bounds + _limitCenter: function (center, zoom, bounds) { + + if (!bounds) { return center; } + + var centerPoint = this.project(center, zoom), + viewHalf = this.getSize().divideBy(2), + viewBounds = new Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)), + offset = this._getBoundsOffset(viewBounds, bounds, zoom); + + // If offset is less than a pixel, ignore. + // This prevents unstable projections from getting into + // an infinite loop of tiny offsets. + if (offset.round().equals([0, 0])) { + return center; + } + + return this.unproject(centerPoint.add(offset), zoom); + }, + + // adjust offset for view to get inside bounds + _limitOffset: function (offset, bounds) { + if (!bounds) { return offset; } + + var viewBounds = this.getPixelBounds(), + newBounds = new Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset)); + + return offset.add(this._getBoundsOffset(newBounds, bounds)); + }, + + // returns offset needed for pxBounds to get inside maxBounds at a specified zoom + _getBoundsOffset: function (pxBounds, maxBounds, zoom) { + var projectedMaxBounds = toBounds( + this.project(maxBounds.getNorthEast(), zoom), + this.project(maxBounds.getSouthWest(), zoom) + ), + minOffset = projectedMaxBounds.min.subtract(pxBounds.min), + maxOffset = projectedMaxBounds.max.subtract(pxBounds.max), + + dx = this._rebound(minOffset.x, -maxOffset.x), + dy = this._rebound(minOffset.y, -maxOffset.y); + + return new Point(dx, dy); + }, + + _rebound: function (left, right) { + return left + right > 0 ? + Math.round(left - right) / 2 : + Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right)); + }, + + _limitZoom: function (zoom) { + var min = this.getMinZoom(), + max = this.getMaxZoom(), + snap = any3d ? this.options.zoomSnap : 1; + if (snap) { + zoom = Math.round(zoom / snap) * snap; + } + return Math.max(min, Math.min(max, zoom)); + }, + + _onPanTransitionStep: function () { + this.fire('move'); + }, + + _onPanTransitionEnd: function () { + removeClass(this._mapPane, 'leaflet-pan-anim'); + this.fire('moveend'); + }, + + _tryAnimatedPan: function (center, options) { + // difference between the new and current centers in pixels + var offset = this._getCenterOffset(center)._trunc(); + + // don't animate too far unless animate: true specified in options + if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; } + + this.panBy(offset, options); + + return true; + }, + + _createAnimProxy: function () { + + var proxy = this._proxy = create$1('div', 'leaflet-proxy leaflet-zoom-animated'); + this._panes.mapPane.appendChild(proxy); + + this.on('zoomanim', function (e) { + var prop = TRANSFORM, + transform = this._proxy.style[prop]; + + setTransform(this._proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1)); + + // workaround for case when transform is the same and so transitionend event is not fired + if (transform === this._proxy.style[prop] && this._animatingZoom) { + this._onZoomTransitionEnd(); + } + }, this); + + this.on('load moveend', function () { + var c = this.getCenter(), + z = this.getZoom(); + setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1)); + }, this); + + this._on('unload', this._destroyAnimProxy, this); + }, + + _destroyAnimProxy: function () { + remove(this._proxy); + delete this._proxy; + }, + + _catchTransitionEnd: function (e) { + if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) { + this._onZoomTransitionEnd(); + } + }, + + _nothingToAnimate: function () { + return !this._container.getElementsByClassName('leaflet-zoom-animated').length; + }, + + _tryAnimatedZoom: function (center, zoom, options) { + + if (this._animatingZoom) { return true; } + + options = options || {}; + + // don't animate if disabled, not supported or zoom difference is too large + if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() || + Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; } + + // offset is the pixel coords of the zoom origin relative to the current center + var scale = this.getZoomScale(zoom), + offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale); + + // don't animate if the zoom origin isn't within one screen from the current center, unless forced + if (options.animate !== true && !this.getSize().contains(offset)) { return false; } + + requestAnimFrame(function () { + this + ._moveStart(true, false) + ._animateZoom(center, zoom, true); + }, this); + + return true; + }, + + _animateZoom: function (center, zoom, startAnim, noUpdate) { + if (!this._mapPane) { return; } + + if (startAnim) { + this._animatingZoom = true; + + // remember what center/zoom to set after animation + this._animateToCenter = center; + this._animateToZoom = zoom; + + addClass(this._mapPane, 'leaflet-zoom-anim'); + } + + // @event zoomanim: ZoomAnimEvent + // Fired on every frame of a zoom animation + this.fire('zoomanim', { + center: center, + zoom: zoom, + noUpdate: noUpdate + }); + + // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693 + setTimeout(bind(this._onZoomTransitionEnd, this), 250); + }, + + _onZoomTransitionEnd: function () { + if (!this._animatingZoom) { return; } + + if (this._mapPane) { + removeClass(this._mapPane, 'leaflet-zoom-anim'); + } + + this._animatingZoom = false; + + this._move(this._animateToCenter, this._animateToZoom); + + // This anim frame should prevent an obscure iOS webkit tile loading race condition. + requestAnimFrame(function () { + this._moveEnd(true); + }, this); + } +}); + +// @section + +// @factory L.map(id: String, options?: Map options) +// Instantiates a map object given the DOM ID of a `
` element +// and optionally an object literal with `Map options`. +// +// @alternative +// @factory L.map(el: HTMLElement, options?: Map options) +// Instantiates a map object given an instance of a `
` HTML element +// and optionally an object literal with `Map options`. +function createMap(id, options) { + return new Map(id, options); +} + +/* + * @class Control + * @aka L.Control + * @inherits Class + * + * L.Control is a base class for implementing map controls. Handles positioning. + * All other controls extend from this class. + */ + +var Control = Class.extend({ + // @section + // @aka Control options + options: { + // @option position: String = 'topright' + // The position of the control (one of the map corners). Possible values are `'topleft'`, + // `'topright'`, `'bottomleft'` or `'bottomright'` + position: 'topright' + }, + + initialize: function (options) { + setOptions(this, options); + }, + + /* @section + * Classes extending L.Control will inherit the following methods: + * + * @method getPosition: string + * Returns the position of the control. + */ + getPosition: function () { + return this.options.position; + }, + + // @method setPosition(position: string): this + // Sets the position of the control. + setPosition: function (position) { + var map = this._map; + + if (map) { + map.removeControl(this); + } + + this.options.position = position; + + if (map) { + map.addControl(this); + } + + return this; + }, + + // @method getContainer: HTMLElement + // Returns the HTMLElement that contains the control. + getContainer: function () { + return this._container; + }, + + // @method addTo(map: Map): this + // Adds the control to the given map. + addTo: function (map) { + this.remove(); + this._map = map; + + var container = this._container = this.onAdd(map), + pos = this.getPosition(), + corner = map._controlCorners[pos]; + + addClass(container, 'leaflet-control'); + + if (pos.indexOf('bottom') !== -1) { + corner.insertBefore(container, corner.firstChild); + } else { + corner.appendChild(container); + } + + return this; + }, + + // @method remove: this + // Removes the control from the map it is currently active on. + remove: function () { + if (!this._map) { + return this; + } + + remove(this._container); + + if (this.onRemove) { + this.onRemove(this._map); + } + + this._map = null; + + return this; + }, + + _refocusOnMap: function (e) { + // if map exists and event is not a keyboard event + if (this._map && e && e.screenX > 0 && e.screenY > 0) { + this._map.getContainer().focus(); + } + } +}); + +var control = function (options) { + return new Control(options); +}; + +/* @section Extension methods + * @uninheritable + * + * Every control should extend from `L.Control` and (re-)implement the following methods. + * + * @method onAdd(map: Map): HTMLElement + * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo). + * + * @method onRemove(map: Map) + * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove). + */ + +/* @namespace Map + * @section Methods for Layers and Controls + */ +Map.include({ + // @method addControl(control: Control): this + // Adds the given control to the map + addControl: function (control) { + control.addTo(this); + return this; + }, + + // @method removeControl(control: Control): this + // Removes the given control from the map + removeControl: function (control) { + control.remove(); + return this; + }, + + _initControlPos: function () { + var corners = this._controlCorners = {}, + l = 'leaflet-', + container = this._controlContainer = + create$1('div', l + 'control-container', this._container); + + function createCorner(vSide, hSide) { + var className = l + vSide + ' ' + l + hSide; + + corners[vSide + hSide] = create$1('div', className, container); + } + + createCorner('top', 'left'); + createCorner('top', 'right'); + createCorner('bottom', 'left'); + createCorner('bottom', 'right'); + }, + + _clearControlPos: function () { + for (var i in this._controlCorners) { + remove(this._controlCorners[i]); + } + remove(this._controlContainer); + delete this._controlCorners; + delete this._controlContainer; + } +}); + +/* + * @class Control.Layers + * @aka L.Control.Layers + * @inherits Control + * + * The layers control gives users the ability to switch between different base layers and switch overlays on/off (check out the [detailed example](http://leafletjs.com/examples/layers-control/)). Extends `Control`. + * + * @example + * + * ```js + * var baseLayers = { + * "Mapbox": mapbox, + * "OpenStreetMap": osm + * }; + * + * var overlays = { + * "Marker": marker, + * "Roads": roadsLayer + * }; + * + * L.control.layers(baseLayers, overlays).addTo(map); + * ``` + * + * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values: + * + * ```js + * { + * "": layer1, + * "": layer2 + * } + * ``` + * + * The layer names can contain HTML, which allows you to add additional styling to the items: + * + * ```js + * {" My Layer": myLayer} + * ``` + */ + +var Layers = Control.extend({ + // @section + // @aka Control.Layers options + options: { + // @option collapsed: Boolean = true + // If `true`, the control will be collapsed into an icon and expanded on mouse hover or touch. + collapsed: true, + position: 'topright', + + // @option autoZIndex: Boolean = true + // If `true`, the control will assign zIndexes in increasing order to all of its layers so that the order is preserved when switching them on/off. + autoZIndex: true, + + // @option hideSingleBase: Boolean = false + // If `true`, the base layers in the control will be hidden when there is only one. + hideSingleBase: false, + + // @option sortLayers: Boolean = false + // Whether to sort the layers. When `false`, layers will keep the order + // in which they were added to the control. + sortLayers: false, + + // @option sortFunction: Function = * + // A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) + // that will be used for sorting the layers, when `sortLayers` is `true`. + // The function receives both the `L.Layer` instances and their names, as in + // `sortFunction(layerA, layerB, nameA, nameB)`. + // By default, it sorts layers alphabetically by their name. + sortFunction: function (layerA, layerB, nameA, nameB) { + return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0); + } + }, + + initialize: function (baseLayers, overlays, options) { + setOptions(this, options); + + this._layerControlInputs = []; + this._layers = []; + this._lastZIndex = 0; + this._handlingClick = false; + + for (var i in baseLayers) { + this._addLayer(baseLayers[i], i); + } + + for (i in overlays) { + this._addLayer(overlays[i], i, true); + } + }, + + onAdd: function (map) { + this._initLayout(); + this._update(); + + this._map = map; + map.on('zoomend', this._checkDisabledLayers, this); + + for (var i = 0; i < this._layers.length; i++) { + this._layers[i].layer.on('add remove', this._onLayerChange, this); + } + + return this._container; + }, + + addTo: function (map) { + Control.prototype.addTo.call(this, map); + // Trigger expand after Layers Control has been inserted into DOM so that is now has an actual height. + return this._expandIfNotCollapsed(); + }, + + onRemove: function () { + this._map.off('zoomend', this._checkDisabledLayers, this); + + for (var i = 0; i < this._layers.length; i++) { + this._layers[i].layer.off('add remove', this._onLayerChange, this); + } + }, + + // @method addBaseLayer(layer: Layer, name: String): this + // Adds a base layer (radio button entry) with the given name to the control. + addBaseLayer: function (layer, name) { + this._addLayer(layer, name); + return (this._map) ? this._update() : this; + }, + + // @method addOverlay(layer: Layer, name: String): this + // Adds an overlay (checkbox entry) with the given name to the control. + addOverlay: function (layer, name) { + this._addLayer(layer, name, true); + return (this._map) ? this._update() : this; + }, + + // @method removeLayer(layer: Layer): this + // Remove the given layer from the control. + removeLayer: function (layer) { + layer.off('add remove', this._onLayerChange, this); + + var obj = this._getLayer(stamp(layer)); + if (obj) { + this._layers.splice(this._layers.indexOf(obj), 1); + } + return (this._map) ? this._update() : this; + }, + + // @method expand(): this + // Expand the control container if collapsed. + expand: function () { + addClass(this._container, 'leaflet-control-layers-expanded'); + this._form.style.height = null; + var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50); + if (acceptableHeight < this._form.clientHeight) { + addClass(this._form, 'leaflet-control-layers-scrollbar'); + this._form.style.height = acceptableHeight + 'px'; + } else { + removeClass(this._form, 'leaflet-control-layers-scrollbar'); + } + this._checkDisabledLayers(); + return this; + }, + + // @method collapse(): this + // Collapse the control container if expanded. + collapse: function () { + removeClass(this._container, 'leaflet-control-layers-expanded'); + return this; + }, + + _initLayout: function () { + var className = 'leaflet-control-layers', + container = this._container = create$1('div', className), + collapsed = this.options.collapsed; + + // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released + container.setAttribute('aria-haspopup', true); + + disableClickPropagation(container); + disableScrollPropagation(container); + + var form = this._form = create$1('form', className + '-list'); + + if (collapsed) { + this._map.on('click', this.collapse, this); + + if (!android) { + on(container, { + mouseenter: this.expand, + mouseleave: this.collapse + }, this); + } + } + + var link = this._layersLink = create$1('a', className + '-toggle', container); + link.href = '#'; + link.title = 'Layers'; + + if (touch) { + on(link, 'click', stop); + on(link, 'click', this.expand, this); + } else { + on(link, 'focus', this.expand, this); + } + + if (!collapsed) { + this.expand(); + } + + this._baseLayersList = create$1('div', className + '-base', form); + this._separator = create$1('div', className + '-separator', form); + this._overlaysList = create$1('div', className + '-overlays', form); + + container.appendChild(form); + }, + + _getLayer: function (id) { + for (var i = 0; i < this._layers.length; i++) { + + if (this._layers[i] && stamp(this._layers[i].layer) === id) { + return this._layers[i]; + } + } + }, + + _addLayer: function (layer, name, overlay) { + if (this._map) { + layer.on('add remove', this._onLayerChange, this); + } + + this._layers.push({ + layer: layer, + name: name, + overlay: overlay + }); + + if (this.options.sortLayers) { + this._layers.sort(bind(function (a, b) { + return this.options.sortFunction(a.layer, b.layer, a.name, b.name); + }, this)); + } + + if (this.options.autoZIndex && layer.setZIndex) { + this._lastZIndex++; + layer.setZIndex(this._lastZIndex); + } + + this._expandIfNotCollapsed(); + }, + + _update: function () { + if (!this._container) { return this; } + + empty(this._baseLayersList); + empty(this._overlaysList); + + this._layerControlInputs = []; + var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0; + + for (i = 0; i < this._layers.length; i++) { + obj = this._layers[i]; + this._addItem(obj); + overlaysPresent = overlaysPresent || obj.overlay; + baseLayersPresent = baseLayersPresent || !obj.overlay; + baseLayersCount += !obj.overlay ? 1 : 0; + } + + // Hide base layers section if there's only one layer. + if (this.options.hideSingleBase) { + baseLayersPresent = baseLayersPresent && baseLayersCount > 1; + this._baseLayersList.style.display = baseLayersPresent ? '' : 'none'; + } + + this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none'; + + return this; + }, + + _onLayerChange: function (e) { + if (!this._handlingClick) { + this._update(); + } + + var obj = this._getLayer(stamp(e.target)); + + // @namespace Map + // @section Layer events + // @event baselayerchange: LayersControlEvent + // Fired when the base layer is changed through the [layer control](#control-layers). + // @event overlayadd: LayersControlEvent + // Fired when an overlay is selected through the [layer control](#control-layers). + // @event overlayremove: LayersControlEvent + // Fired when an overlay is deselected through the [layer control](#control-layers). + // @namespace Control.Layers + var type = obj.overlay ? + (e.type === 'add' ? 'overlayadd' : 'overlayremove') : + (e.type === 'add' ? 'baselayerchange' : null); + + if (type) { + this._map.fire(type, obj); + } + }, + + // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe) + _createRadioElement: function (name, checked) { + + var radioHtml = ''; + + var radioFragment = document.createElement('div'); + radioFragment.innerHTML = radioHtml; + + return radioFragment.firstChild; + }, + + _addItem: function (obj) { + var label = document.createElement('label'), + checked = this._map.hasLayer(obj.layer), + input; + + if (obj.overlay) { + input = document.createElement('input'); + input.type = 'checkbox'; + input.className = 'leaflet-control-layers-selector'; + input.defaultChecked = checked; + } else { + input = this._createRadioElement('leaflet-base-layers', checked); + } + + this._layerControlInputs.push(input); + input.layerId = stamp(obj.layer); + + on(input, 'click', this._onInputClick, this); + + var name = document.createElement('span'); + name.innerHTML = ' ' + obj.name; + + // Helps from preventing layer control flicker when checkboxes are disabled + // https://github.com/Leaflet/Leaflet/issues/2771 + var holder = document.createElement('div'); + + label.appendChild(holder); + holder.appendChild(input); + holder.appendChild(name); + + var container = obj.overlay ? this._overlaysList : this._baseLayersList; + container.appendChild(label); + + this._checkDisabledLayers(); + return label; + }, + + _onInputClick: function () { + var inputs = this._layerControlInputs, + input, layer; + var addedLayers = [], + removedLayers = []; + + this._handlingClick = true; + + for (var i = inputs.length - 1; i >= 0; i--) { + input = inputs[i]; + layer = this._getLayer(input.layerId).layer; + + if (input.checked) { + addedLayers.push(layer); + } else if (!input.checked) { + removedLayers.push(layer); + } + } + + // Bugfix issue 2318: Should remove all old layers before readding new ones + for (i = 0; i < removedLayers.length; i++) { + if (this._map.hasLayer(removedLayers[i])) { + this._map.removeLayer(removedLayers[i]); + } + } + for (i = 0; i < addedLayers.length; i++) { + if (!this._map.hasLayer(addedLayers[i])) { + this._map.addLayer(addedLayers[i]); + } + } + + this._handlingClick = false; + + this._refocusOnMap(); + }, + + _checkDisabledLayers: function () { + var inputs = this._layerControlInputs, + input, + layer, + zoom = this._map.getZoom(); + + for (var i = inputs.length - 1; i >= 0; i--) { + input = inputs[i]; + layer = this._getLayer(input.layerId).layer; + input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) || + (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom); + + } + }, + + _expandIfNotCollapsed: function () { + if (this._map && !this.options.collapsed) { + this.expand(); + } + return this; + }, + + _expand: function () { + // Backward compatibility, remove me in 1.1. + return this.expand(); + }, + + _collapse: function () { + // Backward compatibility, remove me in 1.1. + return this.collapse(); + } + +}); + + +// @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options) +// Creates an attribution control with the given layers. Base layers will be switched with radio buttons, while overlays will be switched with checkboxes. Note that all base layers should be passed in the base layers object, but only one should be added to the map during map instantiation. +var layers = function (baseLayers, overlays, options) { + return new Layers(baseLayers, overlays, options); +}; + +/* + * @class Control.Zoom + * @aka L.Control.Zoom + * @inherits Control + * + * A basic zoom control with two buttons (zoom in and zoom out). It is put on the map by default unless you set its [`zoomControl` option](#map-zoomcontrol) to `false`. Extends `Control`. + */ + +var Zoom = Control.extend({ + // @section + // @aka Control.Zoom options + options: { + position: 'topleft', + + // @option zoomInText: String = '+' + // The text set on the 'zoom in' button. + zoomInText: '+', + + // @option zoomInTitle: String = 'Zoom in' + // The title set on the 'zoom in' button. + zoomInTitle: 'Zoom in', + + // @option zoomOutText: String = '−' + // The text set on the 'zoom out' button. + zoomOutText: '−', + + // @option zoomOutTitle: String = 'Zoom out' + // The title set on the 'zoom out' button. + zoomOutTitle: 'Zoom out' + }, + + onAdd: function (map) { + var zoomName = 'leaflet-control-zoom', + container = create$1('div', zoomName + ' leaflet-bar'), + options = this.options; + + this._zoomInButton = this._createButton(options.zoomInText, options.zoomInTitle, + zoomName + '-in', container, this._zoomIn); + this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle, + zoomName + '-out', container, this._zoomOut); + + this._updateDisabled(); + map.on('zoomend zoomlevelschange', this._updateDisabled, this); + + return container; + }, + + onRemove: function (map) { + map.off('zoomend zoomlevelschange', this._updateDisabled, this); + }, + + disable: function () { + this._disabled = true; + this._updateDisabled(); + return this; + }, + + enable: function () { + this._disabled = false; + this._updateDisabled(); + return this; + }, + + _zoomIn: function (e) { + if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) { + this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1)); + } + }, + + _zoomOut: function (e) { + if (!this._disabled && this._map._zoom > this._map.getMinZoom()) { + this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1)); + } + }, + + _createButton: function (html, title, className, container, fn) { + var link = create$1('a', className, container); + link.innerHTML = html; + link.href = '#'; + link.title = title; + + /* + * Will force screen readers like VoiceOver to read this as "Zoom in - button" + */ + link.setAttribute('role', 'button'); + link.setAttribute('aria-label', title); + + disableClickPropagation(link); + on(link, 'click', stop); + on(link, 'click', fn, this); + on(link, 'click', this._refocusOnMap, this); + + return link; + }, + + _updateDisabled: function () { + var map = this._map, + className = 'leaflet-disabled'; + + removeClass(this._zoomInButton, className); + removeClass(this._zoomOutButton, className); + + if (this._disabled || map._zoom === map.getMinZoom()) { + addClass(this._zoomOutButton, className); + } + if (this._disabled || map._zoom === map.getMaxZoom()) { + addClass(this._zoomInButton, className); + } + } +}); + +// @namespace Map +// @section Control options +// @option zoomControl: Boolean = true +// Whether a [zoom control](#control-zoom) is added to the map by default. +Map.mergeOptions({ + zoomControl: true +}); + +Map.addInitHook(function () { + if (this.options.zoomControl) { + this.zoomControl = new Zoom(); + this.addControl(this.zoomControl); + } +}); + +// @namespace Control.Zoom +// @factory L.control.zoom(options: Control.Zoom options) +// Creates a zoom control +var zoom = function (options) { + return new Zoom(options); +}; + +/* + * @class Control.Scale + * @aka L.Control.Scale + * @inherits Control + * + * A simple scale control that shows the scale of the current center of screen in metric (m/km) and imperial (mi/ft) systems. Extends `Control`. + * + * @example + * + * ```js + * L.control.scale().addTo(map); + * ``` + */ + +var Scale = Control.extend({ + // @section + // @aka Control.Scale options + options: { + position: 'bottomleft', + + // @option maxWidth: Number = 100 + // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500). + maxWidth: 100, + + // @option metric: Boolean = True + // Whether to show the metric scale line (m/km). + metric: true, + + // @option imperial: Boolean = True + // Whether to show the imperial scale line (mi/ft). + imperial: true + + // @option updateWhenIdle: Boolean = false + // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)). + }, + + onAdd: function (map) { + var className = 'leaflet-control-scale', + container = create$1('div', className), + options = this.options; + + this._addScales(options, className + '-line', container); + + map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this); + map.whenReady(this._update, this); + + return container; + }, + + onRemove: function (map) { + map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this); + }, + + _addScales: function (options, className, container) { + if (options.metric) { + this._mScale = create$1('div', className, container); + } + if (options.imperial) { + this._iScale = create$1('div', className, container); + } + }, + + _update: function () { + var map = this._map, + y = map.getSize().y / 2; + + var maxMeters = map.distance( + map.containerPointToLatLng([0, y]), + map.containerPointToLatLng([this.options.maxWidth, y])); + + this._updateScales(maxMeters); + }, + + _updateScales: function (maxMeters) { + if (this.options.metric && maxMeters) { + this._updateMetric(maxMeters); + } + if (this.options.imperial && maxMeters) { + this._updateImperial(maxMeters); + } + }, + + _updateMetric: function (maxMeters) { + var meters = this._getRoundNum(maxMeters), + label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km'; + + this._updateScale(this._mScale, label, meters / maxMeters); + }, + + _updateImperial: function (maxMeters) { + var maxFeet = maxMeters * 3.2808399, + maxMiles, miles, feet; + + if (maxFeet > 5280) { + maxMiles = maxFeet / 5280; + miles = this._getRoundNum(maxMiles); + this._updateScale(this._iScale, miles + ' mi', miles / maxMiles); + + } else { + feet = this._getRoundNum(maxFeet); + this._updateScale(this._iScale, feet + ' ft', feet / maxFeet); + } + }, + + _updateScale: function (scale, text, ratio) { + scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px'; + scale.innerHTML = text; + }, + + _getRoundNum: function (num) { + var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1), + d = num / pow10; + + d = d >= 10 ? 10 : + d >= 5 ? 5 : + d >= 3 ? 3 : + d >= 2 ? 2 : 1; + + return pow10 * d; + } +}); + + +// @factory L.control.scale(options?: Control.Scale options) +// Creates an scale control with the given options. +var scale = function (options) { + return new Scale(options); +}; + +/* + * @class Control.Attribution + * @aka L.Control.Attribution + * @inherits Control + * + * The attribution control allows you to display attribution data in a small text box on a map. It is put on the map by default unless you set its [`attributionControl` option](#map-attributioncontrol) to `false`, and it fetches attribution texts from layers with the [`getAttribution` method](#layer-getattribution) automatically. Extends Control. + */ + +var Attribution = Control.extend({ + // @section + // @aka Control.Attribution options + options: { + position: 'bottomright', + + // @option prefix: String = 'Leaflet' + // The HTML text shown before the attributions. Pass `false` to disable. + prefix: 'Leaflet' + }, + + initialize: function (options) { + setOptions(this, options); + + this._attributions = {}; + }, + + onAdd: function (map) { + map.attributionControl = this; + this._container = create$1('div', 'leaflet-control-attribution'); + disableClickPropagation(this._container); + + // TODO ugly, refactor + for (var i in map._layers) { + if (map._layers[i].getAttribution) { + this.addAttribution(map._layers[i].getAttribution()); + } + } + + this._update(); + + return this._container; + }, + + // @method setPrefix(prefix: String): this + // Sets the text before the attributions. + setPrefix: function (prefix) { + this.options.prefix = prefix; + this._update(); + return this; + }, + + // @method addAttribution(text: String): this + // Adds an attribution text (e.g. `'Vector data © Mapbox'`). + addAttribution: function (text) { + if (!text) { return this; } + + if (!this._attributions[text]) { + this._attributions[text] = 0; + } + this._attributions[text]++; + + this._update(); + + return this; + }, + + // @method removeAttribution(text: String): this + // Removes an attribution text. + removeAttribution: function (text) { + if (!text) { return this; } + + if (this._attributions[text]) { + this._attributions[text]--; + this._update(); + } + + return this; + }, + + _update: function () { + if (!this._map) { return; } + + var attribs = []; + + for (var i in this._attributions) { + if (this._attributions[i]) { + attribs.push(i); + } + } + + var prefixAndAttribs = []; + + if (this.options.prefix) { + prefixAndAttribs.push(this.options.prefix); + } + if (attribs.length) { + prefixAndAttribs.push(attribs.join(', ')); + } + + this._container.innerHTML = prefixAndAttribs.join(' | '); + } +}); + +// @namespace Map +// @section Control options +// @option attributionControl: Boolean = true +// Whether a [attribution control](#control-attribution) is added to the map by default. +Map.mergeOptions({ + attributionControl: true +}); + +Map.addInitHook(function () { + if (this.options.attributionControl) { + new Attribution().addTo(this); + } +}); + +// @namespace Control.Attribution +// @factory L.control.attribution(options: Control.Attribution options) +// Creates an attribution control. +var attribution = function (options) { + return new Attribution(options); +}; + +Control.Layers = Layers; +Control.Zoom = Zoom; +Control.Scale = Scale; +Control.Attribution = Attribution; + +control.layers = layers; +control.zoom = zoom; +control.scale = scale; +control.attribution = attribution; + +/* + L.Handler is a base class for handler classes that are used internally to inject + interaction features like dragging to classes like Map and Marker. +*/ + +// @class Handler +// @aka L.Handler +// Abstract class for map interaction handlers + +var Handler = Class.extend({ + initialize: function (map) { + this._map = map; + }, + + // @method enable(): this + // Enables the handler + enable: function () { + if (this._enabled) { return this; } + + this._enabled = true; + this.addHooks(); + return this; + }, + + // @method disable(): this + // Disables the handler + disable: function () { + if (!this._enabled) { return this; } + + this._enabled = false; + this.removeHooks(); + return this; + }, + + // @method enabled(): Boolean + // Returns `true` if the handler is enabled + enabled: function () { + return !!this._enabled; + } + + // @section Extension methods + // Classes inheriting from `Handler` must implement the two following methods: + // @method addHooks() + // Called when the handler is enabled, should add event hooks. + // @method removeHooks() + // Called when the handler is disabled, should remove the event hooks added previously. +}); + +// @section There is static function which can be called without instantiating L.Handler: +// @function addTo(map: Map, name: String): this +// Adds a new Handler to the given map with the given name. +Handler.addTo = function (map, name) { + map.addHandler(name, this); + return this; +}; + +var Mixin = {Events: Events}; + +/* + * @class Draggable + * @aka L.Draggable + * @inherits Evented + * + * A class for making DOM elements draggable (including touch support). + * Used internally for map and marker dragging. Only works for elements + * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition). + * + * @example + * ```js + * var draggable = new L.Draggable(elementToDrag); + * draggable.enable(); + * ``` + */ + +var START = touch ? 'touchstart mousedown' : 'mousedown'; +var END = { + mousedown: 'mouseup', + touchstart: 'touchend', + pointerdown: 'touchend', + MSPointerDown: 'touchend' +}; +var MOVE = { + mousedown: 'mousemove', + touchstart: 'touchmove', + pointerdown: 'touchmove', + MSPointerDown: 'touchmove' +}; + + +var Draggable = Evented.extend({ + + options: { + // @section + // @aka Draggable options + // @option clickTolerance: Number = 3 + // The max number of pixels a user can shift the mouse pointer during a click + // for it to be considered a valid click (as opposed to a mouse drag). + clickTolerance: 3 + }, + + // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline?: Boolean, options?: Draggable options) + // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default). + initialize: function (element, dragStartTarget, preventOutline$$1, options) { + setOptions(this, options); + + this._element = element; + this._dragStartTarget = dragStartTarget || element; + this._preventOutline = preventOutline$$1; + }, + + // @method enable() + // Enables the dragging ability + enable: function () { + if (this._enabled) { return; } + + on(this._dragStartTarget, START, this._onDown, this); + + this._enabled = true; + }, + + // @method disable() + // Disables the dragging ability + disable: function () { + if (!this._enabled) { return; } + + // If we're currently dragging this draggable, + // disabling it counts as first ending the drag. + if (Draggable._dragging === this) { + this.finishDrag(); + } + + off(this._dragStartTarget, START, this._onDown, this); + + this._enabled = false; + this._moved = false; + }, + + _onDown: function (e) { + // Ignore simulated events, since we handle both touch and + // mouse explicitly; otherwise we risk getting duplicates of + // touch events, see #4315. + // Also ignore the event if disabled; this happens in IE11 + // under some circumstances, see #3666. + if (e._simulated || !this._enabled) { return; } + + this._moved = false; + + if (hasClass(this._element, 'leaflet-zoom-anim')) { return; } + + if (Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; } + Draggable._dragging = this; // Prevent dragging multiple objects at once. + + if (this._preventOutline) { + preventOutline(this._element); + } + + disableImageDrag(); + disableTextSelection(); + + if (this._moving) { return; } + + // @event down: Event + // Fired when a drag is about to start. + this.fire('down'); + + var first = e.touches ? e.touches[0] : e; + + this._startPoint = new Point(first.clientX, first.clientY); + + on(document, MOVE[e.type], this._onMove, this); + on(document, END[e.type], this._onUp, this); + }, + + _onMove: function (e) { + // Ignore simulated events, since we handle both touch and + // mouse explicitly; otherwise we risk getting duplicates of + // touch events, see #4315. + // Also ignore the event if disabled; this happens in IE11 + // under some circumstances, see #3666. + if (e._simulated || !this._enabled) { return; } + + if (e.touches && e.touches.length > 1) { + this._moved = true; + return; + } + + var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e), + newPoint = new Point(first.clientX, first.clientY), + offset = newPoint.subtract(this._startPoint); + + if (!offset.x && !offset.y) { return; } + if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; } + + preventDefault(e); + + if (!this._moved) { + // @event dragstart: Event + // Fired when a drag starts + this.fire('dragstart'); + + this._moved = true; + this._startPos = getPosition(this._element).subtract(offset); + + addClass(document.body, 'leaflet-dragging'); + + this._lastTarget = e.target || e.srcElement; + // IE and Edge do not give the element, so fetch it + // if necessary + if ((window.SVGElementInstance) && (this._lastTarget instanceof SVGElementInstance)) { + this._lastTarget = this._lastTarget.correspondingUseElement; + } + addClass(this._lastTarget, 'leaflet-drag-target'); + } + + this._newPos = this._startPos.add(offset); + this._moving = true; + + cancelAnimFrame(this._animRequest); + this._lastEvent = e; + this._animRequest = requestAnimFrame(this._updatePosition, this, true); + }, + + _updatePosition: function () { + var e = {originalEvent: this._lastEvent}; + + // @event predrag: Event + // Fired continuously during dragging *before* each corresponding + // update of the element's position. + this.fire('predrag', e); + setPosition(this._element, this._newPos); + + // @event drag: Event + // Fired continuously during dragging. + this.fire('drag', e); + }, + + _onUp: function (e) { + // Ignore simulated events, since we handle both touch and + // mouse explicitly; otherwise we risk getting duplicates of + // touch events, see #4315. + // Also ignore the event if disabled; this happens in IE11 + // under some circumstances, see #3666. + if (e._simulated || !this._enabled) { return; } + this.finishDrag(); + }, + + finishDrag: function () { + removeClass(document.body, 'leaflet-dragging'); + + if (this._lastTarget) { + removeClass(this._lastTarget, 'leaflet-drag-target'); + this._lastTarget = null; + } + + for (var i in MOVE) { + off(document, MOVE[i], this._onMove, this); + off(document, END[i], this._onUp, this); + } + + enableImageDrag(); + enableTextSelection(); + + if (this._moved && this._moving) { + // ensure drag is not fired after dragend + cancelAnimFrame(this._animRequest); + + // @event dragend: DragEndEvent + // Fired when the drag ends. + this.fire('dragend', { + distance: this._newPos.distanceTo(this._startPos) + }); + } + + this._moving = false; + Draggable._dragging = false; + } + +}); + +/* + * @namespace LineUtil + * + * Various utility functions for polyline points processing, used by Leaflet internally to make polylines lightning-fast. + */ + +// Simplify polyline with vertex reduction and Douglas-Peucker simplification. +// Improves rendering performance dramatically by lessening the number of points to draw. + +// @function simplify(points: Point[], tolerance: Number): Point[] +// Dramatically reduces the number of points in a polyline while retaining +// its shape and returns a new array of simplified points, using the +// [Douglas-Peucker algorithm](http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm). +// Used for a huge performance boost when processing/displaying Leaflet polylines for +// each zoom level and also reducing visual noise. tolerance affects the amount of +// simplification (lesser value means higher quality but slower and with more points). +// Also released as a separated micro-library [Simplify.js](http://mourner.github.com/simplify-js/). +function simplify(points, tolerance) { + if (!tolerance || !points.length) { + return points.slice(); + } + + var sqTolerance = tolerance * tolerance; + + // stage 1: vertex reduction + points = _reducePoints(points, sqTolerance); + + // stage 2: Douglas-Peucker simplification + points = _simplifyDP(points, sqTolerance); + + return points; +} + +// @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number +// Returns the distance between point `p` and segment `p1` to `p2`. +function pointToSegmentDistance(p, p1, p2) { + return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true)); +} + +// @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number +// Returns the closest point from a point `p` on a segment `p1` to `p2`. +function closestPointOnSegment(p, p1, p2) { + return _sqClosestPointOnSegment(p, p1, p2); +} + +// Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm +function _simplifyDP(points, sqTolerance) { + + var len = points.length, + ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array, + markers = new ArrayConstructor(len); + + markers[0] = markers[len - 1] = 1; + + _simplifyDPStep(points, markers, sqTolerance, 0, len - 1); + + var i, + newPoints = []; + + for (i = 0; i < len; i++) { + if (markers[i]) { + newPoints.push(points[i]); + } + } + + return newPoints; +} + +function _simplifyDPStep(points, markers, sqTolerance, first, last) { + + var maxSqDist = 0, + index, i, sqDist; + + for (i = first + 1; i <= last - 1; i++) { + sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true); + + if (sqDist > maxSqDist) { + index = i; + maxSqDist = sqDist; + } + } + + if (maxSqDist > sqTolerance) { + markers[index] = 1; + + _simplifyDPStep(points, markers, sqTolerance, first, index); + _simplifyDPStep(points, markers, sqTolerance, index, last); + } +} + +// reduce points that are too close to each other to a single point +function _reducePoints(points, sqTolerance) { + var reducedPoints = [points[0]]; + + for (var i = 1, prev = 0, len = points.length; i < len; i++) { + if (_sqDist(points[i], points[prev]) > sqTolerance) { + reducedPoints.push(points[i]); + prev = i; + } + } + if (prev < len - 1) { + reducedPoints.push(points[len - 1]); + } + return reducedPoints; +} + +var _lastCode; + +// @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean +// Clips the segment a to b by rectangular bounds with the +// [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm) +// (modifying the segment points directly!). Used by Leaflet to only show polyline +// points that are on the screen or near, increasing performance. +function clipSegment(a, b, bounds, useLastCode, round) { + var codeA = useLastCode ? _lastCode : _getBitCode(a, bounds), + codeB = _getBitCode(b, bounds), + + codeOut, p, newCode; + + // save 2nd code to avoid calculating it on the next segment + _lastCode = codeB; + + while (true) { + // if a,b is inside the clip window (trivial accept) + if (!(codeA | codeB)) { + return [a, b]; + } + + // if a,b is outside the clip window (trivial reject) + if (codeA & codeB) { + return false; + } + + // other cases + codeOut = codeA || codeB; + p = _getEdgeIntersection(a, b, codeOut, bounds, round); + newCode = _getBitCode(p, bounds); + + if (codeOut === codeA) { + a = p; + codeA = newCode; + } else { + b = p; + codeB = newCode; + } + } +} + +function _getEdgeIntersection(a, b, code, bounds, round) { + var dx = b.x - a.x, + dy = b.y - a.y, + min = bounds.min, + max = bounds.max, + x, y; + + if (code & 8) { // top + x = a.x + dx * (max.y - a.y) / dy; + y = max.y; + + } else if (code & 4) { // bottom + x = a.x + dx * (min.y - a.y) / dy; + y = min.y; + + } else if (code & 2) { // right + x = max.x; + y = a.y + dy * (max.x - a.x) / dx; + + } else if (code & 1) { // left + x = min.x; + y = a.y + dy * (min.x - a.x) / dx; + } + + return new Point(x, y, round); +} + +function _getBitCode(p, bounds) { + var code = 0; + + if (p.x < bounds.min.x) { // left + code |= 1; + } else if (p.x > bounds.max.x) { // right + code |= 2; + } + + if (p.y < bounds.min.y) { // bottom + code |= 4; + } else if (p.y > bounds.max.y) { // top + code |= 8; + } + + return code; +} + +// square distance (to avoid unnecessary Math.sqrt calls) +function _sqDist(p1, p2) { + var dx = p2.x - p1.x, + dy = p2.y - p1.y; + return dx * dx + dy * dy; +} + +// return closest point on segment or distance to that point +function _sqClosestPointOnSegment(p, p1, p2, sqDist) { + var x = p1.x, + y = p1.y, + dx = p2.x - x, + dy = p2.y - y, + dot = dx * dx + dy * dy, + t; + + if (dot > 0) { + t = ((p.x - x) * dx + (p.y - y) * dy) / dot; + + if (t > 1) { + x = p2.x; + y = p2.y; + } else if (t > 0) { + x += dx * t; + y += dy * t; + } + } + + dx = p.x - x; + dy = p.y - y; + + return sqDist ? dx * dx + dy * dy : new Point(x, y); +} + + +// @function isFlat(latlngs: LatLng[]): Boolean +// Returns true if `latlngs` is a flat array, false is nested. +function isFlat(latlngs) { + return !isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined'); +} + +function _flat(latlngs) { + console.warn('Deprecated use of _flat, please use L.LineUtil.isFlat instead.'); + return isFlat(latlngs); +} + + +var LineUtil = (Object.freeze || Object)({ + simplify: simplify, + pointToSegmentDistance: pointToSegmentDistance, + closestPointOnSegment: closestPointOnSegment, + clipSegment: clipSegment, + _getEdgeIntersection: _getEdgeIntersection, + _getBitCode: _getBitCode, + _sqClosestPointOnSegment: _sqClosestPointOnSegment, + isFlat: isFlat, + _flat: _flat +}); + +/* + * @namespace PolyUtil + * Various utility functions for polygon geometries. + */ + +/* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[] + * Clips the polygon geometry defined by the given `points` by the given bounds (using the [Sutherland-Hodgman algorithm](https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm)). + * Used by Leaflet to only show polygon points that are on the screen or near, increasing + * performance. Note that polygon points needs different algorithm for clipping + * than polyline, so there's a separate method for it. + */ +function clipPolygon(points, bounds, round) { + var clippedPoints, + edges = [1, 4, 2, 8], + i, j, k, + a, b, + len, edge, p; + + for (i = 0, len = points.length; i < len; i++) { + points[i]._code = _getBitCode(points[i], bounds); + } + + // for each edge (left, bottom, right, top) + for (k = 0; k < 4; k++) { + edge = edges[k]; + clippedPoints = []; + + for (i = 0, len = points.length, j = len - 1; i < len; j = i++) { + a = points[i]; + b = points[j]; + + // if a is inside the clip window + if (!(a._code & edge)) { + // if b is outside the clip window (a->b goes out of screen) + if (b._code & edge) { + p = _getEdgeIntersection(b, a, edge, bounds, round); + p._code = _getBitCode(p, bounds); + clippedPoints.push(p); + } + clippedPoints.push(a); + + // else if b is inside the clip window (a->b enters the screen) + } else if (!(b._code & edge)) { + p = _getEdgeIntersection(b, a, edge, bounds, round); + p._code = _getBitCode(p, bounds); + clippedPoints.push(p); + } + } + points = clippedPoints; + } + + return points; +} + + +var PolyUtil = (Object.freeze || Object)({ + clipPolygon: clipPolygon +}); + +/* + * @namespace Projection + * @section + * Leaflet comes with a set of already defined Projections out of the box: + * + * @projection L.Projection.LonLat + * + * Equirectangular, or Plate Carree projection — the most simple projection, + * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as + * latitude. Also suitable for flat worlds, e.g. game maps. Used by the + * `EPSG:4326` and `Simple` CRS. + */ + +var LonLat = { + project: function (latlng) { + return new Point(latlng.lng, latlng.lat); + }, + + unproject: function (point) { + return new LatLng(point.y, point.x); + }, + + bounds: new Bounds([-180, -90], [180, 90]) +}; + +/* + * @namespace Projection + * @projection L.Projection.Mercator + * + * Elliptical Mercator projection — more complex than Spherical Mercator. Takes into account that Earth is a geoid, not a perfect sphere. Used by the EPSG:3395 CRS. + */ + +var Mercator = { + R: 6378137, + R_MINOR: 6356752.314245179, + + bounds: new Bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]), + + project: function (latlng) { + var d = Math.PI / 180, + r = this.R, + y = latlng.lat * d, + tmp = this.R_MINOR / r, + e = Math.sqrt(1 - tmp * tmp), + con = e * Math.sin(y); + + var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2); + y = -r * Math.log(Math.max(ts, 1E-10)); + + return new Point(latlng.lng * d * r, y); + }, + + unproject: function (point) { + var d = 180 / Math.PI, + r = this.R, + tmp = this.R_MINOR / r, + e = Math.sqrt(1 - tmp * tmp), + ts = Math.exp(-point.y / r), + phi = Math.PI / 2 - 2 * Math.atan(ts); + + for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) { + con = e * Math.sin(phi); + con = Math.pow((1 - con) / (1 + con), e / 2); + dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi; + phi += dphi; + } + + return new LatLng(phi * d, point.x * d / r); + } +}; + +/* + * @class Projection + + * An object with methods for projecting geographical coordinates of the world onto + * a flat surface (and back). See [Map projection](http://en.wikipedia.org/wiki/Map_projection). + + * @property bounds: Bounds + * The bounds (specified in CRS units) where the projection is valid + + * @method project(latlng: LatLng): Point + * Projects geographical coordinates into a 2D point. + * Only accepts actual `L.LatLng` instances, not arrays. + + * @method unproject(point: Point): LatLng + * The inverse of `project`. Projects a 2D point into a geographical location. + * Only accepts actual `L.Point` instances, not arrays. + + * Note that the projection instances do not inherit from Leafet's `Class` object, + * and can't be instantiated. Also, new classes can't inherit from them, + * and methods can't be added to them with the `include` function. + + */ + + + + +var index = (Object.freeze || Object)({ + LonLat: LonLat, + Mercator: Mercator, + SphericalMercator: SphericalMercator +}); + +/* + * @namespace CRS + * @crs L.CRS.EPSG3395 + * + * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection. + */ +var EPSG3395 = extend({}, Earth, { + code: 'EPSG:3395', + projection: Mercator, + + transformation: (function () { + var scale = 0.5 / (Math.PI * Mercator.R); + return toTransformation(scale, 0.5, -scale, 0.5); + }()) +}); + +/* + * @namespace CRS + * @crs L.CRS.EPSG4326 + * + * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection. + * + * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic), + * which is a breaking change from 0.7.x behaviour. If you are using a `TileLayer` + * with this CRS, ensure that there are two 256x256 pixel tiles covering the + * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90), + * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set. + */ + +var EPSG4326 = extend({}, Earth, { + code: 'EPSG:4326', + projection: LonLat, + transformation: toTransformation(1 / 180, 1, -1 / 180, 0.5) +}); + +/* + * @namespace CRS + * @crs L.CRS.Simple + * + * A simple CRS that maps longitude and latitude into `x` and `y` directly. + * May be used for maps of flat surfaces (e.g. game maps). Note that the `y` + * axis should still be inverted (going from bottom to top). `distance()` returns + * simple euclidean distance. + */ + +var Simple = extend({}, CRS, { + projection: LonLat, + transformation: toTransformation(1, 0, -1, 0), + + scale: function (zoom) { + return Math.pow(2, zoom); + }, + + zoom: function (scale) { + return Math.log(scale) / Math.LN2; + }, + + distance: function (latlng1, latlng2) { + var dx = latlng2.lng - latlng1.lng, + dy = latlng2.lat - latlng1.lat; + + return Math.sqrt(dx * dx + dy * dy); + }, + + infinite: true +}); + +CRS.Earth = Earth; +CRS.EPSG3395 = EPSG3395; +CRS.EPSG3857 = EPSG3857; +CRS.EPSG900913 = EPSG900913; +CRS.EPSG4326 = EPSG4326; +CRS.Simple = Simple; + +/* + * @class Layer + * @inherits Evented + * @aka L.Layer + * @aka ILayer + * + * A set of methods from the Layer base class that all Leaflet layers use. + * Inherits all methods, options and events from `L.Evented`. + * + * @example + * + * ```js + * var layer = L.Marker(latlng).addTo(map); + * layer.addTo(map); + * layer.remove(); + * ``` + * + * @event add: Event + * Fired after the layer is added to a map + * + * @event remove: Event + * Fired after the layer is removed from a map + */ + + +var Layer = Evented.extend({ + + // Classes extending `L.Layer` will inherit the following options: + options: { + // @option pane: String = 'overlayPane' + // By default the layer will be added to the map's [overlay pane](#map-overlaypane). Overriding this option will cause the layer to be placed on another pane by default. + pane: 'overlayPane', + + // @option attribution: String = null + // String to be shown in the attribution control, describes the layer data, e.g. "© Mapbox". + attribution: null, + + bubblingMouseEvents: true + }, + + /* @section + * Classes extending `L.Layer` will inherit the following methods: + * + * @method addTo(map: Map|LayerGroup): this + * Adds the layer to the given map or layer group. + */ + addTo: function (map) { + map.addLayer(this); + return this; + }, + + // @method remove: this + // Removes the layer from the map it is currently active on. + remove: function () { + return this.removeFrom(this._map || this._mapToAdd); + }, + + // @method removeFrom(map: Map): this + // Removes the layer from the given map + removeFrom: function (obj) { + if (obj) { + obj.removeLayer(this); + } + return this; + }, + + // @method getPane(name? : String): HTMLElement + // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer. + getPane: function (name) { + return this._map.getPane(name ? (this.options[name] || name) : this.options.pane); + }, + + addInteractiveTarget: function (targetEl) { + this._map._targets[stamp(targetEl)] = this; + return this; + }, + + removeInteractiveTarget: function (targetEl) { + delete this._map._targets[stamp(targetEl)]; + return this; + }, + + // @method getAttribution: String + // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution). + getAttribution: function () { + return this.options.attribution; + }, + + _layerAdd: function (e) { + var map = e.target; + + // check in case layer gets added and then removed before the map is ready + if (!map.hasLayer(this)) { return; } + + this._map = map; + this._zoomAnimated = map._zoomAnimated; + + if (this.getEvents) { + var events = this.getEvents(); + map.on(events, this); + this.once('remove', function () { + map.off(events, this); + }, this); + } + + this.onAdd(map); + + if (this.getAttribution && map.attributionControl) { + map.attributionControl.addAttribution(this.getAttribution()); + } + + this.fire('add'); + map.fire('layeradd', {layer: this}); + } +}); + +/* @section Extension methods + * @uninheritable + * + * Every layer should extend from `L.Layer` and (re-)implement the following methods. + * + * @method onAdd(map: Map): this + * Should contain code that creates DOM elements for the layer, adds them to `map panes` where they should belong and puts listeners on relevant map events. Called on [`map.addLayer(layer)`](#map-addlayer). + * + * @method onRemove(map: Map): this + * Should contain all clean up code that removes the layer's elements from the DOM and removes listeners previously added in [`onAdd`](#layer-onadd). Called on [`map.removeLayer(layer)`](#map-removelayer). + * + * @method getEvents(): Object + * This optional method should return an object like `{ viewreset: this._reset }` for [`addEventListener`](#evented-addeventlistener). The event handlers in this object will be automatically added and removed from the map with your layer. + * + * @method getAttribution(): String + * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible. + * + * @method beforeAdd(map: Map): this + * Optional method. Called on [`map.addLayer(layer)`](#map-addlayer), before the layer is added to the map, before events are initialized, without waiting until the map is in a usable state. Use for early initialization only. + */ + + +/* @namespace Map + * @section Layer events + * + * @event layeradd: LayerEvent + * Fired when a new layer is added to the map. + * + * @event layerremove: LayerEvent + * Fired when some layer is removed from the map + * + * @section Methods for Layers and Controls + */ +Map.include({ + // @method addLayer(layer: Layer): this + // Adds the given layer to the map + addLayer: function (layer) { + if (!layer._layerAdd) { + throw new Error('The provided object is not a Layer.'); + } + + var id = stamp(layer); + if (this._layers[id]) { return this; } + this._layers[id] = layer; + + layer._mapToAdd = this; + + if (layer.beforeAdd) { + layer.beforeAdd(this); + } + + this.whenReady(layer._layerAdd, layer); + + return this; + }, + + // @method removeLayer(layer: Layer): this + // Removes the given layer from the map. + removeLayer: function (layer) { + var id = stamp(layer); + + if (!this._layers[id]) { return this; } + + if (this._loaded) { + layer.onRemove(this); + } + + if (layer.getAttribution && this.attributionControl) { + this.attributionControl.removeAttribution(layer.getAttribution()); + } + + delete this._layers[id]; + + if (this._loaded) { + this.fire('layerremove', {layer: layer}); + layer.fire('remove'); + } + + layer._map = layer._mapToAdd = null; + + return this; + }, + + // @method hasLayer(layer: Layer): Boolean + // Returns `true` if the given layer is currently added to the map + hasLayer: function (layer) { + return !!layer && (stamp(layer) in this._layers); + }, + + /* @method eachLayer(fn: Function, context?: Object): this + * Iterates over the layers of the map, optionally specifying context of the iterator function. + * ``` + * map.eachLayer(function(layer){ + * layer.bindPopup('Hello'); + * }); + * ``` + */ + eachLayer: function (method, context) { + for (var i in this._layers) { + method.call(context, this._layers[i]); + } + return this; + }, + + _addLayers: function (layers) { + layers = layers ? (isArray(layers) ? layers : [layers]) : []; + + for (var i = 0, len = layers.length; i < len; i++) { + this.addLayer(layers[i]); + } + }, + + _addZoomLimit: function (layer) { + if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) { + this._zoomBoundLayers[stamp(layer)] = layer; + this._updateZoomLevels(); + } + }, + + _removeZoomLimit: function (layer) { + var id = stamp(layer); + + if (this._zoomBoundLayers[id]) { + delete this._zoomBoundLayers[id]; + this._updateZoomLevels(); + } + }, + + _updateZoomLevels: function () { + var minZoom = Infinity, + maxZoom = -Infinity, + oldZoomSpan = this._getZoomSpan(); + + for (var i in this._zoomBoundLayers) { + var options = this._zoomBoundLayers[i].options; + + minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom); + maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom); + } + + this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom; + this._layersMinZoom = minZoom === Infinity ? undefined : minZoom; + + // @section Map state change events + // @event zoomlevelschange: Event + // Fired when the number of zoomlevels on the map is changed due + // to adding or removing a layer. + if (oldZoomSpan !== this._getZoomSpan()) { + this.fire('zoomlevelschange'); + } + + if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) { + this.setZoom(this._layersMaxZoom); + } + if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) { + this.setZoom(this._layersMinZoom); + } + } +}); + +/* + * @class LayerGroup + * @aka L.LayerGroup + * @inherits Layer + * + * Used to group several layers and handle them as one. If you add it to the map, + * any layers added or removed from the group will be added/removed on the map as + * well. Extends `Layer`. + * + * @example + * + * ```js + * L.layerGroup([marker1, marker2]) + * .addLayer(polyline) + * .addTo(map); + * ``` + */ + +var LayerGroup = Layer.extend({ + + initialize: function (layers, options) { + setOptions(this, options); + + this._layers = {}; + + var i, len; + + if (layers) { + for (i = 0, len = layers.length; i < len; i++) { + this.addLayer(layers[i]); + } + } + }, + + // @method addLayer(layer: Layer): this + // Adds the given layer to the group. + addLayer: function (layer) { + var id = this.getLayerId(layer); + + this._layers[id] = layer; + + if (this._map) { + this._map.addLayer(layer); + } + + return this; + }, + + // @method removeLayer(layer: Layer): this + // Removes the given layer from the group. + // @alternative + // @method removeLayer(id: Number): this + // Removes the layer with the given internal ID from the group. + removeLayer: function (layer) { + var id = layer in this._layers ? layer : this.getLayerId(layer); + + if (this._map && this._layers[id]) { + this._map.removeLayer(this._layers[id]); + } + + delete this._layers[id]; + + return this; + }, + + // @method hasLayer(layer: Layer): Boolean + // Returns `true` if the given layer is currently added to the group. + // @alternative + // @method hasLayer(id: Number): Boolean + // Returns `true` if the given internal ID is currently added to the group. + hasLayer: function (layer) { + return !!layer && (layer in this._layers || this.getLayerId(layer) in this._layers); + }, + + // @method clearLayers(): this + // Removes all the layers from the group. + clearLayers: function () { + return this.eachLayer(this.removeLayer, this); + }, + + // @method invoke(methodName: String, …): this + // Calls `methodName` on every layer contained in this group, passing any + // additional parameters. Has no effect if the layers contained do not + // implement `methodName`. + invoke: function (methodName) { + var args = Array.prototype.slice.call(arguments, 1), + i, layer; + + for (i in this._layers) { + layer = this._layers[i]; + + if (layer[methodName]) { + layer[methodName].apply(layer, args); + } + } + + return this; + }, + + onAdd: function (map) { + this.eachLayer(map.addLayer, map); + }, + + onRemove: function (map) { + this.eachLayer(map.removeLayer, map); + }, + + // @method eachLayer(fn: Function, context?: Object): this + // Iterates over the layers of the group, optionally specifying context of the iterator function. + // ```js + // group.eachLayer(function (layer) { + // layer.bindPopup('Hello'); + // }); + // ``` + eachLayer: function (method, context) { + for (var i in this._layers) { + method.call(context, this._layers[i]); + } + return this; + }, + + // @method getLayer(id: Number): Layer + // Returns the layer with the given internal ID. + getLayer: function (id) { + return this._layers[id]; + }, + + // @method getLayers(): Layer[] + // Returns an array of all the layers added to the group. + getLayers: function () { + var layers = []; + this.eachLayer(layers.push, layers); + return layers; + }, + + // @method setZIndex(zIndex: Number): this + // Calls `setZIndex` on every layer contained in this group, passing the z-index. + setZIndex: function (zIndex) { + return this.invoke('setZIndex', zIndex); + }, + + // @method getLayerId(layer: Layer): Number + // Returns the internal ID for a layer + getLayerId: function (layer) { + return stamp(layer); + } +}); + + +// @factory L.layerGroup(layers?: Layer[], options?: Object) +// Create a layer group, optionally given an initial set of layers and an `options` object. +var layerGroup = function (layers, options) { + return new LayerGroup(layers, options); +}; + +/* + * @class FeatureGroup + * @aka L.FeatureGroup + * @inherits LayerGroup + * + * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers: + * * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip)) + * * Events are propagated to the `FeatureGroup`, so if the group has an event + * handler, it will handle events from any of the layers. This includes mouse events + * and custom events. + * * Has `layeradd` and `layerremove` events + * + * @example + * + * ```js + * L.featureGroup([marker1, marker2, polyline]) + * .bindPopup('Hello world!') + * .on('click', function() { alert('Clicked on a member of the group!'); }) + * .addTo(map); + * ``` + */ + +var FeatureGroup = LayerGroup.extend({ + + addLayer: function (layer) { + if (this.hasLayer(layer)) { + return this; + } + + layer.addEventParent(this); + + LayerGroup.prototype.addLayer.call(this, layer); + + // @event layeradd: LayerEvent + // Fired when a layer is added to this `FeatureGroup` + return this.fire('layeradd', {layer: layer}); + }, + + removeLayer: function (layer) { + if (!this.hasLayer(layer)) { + return this; + } + if (layer in this._layers) { + layer = this._layers[layer]; + } + + layer.removeEventParent(this); + + LayerGroup.prototype.removeLayer.call(this, layer); + + // @event layerremove: LayerEvent + // Fired when a layer is removed from this `FeatureGroup` + return this.fire('layerremove', {layer: layer}); + }, + + // @method setStyle(style: Path options): this + // Sets the given path options to each layer of the group that has a `setStyle` method. + setStyle: function (style) { + return this.invoke('setStyle', style); + }, + + // @method bringToFront(): this + // Brings the layer group to the top of all other layers + bringToFront: function () { + return this.invoke('bringToFront'); + }, + + // @method bringToBack(): this + // Brings the layer group to the back of all other layers + bringToBack: function () { + return this.invoke('bringToBack'); + }, + + // @method getBounds(): LatLngBounds + // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children). + getBounds: function () { + var bounds = new LatLngBounds(); + + for (var id in this._layers) { + var layer = this._layers[id]; + bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng()); + } + return bounds; + } +}); + +// @factory L.featureGroup(layers: Layer[]) +// Create a feature group, optionally given an initial set of layers. +var featureGroup = function (layers) { + return new FeatureGroup(layers); +}; + +/* + * @class Icon + * @aka L.Icon + * + * Represents an icon to provide when creating a marker. + * + * @example + * + * ```js + * var myIcon = L.icon({ + * iconUrl: 'my-icon.png', + * iconRetinaUrl: 'my-icon@2x.png', + * iconSize: [38, 95], + * iconAnchor: [22, 94], + * popupAnchor: [-3, -76], + * shadowUrl: 'my-icon-shadow.png', + * shadowRetinaUrl: 'my-icon-shadow@2x.png', + * shadowSize: [68, 95], + * shadowAnchor: [22, 94] + * }); + * + * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map); + * ``` + * + * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default. + * + */ + +var Icon = Class.extend({ + + /* @section + * @aka Icon options + * + * @option iconUrl: String = null + * **(required)** The URL to the icon image (absolute or relative to your script path). + * + * @option iconRetinaUrl: String = null + * The URL to a retina sized version of the icon image (absolute or relative to your + * script path). Used for Retina screen devices. + * + * @option iconSize: Point = null + * Size of the icon image in pixels. + * + * @option iconAnchor: Point = null + * The coordinates of the "tip" of the icon (relative to its top left corner). The icon + * will be aligned so that this point is at the marker's geographical location. Centered + * by default if size is specified, also can be set in CSS with negative margins. + * + * @option popupAnchor: Point = [0, 0] + * The coordinates of the point from which popups will "open", relative to the icon anchor. + * + * @option tooltipAnchor: Point = [0, 0] + * The coordinates of the point from which tooltips will "open", relative to the icon anchor. + * + * @option shadowUrl: String = null + * The URL to the icon shadow image. If not specified, no shadow image will be created. + * + * @option shadowRetinaUrl: String = null + * + * @option shadowSize: Point = null + * Size of the shadow image in pixels. + * + * @option shadowAnchor: Point = null + * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same + * as iconAnchor if not specified). + * + * @option className: String = '' + * A custom class name to assign to both icon and shadow images. Empty by default. + */ + + options: { + popupAnchor: [0, 0], + tooltipAnchor: [0, 0], + }, + + initialize: function (options) { + setOptions(this, options); + }, + + // @method createIcon(oldIcon?: HTMLElement): HTMLElement + // Called internally when the icon has to be shown, returns a `` HTML element + // styled according to the options. + createIcon: function (oldIcon) { + return this._createIcon('icon', oldIcon); + }, + + // @method createShadow(oldIcon?: HTMLElement): HTMLElement + // As `createIcon`, but for the shadow beneath it. + createShadow: function (oldIcon) { + return this._createIcon('shadow', oldIcon); + }, + + _createIcon: function (name, oldIcon) { + var src = this._getIconUrl(name); + + if (!src) { + if (name === 'icon') { + throw new Error('iconUrl not set in Icon options (see the docs).'); + } + return null; + } + + var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null); + this._setIconStyles(img, name); + + return img; + }, + + _setIconStyles: function (img, name) { + var options = this.options; + var sizeOption = options[name + 'Size']; + + if (typeof sizeOption === 'number') { + sizeOption = [sizeOption, sizeOption]; + } + + var size = toPoint(sizeOption), + anchor = toPoint(name === 'shadow' && options.shadowAnchor || options.iconAnchor || + size && size.divideBy(2, true)); + + img.className = 'leaflet-marker-' + name + ' ' + (options.className || ''); + + if (anchor) { + img.style.marginLeft = (-anchor.x) + 'px'; + img.style.marginTop = (-anchor.y) + 'px'; + } + + if (size) { + img.style.width = size.x + 'px'; + img.style.height = size.y + 'px'; + } + }, + + _createImg: function (src, el) { + el = el || document.createElement('img'); + el.src = src; + return el; + }, + + _getIconUrl: function (name) { + return retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url']; + } +}); + + +// @factory L.icon(options: Icon options) +// Creates an icon instance with the given options. +function icon(options) { + return new Icon(options); +} + +/* + * @miniclass Icon.Default (Icon) + * @aka L.Icon.Default + * @section + * + * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when + * no icon is specified. Points to the blue marker image distributed with Leaflet + * releases. + * + * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options` + * (which is a set of `Icon options`). + * + * If you want to _completely_ replace the default icon, override the + * `L.Marker.prototype.options.icon` with your own icon instead. + */ + +var IconDefault = Icon.extend({ + + options: { + iconUrl: 'marker-icon.png', + iconRetinaUrl: 'marker-icon-2x.png', + shadowUrl: 'marker-shadow.png', + iconSize: [25, 41], + iconAnchor: [12, 41], + popupAnchor: [1, -34], + tooltipAnchor: [16, -28], + shadowSize: [41, 41] + }, + + _getIconUrl: function (name) { + if (!IconDefault.imagePath) { // Deprecated, backwards-compatibility only + IconDefault.imagePath = this._detectIconPath(); + } + + // @option imagePath: String + // `Icon.Default` will try to auto-detect the location of the + // blue icon images. If you are placing these images in a non-standard + // way, set this option to point to the right path. + return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name); + }, + + _detectIconPath: function () { + var el = create$1('div', 'leaflet-default-icon-path', document.body); + var path = getStyle(el, 'background-image') || + getStyle(el, 'backgroundImage'); // IE8 + + document.body.removeChild(el); + + if (path === null || path.indexOf('url') !== 0) { + path = ''; + } else { + path = path.replace(/^url\(["']?/, '').replace(/marker-icon\.png["']?\)$/, ''); + } + + return path; + } +}); + +/* + * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable. + */ + + +/* @namespace Marker + * @section Interaction handlers + * + * Interaction handlers are properties of a marker instance that allow you to control interaction behavior in runtime, enabling or disabling certain features such as dragging (see `Handler` methods). Example: + * + * ```js + * marker.dragging.disable(); + * ``` + * + * @property dragging: Handler + * Marker dragging handler (by both mouse and touch). Only valid when the marker is on the map (Otherwise set [`marker.options.draggable`](#marker-draggable)). + */ + +var MarkerDrag = Handler.extend({ + initialize: function (marker) { + this._marker = marker; + }, + + addHooks: function () { + var icon = this._marker._icon; + + if (!this._draggable) { + this._draggable = new Draggable(icon, icon, true); + } + + this._draggable.on({ + dragstart: this._onDragStart, + predrag: this._onPreDrag, + drag: this._onDrag, + dragend: this._onDragEnd + }, this).enable(); + + addClass(icon, 'leaflet-marker-draggable'); + }, + + removeHooks: function () { + this._draggable.off({ + dragstart: this._onDragStart, + predrag: this._onPreDrag, + drag: this._onDrag, + dragend: this._onDragEnd + }, this).disable(); + + if (this._marker._icon) { + removeClass(this._marker._icon, 'leaflet-marker-draggable'); + } + }, + + moved: function () { + return this._draggable && this._draggable._moved; + }, + + _adjustPan: function (e) { + var marker = this._marker, + map = marker._map, + speed = this._marker.options.autoPanSpeed, + padding = this._marker.options.autoPanPadding, + iconPos = L.DomUtil.getPosition(marker._icon), + bounds = map.getPixelBounds(), + origin = map.getPixelOrigin(); + + var panBounds = toBounds( + bounds.min._subtract(origin).add(padding), + bounds.max._subtract(origin).subtract(padding) + ); + + if (!panBounds.contains(iconPos)) { + // Compute incremental movement + var movement = toPoint( + (Math.max(panBounds.max.x, iconPos.x) - panBounds.max.x) / (bounds.max.x - panBounds.max.x) - + (Math.min(panBounds.min.x, iconPos.x) - panBounds.min.x) / (bounds.min.x - panBounds.min.x), + + (Math.max(panBounds.max.y, iconPos.y) - panBounds.max.y) / (bounds.max.y - panBounds.max.y) - + (Math.min(panBounds.min.y, iconPos.y) - panBounds.min.y) / (bounds.min.y - panBounds.min.y) + ).multiplyBy(speed); + + map.panBy(movement, {animate: false}); + + this._draggable._newPos._add(movement); + this._draggable._startPos._add(movement); + + L.DomUtil.setPosition(marker._icon, this._draggable._newPos); + this._onDrag(e); + + this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e)); + } + }, + + _onDragStart: function () { + // @section Dragging events + // @event dragstart: Event + // Fired when the user starts dragging the marker. + + // @event movestart: Event + // Fired when the marker starts moving (because of dragging). + + this._oldLatLng = this._marker.getLatLng(); + this._marker + .closePopup() + .fire('movestart') + .fire('dragstart'); + }, + + _onPreDrag: function (e) { + if (this._marker.options.autoPan) { + cancelAnimFrame(this._panRequest); + this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e)); + } + }, + + _onDrag: function (e) { + var marker = this._marker, + shadow = marker._shadow, + iconPos = getPosition(marker._icon), + latlng = marker._map.layerPointToLatLng(iconPos); + + // update shadow position + if (shadow) { + setPosition(shadow, iconPos); + } + + marker._latlng = latlng; + e.latlng = latlng; + e.oldLatLng = this._oldLatLng; + + // @event drag: Event + // Fired repeatedly while the user drags the marker. + marker + .fire('move', e) + .fire('drag', e); + }, + + _onDragEnd: function (e) { + // @event dragend: DragEndEvent + // Fired when the user stops dragging the marker. + + cancelAnimFrame(this._panRequest); + + // @event moveend: Event + // Fired when the marker stops moving (because of dragging). + delete this._oldLatLng; + this._marker + .fire('moveend') + .fire('dragend', e); + } +}); + +/* + * @class Marker + * @inherits Interactive layer + * @aka L.Marker + * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`. + * + * @example + * + * ```js + * L.marker([50.5, 30.5]).addTo(map); + * ``` + */ + +var Marker = Layer.extend({ + + // @section + // @aka Marker options + options: { + // @option icon: Icon = * + // Icon instance to use for rendering the marker. + // See [Icon documentation](#L.Icon) for details on how to customize the marker icon. + // If not specified, a common instance of `L.Icon.Default` is used. + icon: new IconDefault(), + + // Option inherited from "Interactive layer" abstract class + interactive: true, + + // @option draggable: Boolean = false + // Whether the marker is draggable with mouse/touch or not. + draggable: false, + + // @option autoPan: Boolean = false + // Set it to `true` if you want the map to do panning animation when marker hits the edges. + autoPan: false, + + // @option autoPanPadding: Point = Point(50, 50) + // Equivalent of setting both top left and bottom right autopan padding to the same value. + autoPanPadding: [50, 50], + + // @option autoPanSpeed: Number = 10 + // Number of pixels the map should move by. + autoPanSpeed: 10, + + // @option keyboard: Boolean = true + // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter. + keyboard: true, + + // @option title: String = '' + // Text for the browser tooltip that appear on marker hover (no tooltip by default). + title: '', + + // @option alt: String = '' + // Text for the `alt` attribute of the icon image (useful for accessibility). + alt: '', + + // @option zIndexOffset: Number = 0 + // By default, marker images zIndex is set automatically based on its latitude. Use this option if you want to put the marker on top of all others (or below), specifying a high value like `1000` (or high negative value, respectively). + zIndexOffset: 0, + + // @option opacity: Number = 1.0 + // The opacity of the marker. + opacity: 1, + + // @option riseOnHover: Boolean = false + // If `true`, the marker will get on top of others when you hover the mouse over it. + riseOnHover: false, + + // @option riseOffset: Number = 250 + // The z-index offset used for the `riseOnHover` feature. + riseOffset: 250, + + // @option pane: String = 'markerPane' + // `Map pane` where the markers icon will be added. + pane: 'markerPane', + + // @option bubblingMouseEvents: Boolean = false + // When `true`, a mouse event on this marker will trigger the same event on the map + // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used). + bubblingMouseEvents: false + }, + + /* @section + * + * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods: + */ + + initialize: function (latlng, options) { + setOptions(this, options); + this._latlng = toLatLng(latlng); + }, + + onAdd: function (map) { + this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation; + + if (this._zoomAnimated) { + map.on('zoomanim', this._animateZoom, this); + } + + this._initIcon(); + this.update(); + }, + + onRemove: function (map) { + if (this.dragging && this.dragging.enabled()) { + this.options.draggable = true; + this.dragging.removeHooks(); + } + delete this.dragging; + + if (this._zoomAnimated) { + map.off('zoomanim', this._animateZoom, this); + } + + this._removeIcon(); + this._removeShadow(); + }, + + getEvents: function () { + return { + zoom: this.update, + viewreset: this.update + }; + }, + + // @method getLatLng: LatLng + // Returns the current geographical position of the marker. + getLatLng: function () { + return this._latlng; + }, + + // @method setLatLng(latlng: LatLng): this + // Changes the marker position to the given point. + setLatLng: function (latlng) { + var oldLatLng = this._latlng; + this._latlng = toLatLng(latlng); + this.update(); + + // @event move: Event + // Fired when the marker is moved via [`setLatLng`](#marker-setlatlng) or by [dragging](#marker-dragging). Old and new coordinates are included in event arguments as `oldLatLng`, `latlng`. + return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng}); + }, + + // @method setZIndexOffset(offset: Number): this + // Changes the [zIndex offset](#marker-zindexoffset) of the marker. + setZIndexOffset: function (offset) { + this.options.zIndexOffset = offset; + return this.update(); + }, + + // @method setIcon(icon: Icon): this + // Changes the marker icon. + setIcon: function (icon) { + + this.options.icon = icon; + + if (this._map) { + this._initIcon(); + this.update(); + } + + if (this._popup) { + this.bindPopup(this._popup, this._popup.options); + } + + return this; + }, + + getElement: function () { + return this._icon; + }, + + update: function () { + + if (this._icon && this._map) { + var pos = this._map.latLngToLayerPoint(this._latlng).round(); + this._setPos(pos); + } + + return this; + }, + + _initIcon: function () { + var options = this.options, + classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide'); + + var icon = options.icon.createIcon(this._icon), + addIcon = false; + + // if we're not reusing the icon, remove the old one and init new one + if (icon !== this._icon) { + if (this._icon) { + this._removeIcon(); + } + addIcon = true; + + if (options.title) { + icon.title = options.title; + } + + if (icon.tagName === 'IMG') { + icon.alt = options.alt || ''; + } + } + + addClass(icon, classToAdd); + + if (options.keyboard) { + icon.tabIndex = '0'; + } + + this._icon = icon; + + if (options.riseOnHover) { + this.on({ + mouseover: this._bringToFront, + mouseout: this._resetZIndex + }); + } + + var newShadow = options.icon.createShadow(this._shadow), + addShadow = false; + + if (newShadow !== this._shadow) { + this._removeShadow(); + addShadow = true; + } + + if (newShadow) { + addClass(newShadow, classToAdd); + newShadow.alt = ''; + } + this._shadow = newShadow; + + + if (options.opacity < 1) { + this._updateOpacity(); + } + + + if (addIcon) { + this.getPane().appendChild(this._icon); + } + this._initInteraction(); + if (newShadow && addShadow) { + this.getPane('shadowPane').appendChild(this._shadow); + } + }, + + _removeIcon: function () { + if (this.options.riseOnHover) { + this.off({ + mouseover: this._bringToFront, + mouseout: this._resetZIndex + }); + } + + remove(this._icon); + this.removeInteractiveTarget(this._icon); + + this._icon = null; + }, + + _removeShadow: function () { + if (this._shadow) { + remove(this._shadow); + } + this._shadow = null; + }, + + _setPos: function (pos) { + setPosition(this._icon, pos); + + if (this._shadow) { + setPosition(this._shadow, pos); + } + + this._zIndex = pos.y + this.options.zIndexOffset; + + this._resetZIndex(); + }, + + _updateZIndex: function (offset) { + this._icon.style.zIndex = this._zIndex + offset; + }, + + _animateZoom: function (opt) { + var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round(); + + this._setPos(pos); + }, + + _initInteraction: function () { + + if (!this.options.interactive) { return; } + + addClass(this._icon, 'leaflet-interactive'); + + this.addInteractiveTarget(this._icon); + + if (MarkerDrag) { + var draggable = this.options.draggable; + if (this.dragging) { + draggable = this.dragging.enabled(); + this.dragging.disable(); + } + + this.dragging = new MarkerDrag(this); + + if (draggable) { + this.dragging.enable(); + } + } + }, + + // @method setOpacity(opacity: Number): this + // Changes the opacity of the marker. + setOpacity: function (opacity) { + this.options.opacity = opacity; + if (this._map) { + this._updateOpacity(); + } + + return this; + }, + + _updateOpacity: function () { + var opacity = this.options.opacity; + + setOpacity(this._icon, opacity); + + if (this._shadow) { + setOpacity(this._shadow, opacity); + } + }, + + _bringToFront: function () { + this._updateZIndex(this.options.riseOffset); + }, + + _resetZIndex: function () { + this._updateZIndex(0); + }, + + _getPopupAnchor: function () { + return this.options.icon.options.popupAnchor; + }, + + _getTooltipAnchor: function () { + return this.options.icon.options.tooltipAnchor; + } +}); + + +// factory L.marker(latlng: LatLng, options? : Marker options) + +// @factory L.marker(latlng: LatLng, options? : Marker options) +// Instantiates a Marker object given a geographical point and optionally an options object. +function marker(latlng, options) { + return new Marker(latlng, options); +} + +/* + * @class Path + * @aka L.Path + * @inherits Interactive layer + * + * An abstract class that contains options and constants shared between vector + * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`. + */ + +var Path = Layer.extend({ + + // @section + // @aka Path options + options: { + // @option stroke: Boolean = true + // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles. + stroke: true, + + // @option color: String = '#3388ff' + // Stroke color + color: '#3388ff', + + // @option weight: Number = 3 + // Stroke width in pixels + weight: 3, + + // @option opacity: Number = 1.0 + // Stroke opacity + opacity: 1, + + // @option lineCap: String= 'round' + // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke. + lineCap: 'round', + + // @option lineJoin: String = 'round' + // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke. + lineJoin: 'round', + + // @option dashArray: String = null + // A string that defines the stroke [dash pattern](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-dasharray). Doesn't work on `Canvas`-powered layers in [some old browsers](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/setLineDash#Browser_compatibility). + dashArray: null, + + // @option dashOffset: String = null + // A string that defines the [distance into the dash pattern to start the dash](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-dashoffset). Doesn't work on `Canvas`-powered layers in [some old browsers](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/setLineDash#Browser_compatibility). + dashOffset: null, + + // @option fill: Boolean = depends + // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles. + fill: false, + + // @option fillColor: String = * + // Fill color. Defaults to the value of the [`color`](#path-color) option + fillColor: null, + + // @option fillOpacity: Number = 0.2 + // Fill opacity. + fillOpacity: 0.2, + + // @option fillRule: String = 'evenodd' + // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined. + fillRule: 'evenodd', + + // className: '', + + // Option inherited from "Interactive layer" abstract class + interactive: true, + + // @option bubblingMouseEvents: Boolean = true + // When `true`, a mouse event on this path will trigger the same event on the map + // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used). + bubblingMouseEvents: true + }, + + beforeAdd: function (map) { + // Renderer is set here because we need to call renderer.getEvents + // before this.getEvents. + this._renderer = map.getRenderer(this); + }, + + onAdd: function () { + this._renderer._initPath(this); + this._reset(); + this._renderer._addPath(this); + }, + + onRemove: function () { + this._renderer._removePath(this); + }, + + // @method redraw(): this + // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses. + redraw: function () { + if (this._map) { + this._renderer._updatePath(this); + } + return this; + }, + + // @method setStyle(style: Path options): this + // Changes the appearance of a Path based on the options in the `Path options` object. + setStyle: function (style) { + setOptions(this, style); + if (this._renderer) { + this._renderer._updateStyle(this); + } + return this; + }, + + // @method bringToFront(): this + // Brings the layer to the top of all path layers. + bringToFront: function () { + if (this._renderer) { + this._renderer._bringToFront(this); + } + return this; + }, + + // @method bringToBack(): this + // Brings the layer to the bottom of all path layers. + bringToBack: function () { + if (this._renderer) { + this._renderer._bringToBack(this); + } + return this; + }, + + getElement: function () { + return this._path; + }, + + _reset: function () { + // defined in child classes + this._project(); + this._update(); + }, + + _clickTolerance: function () { + // used when doing hit detection for Canvas layers + return (this.options.stroke ? this.options.weight / 2 : 0) + this._renderer.options.tolerance; + } +}); + +/* + * @class CircleMarker + * @aka L.CircleMarker + * @inherits Path + * + * A circle of a fixed size with radius specified in pixels. Extends `Path`. + */ + +var CircleMarker = Path.extend({ + + // @section + // @aka CircleMarker options + options: { + fill: true, + + // @option radius: Number = 10 + // Radius of the circle marker, in pixels + radius: 10 + }, + + initialize: function (latlng, options) { + setOptions(this, options); + this._latlng = toLatLng(latlng); + this._radius = this.options.radius; + }, + + // @method setLatLng(latLng: LatLng): this + // Sets the position of a circle marker to a new location. + setLatLng: function (latlng) { + this._latlng = toLatLng(latlng); + this.redraw(); + return this.fire('move', {latlng: this._latlng}); + }, + + // @method getLatLng(): LatLng + // Returns the current geographical position of the circle marker + getLatLng: function () { + return this._latlng; + }, + + // @method setRadius(radius: Number): this + // Sets the radius of a circle marker. Units are in pixels. + setRadius: function (radius) { + this.options.radius = this._radius = radius; + return this.redraw(); + }, + + // @method getRadius(): Number + // Returns the current radius of the circle + getRadius: function () { + return this._radius; + }, + + setStyle : function (options) { + var radius = options && options.radius || this._radius; + Path.prototype.setStyle.call(this, options); + this.setRadius(radius); + return this; + }, + + _project: function () { + this._point = this._map.latLngToLayerPoint(this._latlng); + this._updateBounds(); + }, + + _updateBounds: function () { + var r = this._radius, + r2 = this._radiusY || r, + w = this._clickTolerance(), + p = [r + w, r2 + w]; + this._pxBounds = new Bounds(this._point.subtract(p), this._point.add(p)); + }, + + _update: function () { + if (this._map) { + this._updatePath(); + } + }, + + _updatePath: function () { + this._renderer._updateCircle(this); + }, + + _empty: function () { + return this._radius && !this._renderer._bounds.intersects(this._pxBounds); + }, + + // Needed by the `Canvas` renderer for interactivity + _containsPoint: function (p) { + return p.distanceTo(this._point) <= this._radius + this._clickTolerance(); + } +}); + + +// @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options) +// Instantiates a circle marker object given a geographical point, and an optional options object. +function circleMarker(latlng, options) { + return new CircleMarker(latlng, options); +} + +/* + * @class Circle + * @aka L.Circle + * @inherits CircleMarker + * + * A class for drawing circle overlays on a map. Extends `CircleMarker`. + * + * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion). + * + * @example + * + * ```js + * L.circle([50.5, 30.5], {radius: 200}).addTo(map); + * ``` + */ + +var Circle = CircleMarker.extend({ + + initialize: function (latlng, options, legacyOptions) { + if (typeof options === 'number') { + // Backwards compatibility with 0.7.x factory (latlng, radius, options?) + options = extend({}, legacyOptions, {radius: options}); + } + setOptions(this, options); + this._latlng = toLatLng(latlng); + + if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); } + + // @section + // @aka Circle options + // @option radius: Number; Radius of the circle, in meters. + this._mRadius = this.options.radius; + }, + + // @method setRadius(radius: Number): this + // Sets the radius of a circle. Units are in meters. + setRadius: function (radius) { + this._mRadius = radius; + return this.redraw(); + }, + + // @method getRadius(): Number + // Returns the current radius of a circle. Units are in meters. + getRadius: function () { + return this._mRadius; + }, + + // @method getBounds(): LatLngBounds + // Returns the `LatLngBounds` of the path. + getBounds: function () { + var half = [this._radius, this._radiusY || this._radius]; + + return new LatLngBounds( + this._map.layerPointToLatLng(this._point.subtract(half)), + this._map.layerPointToLatLng(this._point.add(half))); + }, + + setStyle: Path.prototype.setStyle, + + _project: function () { + + var lng = this._latlng.lng, + lat = this._latlng.lat, + map = this._map, + crs = map.options.crs; + + if (crs.distance === Earth.distance) { + var d = Math.PI / 180, + latR = (this._mRadius / Earth.R) / d, + top = map.project([lat + latR, lng]), + bottom = map.project([lat - latR, lng]), + p = top.add(bottom).divideBy(2), + lat2 = map.unproject(p).lat, + lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) / + (Math.cos(lat * d) * Math.cos(lat2 * d))) / d; + + if (isNaN(lngR) || lngR === 0) { + lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425 + } + + this._point = p.subtract(map.getPixelOrigin()); + this._radius = isNaN(lngR) ? 0 : p.x - map.project([lat2, lng - lngR]).x; + this._radiusY = p.y - top.y; + + } else { + var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0])); + + this._point = map.latLngToLayerPoint(this._latlng); + this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x; + } + + this._updateBounds(); + } +}); + +// @factory L.circle(latlng: LatLng, options?: Circle options) +// Instantiates a circle object given a geographical point, and an options object +// which contains the circle radius. +// @alternative +// @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options) +// Obsolete way of instantiating a circle, for compatibility with 0.7.x code. +// Do not use in new applications or plugins. +function circle(latlng, options, legacyOptions) { + return new Circle(latlng, options, legacyOptions); +} + +/* + * @class Polyline + * @aka L.Polyline + * @inherits Path + * + * A class for drawing polyline overlays on a map. Extends `Path`. + * + * @example + * + * ```js + * // create a red polyline from an array of LatLng points + * var latlngs = [ + * [45.51, -122.68], + * [37.77, -122.43], + * [34.04, -118.2] + * ]; + * + * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map); + * + * // zoom the map to the polyline + * map.fitBounds(polyline.getBounds()); + * ``` + * + * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape: + * + * ```js + * // create a red polyline from an array of arrays of LatLng points + * var latlngs = [ + * [[45.51, -122.68], + * [37.77, -122.43], + * [34.04, -118.2]], + * [[40.78, -73.91], + * [41.83, -87.62], + * [32.76, -96.72]] + * ]; + * ``` + */ + + +var Polyline = Path.extend({ + + // @section + // @aka Polyline options + options: { + // @option smoothFactor: Number = 1.0 + // How much to simplify the polyline on each zoom level. More means + // better performance and smoother look, and less means more accurate representation. + smoothFactor: 1.0, + + // @option noClip: Boolean = false + // Disable polyline clipping. + noClip: false + }, + + initialize: function (latlngs, options) { + setOptions(this, options); + this._setLatLngs(latlngs); + }, + + // @method getLatLngs(): LatLng[] + // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline. + getLatLngs: function () { + return this._latlngs; + }, + + // @method setLatLngs(latlngs: LatLng[]): this + // Replaces all the points in the polyline with the given array of geographical points. + setLatLngs: function (latlngs) { + this._setLatLngs(latlngs); + return this.redraw(); + }, + + // @method isEmpty(): Boolean + // Returns `true` if the Polyline has no LatLngs. + isEmpty: function () { + return !this._latlngs.length; + }, + + // @method closestLayerPoint: Point + // Returns the point closest to `p` on the Polyline. + closestLayerPoint: function (p) { + var minDistance = Infinity, + minPoint = null, + closest = _sqClosestPointOnSegment, + p1, p2; + + for (var j = 0, jLen = this._parts.length; j < jLen; j++) { + var points = this._parts[j]; + + for (var i = 1, len = points.length; i < len; i++) { + p1 = points[i - 1]; + p2 = points[i]; + + var sqDist = closest(p, p1, p2, true); + + if (sqDist < minDistance) { + minDistance = sqDist; + minPoint = closest(p, p1, p2); + } + } + } + if (minPoint) { + minPoint.distance = Math.sqrt(minDistance); + } + return minPoint; + }, + + // @method getCenter(): LatLng + // Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the polyline. + getCenter: function () { + // throws error when not yet added to map as this center calculation requires projected coordinates + if (!this._map) { + throw new Error('Must add layer to map before using getCenter()'); + } + + var i, halfDist, segDist, dist, p1, p2, ratio, + points = this._rings[0], + len = points.length; + + if (!len) { return null; } + + // polyline centroid algorithm; only uses the first ring if there are multiple + + for (i = 0, halfDist = 0; i < len - 1; i++) { + halfDist += points[i].distanceTo(points[i + 1]) / 2; + } + + // The line is so small in the current view that all points are on the same pixel. + if (halfDist === 0) { + return this._map.layerPointToLatLng(points[0]); + } + + for (i = 0, dist = 0; i < len - 1; i++) { + p1 = points[i]; + p2 = points[i + 1]; + segDist = p1.distanceTo(p2); + dist += segDist; + + if (dist > halfDist) { + ratio = (dist - halfDist) / segDist; + return this._map.layerPointToLatLng([ + p2.x - ratio * (p2.x - p1.x), + p2.y - ratio * (p2.y - p1.y) + ]); + } + } + }, + + // @method getBounds(): LatLngBounds + // Returns the `LatLngBounds` of the path. + getBounds: function () { + return this._bounds; + }, + + // @method addLatLng(latlng: LatLng, latlngs? LatLng[]): this + // Adds a given point to the polyline. By default, adds to the first ring of + // the polyline in case of a multi-polyline, but can be overridden by passing + // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)). + addLatLng: function (latlng, latlngs) { + latlngs = latlngs || this._defaultShape(); + latlng = toLatLng(latlng); + latlngs.push(latlng); + this._bounds.extend(latlng); + return this.redraw(); + }, + + _setLatLngs: function (latlngs) { + this._bounds = new LatLngBounds(); + this._latlngs = this._convertLatLngs(latlngs); + }, + + _defaultShape: function () { + return isFlat(this._latlngs) ? this._latlngs : this._latlngs[0]; + }, + + // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way + _convertLatLngs: function (latlngs) { + var result = [], + flat = isFlat(latlngs); + + for (var i = 0, len = latlngs.length; i < len; i++) { + if (flat) { + result[i] = toLatLng(latlngs[i]); + this._bounds.extend(result[i]); + } else { + result[i] = this._convertLatLngs(latlngs[i]); + } + } + + return result; + }, + + _project: function () { + var pxBounds = new Bounds(); + this._rings = []; + this._projectLatlngs(this._latlngs, this._rings, pxBounds); + + var w = this._clickTolerance(), + p = new Point(w, w); + + if (this._bounds.isValid() && pxBounds.isValid()) { + pxBounds.min._subtract(p); + pxBounds.max._add(p); + this._pxBounds = pxBounds; + } + }, + + // recursively turns latlngs into a set of rings with projected coordinates + _projectLatlngs: function (latlngs, result, projectedBounds) { + var flat = latlngs[0] instanceof LatLng, + len = latlngs.length, + i, ring; + + if (flat) { + ring = []; + for (i = 0; i < len; i++) { + ring[i] = this._map.latLngToLayerPoint(latlngs[i]); + projectedBounds.extend(ring[i]); + } + result.push(ring); + } else { + for (i = 0; i < len; i++) { + this._projectLatlngs(latlngs[i], result, projectedBounds); + } + } + }, + + // clip polyline by renderer bounds so that we have less to render for performance + _clipPoints: function () { + var bounds = this._renderer._bounds; + + this._parts = []; + if (!this._pxBounds || !this._pxBounds.intersects(bounds)) { + return; + } + + if (this.options.noClip) { + this._parts = this._rings; + return; + } + + var parts = this._parts, + i, j, k, len, len2, segment, points; + + for (i = 0, k = 0, len = this._rings.length; i < len; i++) { + points = this._rings[i]; + + for (j = 0, len2 = points.length; j < len2 - 1; j++) { + segment = clipSegment(points[j], points[j + 1], bounds, j, true); + + if (!segment) { continue; } + + parts[k] = parts[k] || []; + parts[k].push(segment[0]); + + // if segment goes out of screen, or it's the last one, it's the end of the line part + if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) { + parts[k].push(segment[1]); + k++; + } + } + } + }, + + // simplify each clipped part of the polyline for performance + _simplifyPoints: function () { + var parts = this._parts, + tolerance = this.options.smoothFactor; + + for (var i = 0, len = parts.length; i < len; i++) { + parts[i] = simplify(parts[i], tolerance); + } + }, + + _update: function () { + if (!this._map) { return; } + + this._clipPoints(); + this._simplifyPoints(); + this._updatePath(); + }, + + _updatePath: function () { + this._renderer._updatePoly(this); + }, + + // Needed by the `Canvas` renderer for interactivity + _containsPoint: function (p, closed) { + var i, j, k, len, len2, part, + w = this._clickTolerance(); + + if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; } + + // hit detection for polylines + for (i = 0, len = this._parts.length; i < len; i++) { + part = this._parts[i]; + + for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) { + if (!closed && (j === 0)) { continue; } + + if (pointToSegmentDistance(p, part[k], part[j]) <= w) { + return true; + } + } + } + return false; + } +}); + +// @factory L.polyline(latlngs: LatLng[], options?: Polyline options) +// Instantiates a polyline object given an array of geographical points and +// optionally an options object. You can create a `Polyline` object with +// multiple separate lines (`MultiPolyline`) by passing an array of arrays +// of geographic points. +function polyline(latlngs, options) { + return new Polyline(latlngs, options); +} + +// Retrocompat. Allow plugins to support Leaflet versions before and after 1.1. +Polyline._flat = _flat; + +/* + * @class Polygon + * @aka L.Polygon + * @inherits Polyline + * + * A class for drawing polygon overlays on a map. Extends `Polyline`. + * + * Note that points you pass when creating a polygon shouldn't have an additional last point equal to the first one — it's better to filter out such points. + * + * + * @example + * + * ```js + * // create a red polygon from an array of LatLng points + * var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]]; + * + * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map); + * + * // zoom the map to the polygon + * map.fitBounds(polygon.getBounds()); + * ``` + * + * You can also pass an array of arrays of latlngs, with the first array representing the outer shape and the other arrays representing holes in the outer shape: + * + * ```js + * var latlngs = [ + * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring + * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole + * ]; + * ``` + * + * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape. + * + * ```js + * var latlngs = [ + * [ // first polygon + * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring + * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole + * ], + * [ // second polygon + * [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]] + * ] + * ]; + * ``` + */ + +var Polygon = Polyline.extend({ + + options: { + fill: true + }, + + isEmpty: function () { + return !this._latlngs.length || !this._latlngs[0].length; + }, + + getCenter: function () { + // throws error when not yet added to map as this center calculation requires projected coordinates + if (!this._map) { + throw new Error('Must add layer to map before using getCenter()'); + } + + var i, j, p1, p2, f, area, x, y, center, + points = this._rings[0], + len = points.length; + + if (!len) { return null; } + + // polygon centroid algorithm; only uses the first ring if there are multiple + + area = x = y = 0; + + for (i = 0, j = len - 1; i < len; j = i++) { + p1 = points[i]; + p2 = points[j]; + + f = p1.y * p2.x - p2.y * p1.x; + x += (p1.x + p2.x) * f; + y += (p1.y + p2.y) * f; + area += f * 3; + } + + if (area === 0) { + // Polygon is so small that all points are on same pixel. + center = points[0]; + } else { + center = [x / area, y / area]; + } + return this._map.layerPointToLatLng(center); + }, + + _convertLatLngs: function (latlngs) { + var result = Polyline.prototype._convertLatLngs.call(this, latlngs), + len = result.length; + + // remove last point if it equals first one + if (len >= 2 && result[0] instanceof LatLng && result[0].equals(result[len - 1])) { + result.pop(); + } + return result; + }, + + _setLatLngs: function (latlngs) { + Polyline.prototype._setLatLngs.call(this, latlngs); + if (isFlat(this._latlngs)) { + this._latlngs = [this._latlngs]; + } + }, + + _defaultShape: function () { + return isFlat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0]; + }, + + _clipPoints: function () { + // polygons need a different clipping algorithm so we redefine that + + var bounds = this._renderer._bounds, + w = this.options.weight, + p = new Point(w, w); + + // increase clip padding by stroke width to avoid stroke on clip edges + bounds = new Bounds(bounds.min.subtract(p), bounds.max.add(p)); + + this._parts = []; + if (!this._pxBounds || !this._pxBounds.intersects(bounds)) { + return; + } + + if (this.options.noClip) { + this._parts = this._rings; + return; + } + + for (var i = 0, len = this._rings.length, clipped; i < len; i++) { + clipped = clipPolygon(this._rings[i], bounds, true); + if (clipped.length) { + this._parts.push(clipped); + } + } + }, + + _updatePath: function () { + this._renderer._updatePoly(this, true); + }, + + // Needed by the `Canvas` renderer for interactivity + _containsPoint: function (p) { + var inside = false, + part, p1, p2, i, j, k, len, len2; + + if (!this._pxBounds.contains(p)) { return false; } + + // ray casting algorithm for detecting if point is in polygon + for (i = 0, len = this._parts.length; i < len; i++) { + part = this._parts[i]; + + for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) { + p1 = part[j]; + p2 = part[k]; + + if (((p1.y > p.y) !== (p2.y > p.y)) && (p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x)) { + inside = !inside; + } + } + } + + // also check if it's on polygon stroke + return inside || Polyline.prototype._containsPoint.call(this, p, true); + } + +}); + + +// @factory L.polygon(latlngs: LatLng[], options?: Polyline options) +function polygon(latlngs, options) { + return new Polygon(latlngs, options); +} + +/* + * @class GeoJSON + * @aka L.GeoJSON + * @inherits FeatureGroup + * + * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse + * GeoJSON data and display it on the map. Extends `FeatureGroup`. + * + * @example + * + * ```js + * L.geoJSON(data, { + * style: function (feature) { + * return {color: feature.properties.color}; + * } + * }).bindPopup(function (layer) { + * return layer.feature.properties.description; + * }).addTo(map); + * ``` + */ + +var GeoJSON = FeatureGroup.extend({ + + /* @section + * @aka GeoJSON options + * + * @option pointToLayer: Function = * + * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally + * called when data is added, passing the GeoJSON point feature and its `LatLng`. + * The default is to spawn a default `Marker`: + * ```js + * function(geoJsonPoint, latlng) { + * return L.marker(latlng); + * } + * ``` + * + * @option style: Function = * + * A `Function` defining the `Path options` for styling GeoJSON lines and polygons, + * called internally when data is added. + * The default value is to not override any defaults: + * ```js + * function (geoJsonFeature) { + * return {} + * } + * ``` + * + * @option onEachFeature: Function = * + * A `Function` that will be called once for each created `Feature`, after it has + * been created and styled. Useful for attaching events and popups to features. + * The default is to do nothing with the newly created layers: + * ```js + * function (feature, layer) {} + * ``` + * + * @option filter: Function = * + * A `Function` that will be used to decide whether to include a feature or not. + * The default is to include all features: + * ```js + * function (geoJsonFeature) { + * return true; + * } + * ``` + * Note: dynamically changing the `filter` option will have effect only on newly + * added data. It will _not_ re-evaluate already included features. + * + * @option coordsToLatLng: Function = * + * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s. + * The default is the `coordsToLatLng` static method. + */ + + initialize: function (geojson, options) { + setOptions(this, options); + + this._layers = {}; + + if (geojson) { + this.addData(geojson); + } + }, + + // @method addData( data ): this + // Adds a GeoJSON object to the layer. + addData: function (geojson) { + var features = isArray(geojson) ? geojson : geojson.features, + i, len, feature; + + if (features) { + for (i = 0, len = features.length; i < len; i++) { + // only add this if geometry or geometries are set and not null + feature = features[i]; + if (feature.geometries || feature.geometry || feature.features || feature.coordinates) { + this.addData(feature); + } + } + return this; + } + + var options = this.options; + + if (options.filter && !options.filter(geojson)) { return this; } + + var layer = geometryToLayer(geojson, options); + if (!layer) { + return this; + } + layer.feature = asFeature(geojson); + + layer.defaultOptions = layer.options; + this.resetStyle(layer); + + if (options.onEachFeature) { + options.onEachFeature(geojson, layer); + } + + return this.addLayer(layer); + }, + + // @method resetStyle( layer ): this + // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events. + resetStyle: function (layer) { + // reset any custom styles + layer.options = extend({}, layer.defaultOptions); + this._setLayerStyle(layer, this.options.style); + return this; + }, + + // @method setStyle( style ): this + // Changes styles of GeoJSON vector layers with the given style function. + setStyle: function (style) { + return this.eachLayer(function (layer) { + this._setLayerStyle(layer, style); + }, this); + }, + + _setLayerStyle: function (layer, style) { + if (typeof style === 'function') { + style = style(layer.feature); + } + if (layer.setStyle) { + layer.setStyle(style); + } + } +}); + +// @section +// There are several static functions which can be called without instantiating L.GeoJSON: + +// @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer +// Creates a `Layer` from a given GeoJSON feature. Can use a custom +// [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng) +// functions if provided as options. +function geometryToLayer(geojson, options) { + + var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson, + coords = geometry ? geometry.coordinates : null, + layers = [], + pointToLayer = options && options.pointToLayer, + _coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng, + latlng, latlngs, i, len; + + if (!coords && !geometry) { + return null; + } + + switch (geometry.type) { + case 'Point': + latlng = _coordsToLatLng(coords); + return pointToLayer ? pointToLayer(geojson, latlng) : new Marker(latlng); + + case 'MultiPoint': + for (i = 0, len = coords.length; i < len; i++) { + latlng = _coordsToLatLng(coords[i]); + layers.push(pointToLayer ? pointToLayer(geojson, latlng) : new Marker(latlng)); + } + return new FeatureGroup(layers); + + case 'LineString': + case 'MultiLineString': + latlngs = coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng); + return new Polyline(latlngs, options); + + case 'Polygon': + case 'MultiPolygon': + latlngs = coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, _coordsToLatLng); + return new Polygon(latlngs, options); + + case 'GeometryCollection': + for (i = 0, len = geometry.geometries.length; i < len; i++) { + var layer = geometryToLayer({ + geometry: geometry.geometries[i], + type: 'Feature', + properties: geojson.properties + }, options); + + if (layer) { + layers.push(layer); + } + } + return new FeatureGroup(layers); + + default: + throw new Error('Invalid GeoJSON object.'); + } +} + +// @function coordsToLatLng(coords: Array): LatLng +// Creates a `LatLng` object from an array of 2 numbers (longitude, latitude) +// or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points. +function coordsToLatLng(coords) { + return new LatLng(coords[1], coords[0], coords[2]); +} + +// @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array +// Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array. +// `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default). +// Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function. +function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) { + var latlngs = []; + + for (var i = 0, len = coords.length, latlng; i < len; i++) { + latlng = levelsDeep ? + coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) : + (_coordsToLatLng || coordsToLatLng)(coords[i]); + + latlngs.push(latlng); + } + + return latlngs; +} + +// @function latLngToCoords(latlng: LatLng, precision?: Number): Array +// Reverse of [`coordsToLatLng`](#geojson-coordstolatlng) +function latLngToCoords(latlng, precision) { + precision = typeof precision === 'number' ? precision : 6; + return latlng.alt !== undefined ? + [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] : + [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)]; +} + +// @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean): Array +// Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs) +// `closed` determines whether the first point should be appended to the end of the array to close the feature, only used when `levelsDeep` is 0. False by default. +function latLngsToCoords(latlngs, levelsDeep, closed, precision) { + var coords = []; + + for (var i = 0, len = latlngs.length; i < len; i++) { + coords.push(levelsDeep ? + latLngsToCoords(latlngs[i], levelsDeep - 1, closed, precision) : + latLngToCoords(latlngs[i], precision)); + } + + if (!levelsDeep && closed) { + coords.push(coords[0]); + } + + return coords; +} + +function getFeature(layer, newGeometry) { + return layer.feature ? + extend({}, layer.feature, {geometry: newGeometry}) : + asFeature(newGeometry); +} + +// @function asFeature(geojson: Object): Object +// Normalize GeoJSON geometries/features into GeoJSON features. +function asFeature(geojson) { + if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') { + return geojson; + } + + return { + type: 'Feature', + properties: {}, + geometry: geojson + }; +} + +var PointToGeoJSON = { + toGeoJSON: function (precision) { + return getFeature(this, { + type: 'Point', + coordinates: latLngToCoords(this.getLatLng(), precision) + }); + } +}; + +// @namespace Marker +// @method toGeoJSON(): Object +// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature). +Marker.include(PointToGeoJSON); + +// @namespace CircleMarker +// @method toGeoJSON(): Object +// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature). +Circle.include(PointToGeoJSON); +CircleMarker.include(PointToGeoJSON); + + +// @namespace Polyline +// @method toGeoJSON(): Object +// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature). +Polyline.include({ + toGeoJSON: function (precision) { + var multi = !isFlat(this._latlngs); + + var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0, false, precision); + + return getFeature(this, { + type: (multi ? 'Multi' : '') + 'LineString', + coordinates: coords + }); + } +}); + +// @namespace Polygon +// @method toGeoJSON(): Object +// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature). +Polygon.include({ + toGeoJSON: function (precision) { + var holes = !isFlat(this._latlngs), + multi = holes && !isFlat(this._latlngs[0]); + + var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true, precision); + + if (!holes) { + coords = [coords]; + } + + return getFeature(this, { + type: (multi ? 'Multi' : '') + 'Polygon', + coordinates: coords + }); + } +}); + + +// @namespace LayerGroup +LayerGroup.include({ + toMultiPoint: function (precision) { + var coords = []; + + this.eachLayer(function (layer) { + coords.push(layer.toGeoJSON(precision).geometry.coordinates); + }); + + return getFeature(this, { + type: 'MultiPoint', + coordinates: coords + }); + }, + + // @method toGeoJSON(): Object + // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`). + toGeoJSON: function (precision) { + + var type = this.feature && this.feature.geometry && this.feature.geometry.type; + + if (type === 'MultiPoint') { + return this.toMultiPoint(precision); + } + + var isGeometryCollection = type === 'GeometryCollection', + jsons = []; + + this.eachLayer(function (layer) { + if (layer.toGeoJSON) { + var json = layer.toGeoJSON(precision); + if (isGeometryCollection) { + jsons.push(json.geometry); + } else { + var feature = asFeature(json); + // Squash nested feature collections + if (feature.type === 'FeatureCollection') { + jsons.push.apply(jsons, feature.features); + } else { + jsons.push(feature); + } + } + } + }); + + if (isGeometryCollection) { + return getFeature(this, { + geometries: jsons, + type: 'GeometryCollection' + }); + } + + return { + type: 'FeatureCollection', + features: jsons + }; + } +}); + +// @namespace GeoJSON +// @factory L.geoJSON(geojson?: Object, options?: GeoJSON options) +// Creates a GeoJSON layer. Optionally accepts an object in +// [GeoJSON format](http://geojson.org/geojson-spec.html) to display on the map +// (you can alternatively add it later with `addData` method) and an `options` object. +function geoJSON(geojson, options) { + return new GeoJSON(geojson, options); +} + +// Backward compatibility. +var geoJson = geoJSON; + +/* + * @class ImageOverlay + * @aka L.ImageOverlay + * @inherits Interactive layer + * + * Used to load and display a single image over specific bounds of the map. Extends `Layer`. + * + * @example + * + * ```js + * var imageUrl = 'http://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg', + * imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]]; + * L.imageOverlay(imageUrl, imageBounds).addTo(map); + * ``` + */ + +var ImageOverlay = Layer.extend({ + + // @section + // @aka ImageOverlay options + options: { + // @option opacity: Number = 1.0 + // The opacity of the image overlay. + opacity: 1, + + // @option alt: String = '' + // Text for the `alt` attribute of the image (useful for accessibility). + alt: '', + + // @option interactive: Boolean = false + // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered. + interactive: false, + + // @option crossOrigin: Boolean = false + // If true, the image will have its crossOrigin attribute set to ''. This is needed if you want to access image pixel data. + crossOrigin: false, + + // @option errorOverlayUrl: String = '' + // URL to the overlay image to show in place of the overlay that failed to load. + errorOverlayUrl: '', + + // @option zIndex: Number = 1 + // The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the tile layer. + zIndex: 1, + + // @option className: String = '' + // A custom class name to assign to the image. Empty by default. + className: '', + }, + + initialize: function (url, bounds, options) { // (String, LatLngBounds, Object) + this._url = url; + this._bounds = toLatLngBounds(bounds); + + setOptions(this, options); + }, + + onAdd: function () { + if (!this._image) { + this._initImage(); + + if (this.options.opacity < 1) { + this._updateOpacity(); + } + } + + if (this.options.interactive) { + addClass(this._image, 'leaflet-interactive'); + this.addInteractiveTarget(this._image); + } + + this.getPane().appendChild(this._image); + this._reset(); + }, + + onRemove: function () { + remove(this._image); + if (this.options.interactive) { + this.removeInteractiveTarget(this._image); + } + }, + + // @method setOpacity(opacity: Number): this + // Sets the opacity of the overlay. + setOpacity: function (opacity) { + this.options.opacity = opacity; + + if (this._image) { + this._updateOpacity(); + } + return this; + }, + + setStyle: function (styleOpts) { + if (styleOpts.opacity) { + this.setOpacity(styleOpts.opacity); + } + return this; + }, + + // @method bringToFront(): this + // Brings the layer to the top of all overlays. + bringToFront: function () { + if (this._map) { + toFront(this._image); + } + return this; + }, + + // @method bringToBack(): this + // Brings the layer to the bottom of all overlays. + bringToBack: function () { + if (this._map) { + toBack(this._image); + } + return this; + }, + + // @method setUrl(url: String): this + // Changes the URL of the image. + setUrl: function (url) { + this._url = url; + + if (this._image) { + this._image.src = url; + } + return this; + }, + + // @method setBounds(bounds: LatLngBounds): this + // Update the bounds that this ImageOverlay covers + setBounds: function (bounds) { + this._bounds = toLatLngBounds(bounds); + + if (this._map) { + this._reset(); + } + return this; + }, + + getEvents: function () { + var events = { + zoom: this._reset, + viewreset: this._reset + }; + + if (this._zoomAnimated) { + events.zoomanim = this._animateZoom; + } + + return events; + }, + + // @method: setZIndex(value: Number) : this + // Changes the [zIndex](#imageoverlay-zindex) of the image overlay. + setZIndex: function (value) { + this.options.zIndex = value; + this._updateZIndex(); + return this; + }, + + // @method getBounds(): LatLngBounds + // Get the bounds that this ImageOverlay covers + getBounds: function () { + return this._bounds; + }, + + // @method getElement(): HTMLElement + // Returns the instance of [`HTMLImageElement`](https://developer.mozilla.org/docs/Web/API/HTMLImageElement) + // used by this overlay. + getElement: function () { + return this._image; + }, + + _initImage: function () { + var wasElementSupplied = this._url.tagName === 'IMG'; + var img = this._image = wasElementSupplied ? this._url : create$1('img'); + + addClass(img, 'leaflet-image-layer'); + if (this._zoomAnimated) { addClass(img, 'leaflet-zoom-animated'); } + if (this.options.className) { addClass(img, this.options.className); } + + img.onselectstart = falseFn; + img.onmousemove = falseFn; + + // @event load: Event + // Fired when the ImageOverlay layer has loaded its image + img.onload = bind(this.fire, this, 'load'); + img.onerror = bind(this._overlayOnError, this, 'error'); + + if (this.options.crossOrigin) { + img.crossOrigin = ''; + } + + if (this.options.zIndex) { + this._updateZIndex(); + } + + if (wasElementSupplied) { + this._url = img.src; + return; + } + + img.src = this._url; + img.alt = this.options.alt; + }, + + _animateZoom: function (e) { + var scale = this._map.getZoomScale(e.zoom), + offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min; + + setTransform(this._image, offset, scale); + }, + + _reset: function () { + var image = this._image, + bounds = new Bounds( + this._map.latLngToLayerPoint(this._bounds.getNorthWest()), + this._map.latLngToLayerPoint(this._bounds.getSouthEast())), + size = bounds.getSize(); + + setPosition(image, bounds.min); + + image.style.width = size.x + 'px'; + image.style.height = size.y + 'px'; + }, + + _updateOpacity: function () { + setOpacity(this._image, this.options.opacity); + }, + + _updateZIndex: function () { + if (this._image && this.options.zIndex !== undefined && this.options.zIndex !== null) { + this._image.style.zIndex = this.options.zIndex; + } + }, + + _overlayOnError: function () { + // @event error: Event + // Fired when the ImageOverlay layer has loaded its image + this.fire('error'); + + var errorUrl = this.options.errorOverlayUrl; + if (errorUrl && this._url !== errorUrl) { + this._url = errorUrl; + this._image.src = errorUrl; + } + } +}); + +// @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options) +// Instantiates an image overlay object given the URL of the image and the +// geographical bounds it is tied to. +var imageOverlay = function (url, bounds, options) { + return new ImageOverlay(url, bounds, options); +}; + +/* + * @class VideoOverlay + * @aka L.VideoOverlay + * @inherits ImageOverlay + * + * Used to load and display a video player over specific bounds of the map. Extends `ImageOverlay`. + * + * A video overlay uses the [`